diff --git a/TODO.md b/TODO.md index 0571f78..a53f6fb 100644 --- a/TODO.md +++ b/TODO.md @@ -1,8 +1,4 @@ - sanity checks - tests - https://source.android.com/docs/core/runtime/dex-format#system-annotation -- -- Caused by: java.lang.VerifyError: Verifier rejected class androidx.appcompat.app.AppCompatViewInflater: android.view.View androidx.appcompat.app.AppCompatViewInflater.createView(android.view.View, java.lang.String, android.content.Context, android.util.AttributeSet, boolean, boolean, boolean, boolean): [0xFFFFFFFF] unaligned switch table: at 32, switch offset 319 (declaration of 'androidx.appcompat.app.AppCompatViewInflater' appears in /data/app/~~P3In-lzdBi-g5oUouTDKsA==/com.example.testapplication-rXxXwHN5Yo2wxHAcnkq7iw==/base.apk - - - +- goto size computation diff --git a/androscalpel/src/apk.rs b/androscalpel/src/apk.rs index 98c7783..c609003 100644 --- a/androscalpel/src/apk.rs +++ b/androscalpel/src/apk.rs @@ -721,22 +721,6 @@ impl Apk { let code = if code_off == 0 { None } else { - if descriptor - == IdMethod::from_smali( - "Landroidx/appcompat/app/AppCompatViewInflater;\ - ->createView(Landroid/view/View;Ljava/lang/String;\ - Landroid/content/Context;Landroid/util/AttributeSet;\ - ZZZZ)Landroid/view/View;", - ) - .unwrap() - { - let code_item = dex.get_struct_at_offset::(code_off)?; - let mut off = 0; - for ins in &code_item.insns { - println!("INS 0x{off:x}: {ins:x?}"); - off += ins.size(); - } - } Some(Self::get_code_from_off(code_off, dex).with_context(|| { format!("Failed to parse code of method {}", descriptor.__str__()) })?) diff --git a/androscalpel/src/dex_writer.rs b/androscalpel/src/dex_writer.rs index 8ae62a6..93f9daa 100644 --- a/androscalpel/src/dex_writer.rs +++ b/androscalpel/src/dex_writer.rs @@ -466,6 +466,9 @@ impl DexWriter { let mut payloads = vec![]; let mut goto_idx = 0; let mut payload_addr = addr; + if payload_addr % 2 != 0 { + payload_addr += 1; // For switch and array table alignment + } addr = 0; for ins in &code.insns { match ins { @@ -544,6 +547,14 @@ impl DexWriter { elt_width: ins.elt_width, data: ins.data.clone(), }; + if payload_addr % 2 != 0 { + // https://cs.android.com/android/platform/superproject/main/+/main:art/runtime/verifier/method_verifier.cc;drc=e8c3e7be783937a340cd4f3280b69962d6f1ea0c;l=1347 + // The ART check if the array data table is 4 bytes aligned (= 2 ins alligned) + // TODO: check how it is donne in android and other dex generation code. + let nop = Instruction::Nop(Nop).get_raw_ins()?; + payload_addr += nop.size() / 2; + payloads.push(nop); + } let data_offset = payload_addr as i32 - addr as i32; payload_addr += payload.size() / 2; payloads.push(payload); @@ -578,7 +589,7 @@ impl DexWriter { key_targets.sort_by_key(|(key, _)| *key); let payload = if ins.is_packed() { let (first_key, _) = *key_targets.first().ok_or(anyhow!( - "Found empty swithc in code of {}", + "Found empty swith in code of {}", method_id.__repr__() ))?; let targets: Vec<_> = @@ -587,6 +598,14 @@ impl DexWriter { } else { InsFormat::FormatSparseSwitchPayload { key_targets } }; + if payload_addr % 2 != 0 { + // https://cs.android.com/android/platform/superproject/main/+/main:art/runtime/verifier/method_verifier.cc;drc=e8c3e7be783937a340cd4f3280b69962d6f1ea0c;l=1464 + // The ART check if the switch table is 4 bytes aligned (= 2 ins alligned) + // TODO: check how it is donne in android and other dex generation code. + let nop = Instruction::Nop(Nop).get_raw_ins()?; + payload_addr += nop.size() / 2; + payloads.push(nop); + } let data_offset = payload_addr as i32 - addr as i32; payload_addr += payload.size() / 2; payloads.push(payload); @@ -1302,6 +1321,12 @@ impl DexWriter { } } } + if addr % 2 != 0 { + // make sure the payload section is 4 bytes aligned + let nop = Instruction::Nop(Nop).get_raw_ins()?; + //addr += nop.size() / 2; + insns.push(nop); + } insns.extend(payloads); for try_ in &mut tries { diff --git a/androscalpel/src/tests/mod.rs b/androscalpel/src/tests/mod.rs index ec67d18..3ddaa3f 100644 --- a/androscalpel/src/tests/mod.rs +++ b/androscalpel/src/tests/mod.rs @@ -1,4 +1,5 @@ use super::*; +use androscalpel_serializer::Instruction as InsFormat; use androscalpel_serializer::*; use std::fs::File; use std::io; @@ -31,6 +32,65 @@ fn get_hello_world_recompilled() -> &'static [u8] { HELLO_WORLD_RECOMP.get_or_init(|| get_hello_world_apk().gen_raw_dex().unwrap().pop().unwrap()) } +fn get_class_dex<'a, 'b>(name: &'a str, dex: &'b DexFileReader) -> Option<&'b ClassDefItem> { + let name = name.into(); + for class in dex.get_class_defs() { + let class_name = dex + .get_string( + dex.get_type_id(class.class_idx as usize) + .unwrap() + .descriptor_idx, + ) + .unwrap(); + if class_name == name { + return Some(class); + } + } + None +} + +fn get_method_code_dex(name: &str, dex: &DexFileReader) -> Option { + let method = IdMethod::from_smali(name).unwrap(); + let class_name: String = (&method.class_.0).into(); + let class = get_class_dex(&class_name, dex); + let class = if let Some(class) = class { + class + } else { + return None; + }; + if class.class_data_off == 0 { + return None; + } + let data = dex + .get_struct_at_offset::(class.class_data_off) + .unwrap(); + let mut idx = 0; + for meth in data.direct_methods { + idx += meth.method_idx_diff.0; + let meth_id = Apk::get_id_method_from_idx(idx as usize, dex).unwrap(); + println!("{}", meth_id.__str__()); + if meth_id == method && meth.code_off.0 != 0 { + return Some( + dex.get_struct_at_offset::(meth.code_off.0) + .unwrap(), + ); + } + } + let mut idx = 0; + for meth in data.virtual_methods { + idx += meth.method_idx_diff.0; + let meth_id = Apk::get_id_method_from_idx(idx as usize, dex).unwrap(); + println!("{}", meth_id.__str__()); + if meth_id == method && meth.code_off.0 != 0 { + return Some( + dex.get_struct_at_offset::(meth.code_off.0) + .unwrap(), + ); + } + } + None +} + #[test] fn test_generated_data_size() { let dex_bin = get_hello_world_recompilled(); @@ -91,6 +151,54 @@ fn test_string_order_in_dex() { } } +#[test] +fn test_switch_table_alignement() { + let new_dex = DexFileReader::new(get_hello_world_recompilled()).unwrap(); + let code = get_method_code_dex( + "Landroidx/appcompat/app/AppCompatViewInflater;\ + ->createView(\ + Landroid/view/View;Ljava/lang/String;\ + Landroid/content/Context;Landroid/util/AttributeSet;\ + ZZZZ\ + )Landroid/view/View;", + &new_dex, + ) + .unwrap(); + let mut addr = 0; + for ins in code.insns { + match ins { + InsFormat::FormatPackedSwitchPayload { .. } => { + assert_eq!(addr % 4, 0, "Switch table not aligned") + } + InsFormat::FormatSparseSwitchPayload { .. } => { + assert_eq!(addr % 4, 0, "Switch table not aligned") + } + _ => (), + } + addr += ins.size(); + } +} + +#[test] +fn test_array_table_alignement() { + let new_dex = DexFileReader::new(get_hello_world_recompilled()).unwrap(); + let code = get_method_code_dex( + "Landroidx/constraintlayout/core/widgets/ConstraintWidget;->()V", + &new_dex, + ) + .unwrap(); + let mut addr = 0; + for ins in code.insns { + match ins { + InsFormat::FormatFillArrayDataPayload { .. } => { + assert_eq!(addr % 4, 0, "Array Data table not aligned") + } + _ => (), + } + addr += ins.size(); + } +} + #[test] fn test_parse_type_list() { let types = IdType::get_list_from_str("IILjavalangObject;LjavalangObject;Z").unwrap(); @@ -147,6 +255,7 @@ fn test_parse_method_smali() { ) ); } + #[test] fn test_parse_field_smali() { let proto = IdField::from_smali( @@ -174,7 +283,7 @@ fn test_sort_fields() { "Lcom/google/android/material/search/SearchBarAnimationHelper$$ExternalSyntheticLambda4;\ ->f$0:Landroid/animation/Animator;", ) - .unwrap();list1 + .unwrap(); let mut list1 = vec![f2.clone(), f1.clone()]; let mut list2 = vec![f1.clone(), f2.clone()]; list1.sort(); diff --git a/test.py b/test.py index 89ff9ca..9da62a7 100644 --- a/test.py +++ b/test.py @@ -59,24 +59,24 @@ print(f"[+] New code of {method_id} ") for i in code.insns: print(f" {i}") -# Strip class for debugging -classes = list( - filter( - lambda x: x - not in [ - # IdType("Lcom/example/testapplication/ui/home/HomeViewModel;"), - # IdType("Landroidx/navigation/NavDeepLink$Builder;"), - # IdType("Landroidx/constraintlayout/core/widgets/ConstraintWidget$1;"), - # IdType("Landroidx/appcompat/app/ActionBar;"), - # IdType("Landroidx/constraintlayout/core/state/WidgetFrame;"), - IdType("Landroidx/appcompat/app/AppCompatViewInflater;"), - ], - apk.classes.keys(), - ) -) -for cls in classes: - apk.remove_class(cls) - +# # Strip class for debugging +# classes = list( +# filter( +# lambda x: x +# not in [ +# IdType("Lcom/example/testapplication/ui/home/HomeViewModel;"), +# IdType("Landroidx/navigation/NavDeepLink$Builder;"), +# IdType("Landroidx/constraintlayout/core/widgets/ConstraintWidget$1;"), +# IdType("Landroidx/appcompat/app/ActionBar;"), +# IdType("Landroidx/constraintlayout/core/state/WidgetFrame;"), +# IdType("Landroidx/appcompat/app/AppCompatViewInflater;"), +# ], +# apk.classes.keys(), +# ) +# ) +# for cls in classes: +# apk.remove_class(cls) +# print("[+] Recompile") dex_raw = apk.gen_raw_dex() @@ -86,16 +86,16 @@ for dex in dex_raw: new_apk.add_dex_file(dex) -# print("[+] Repackage") -# -# utils.replace_dex( -# APK_NAME, -# APK_NAME.parent / (APK_NAME.name.removesuffix(".apk") + "-instrumented.apk"), -# dex_raw, -# Path().parent / "my-release-key.jks", -# zipalign=Path.home() / "Android" / "Sdk" / "build-tools" / "34.0.0" / "zipalign", -# apksigner=Path.home() / "Android" / "Sdk" / "build-tools" / "34.0.0" / "apksigner", -# ) +print("[+] Repackage") + +utils.replace_dex( + APK_NAME, + APK_NAME.parent / (APK_NAME.name.removesuffix(".apk") + "-instrumented.apk"), + dex_raw, + Path().parent / "my-release-key.jks", + zipalign=Path.home() / "Android" / "Sdk" / "build-tools" / "34.0.0" / "zipalign", + apksigner=Path.home() / "Android" / "Sdk" / "build-tools" / "34.0.0" / "apksigner", +) last_id = None