diff --git a/androscalpel/src/dex_id.rs b/androscalpel/src/dex_id.rs index e115f99..cebc68b 100644 --- a/androscalpel/src/dex_id.rs +++ b/androscalpel/src/dex_id.rs @@ -227,6 +227,28 @@ impl IdType { Self(ty) } + /// Try to parse a smali representation of type into a IdType. + /// + /// In practice, it's juste wrapper arround `Self::new()` for consistancy with other + /// id types. + /// + /// ``` + /// use androscalpel::IdType + /// + /// let id_type = IdType::from_smali( + /// "Landroidx/core/util/Predicate;" + /// ).unwrap(); + /// + /// assert_eq!( + /// id_type, + /// IdType::class("androidx/core/util/Predicate") + /// ); + /// ``` + #[staticmethod] + pub fn from_smali(smali_repr: &str) -> Result { + Self::new(smali_repr.into()) + } + /// Return a list of types from a string of concatenated types (like the ones between /// parentheses in the repr of a prototype) /// diff --git a/androscalpel/src/tests/mod.rs b/androscalpel/src/tests/mod.rs index 4392ae3..ec67d18 100644 --- a/androscalpel/src/tests/mod.rs +++ b/androscalpel/src/tests/mod.rs @@ -53,7 +53,7 @@ fn test_generated_apk_equivalence() { } #[test] -fn test_string_order() { +fn test_string_order_in_dex() { use std::collections::HashSet; let dex = DexFileReader::new(get_hello_world_dex()).unwrap(); let new_dex = DexFileReader::new(get_hello_world_recompilled()).unwrap(); @@ -67,7 +67,28 @@ fn test_string_order() { println!("{}", string.__str__()); } } - assert_eq!(dex.get_string_ids().len(), new_dex.get_string_ids().len()); + assert_eq!( + dex.get_string_ids().len(), + new_dex.get_string_ids().len(), + "Not the same number of strings" + ); + /* this is hell to debug + let strings: Vec<_> = (0..dex.get_string_ids().len()) + .map(|idx| DexString(dex.get_string(idx as u32).unwrap())) + .collect(); + let new_strings: Vec<_> = (0..new_dex.get_string_ids().len()) + .map(|idx| DexString(new_dex.get_string(idx as u32).unwrap())) + .collect(); + assert_eq!(strings, new_strings); + */ + for idx in 0..dex.get_string_ids().len() { + let string = DexString(dex.get_string(idx as u32).unwrap()); + let new_string = DexString(new_dex.get_string(idx as u32).unwrap()); + assert_eq!( + string, new_string, + "Strings {idx} do not match, left original, right recompiled" + ); + } } #[test] @@ -141,3 +162,58 @@ fn test_parse_field_smali() { ) ); } + +#[test] +fn test_sort_fields() { + let f1 = IdField::from_smali( + "Landroidx/lifecycle/WithLifecycleStateKt$suspendWithStateAtLeastUnchecked$2$observer$1;\ + ->$co:Lkotlinx/coroutines/CancellableContinuation;", + ) + .unwrap(); + let f2 = IdField::from_smali( + "Lcom/google/android/material/search/SearchBarAnimationHelper$$ExternalSyntheticLambda4;\ + ->f$0:Landroid/animation/Animator;", + ) + .unwrap();list1 + let mut list1 = vec![f2.clone(), f1.clone()]; + let mut list2 = vec![f1.clone(), f2.clone()]; + list1.sort(); + list2.sort(); + assert_eq!(list1, list2); + assert_eq!(list1, vec![f1.clone(), f2.clone()]); +} + +#[test] +fn test_sort_types() { + let t1 = IdType::from_smali( + "Landroidx/lifecycle/WithLifecycleStateKt$suspendWithStateAtLeastUnchecked$2$observer$1;", + ) + .unwrap(); + let t2 = IdType::from_smali( + "Lcom/google/android/material/search/SearchBarAnimationHelper$$ExternalSyntheticLambda4;", + ) + .unwrap(); + let mut list1 = vec![t2.clone(), t1.clone()]; + let mut list2 = vec![t1.clone(), t2.clone()]; + list1.sort(); + list2.sort(); + assert_eq!(list1, list2); + assert_eq!(list1, vec![t1.clone(), t2.clone()]); +} + +#[test] +fn test_sort_strings() { + let s1: DexString = + "Landroidx/lifecycle/WithLifecycleStateKt$suspendWithStateAtLeastUnchecked$2$observer$1;" + .into(); + let s2: DexString = + "Lcom/google/android/material/search/SearchBarAnimationHelper$$ExternalSyntheticLambda4;" + .into(); + + let mut list1 = vec![s2.clone(), s1.clone()]; + let mut list2 = vec![s1.clone(), s2.clone()]; + list1.sort(); + list2.sort(); + assert_eq!(list1, list2); + assert_eq!(list1, vec![s1.clone(), s2.clone()]); +} diff --git a/androscalpel_serializer/src/core/string.rs b/androscalpel_serializer/src/core/string.rs index fb520a4..8d85a9b 100644 --- a/androscalpel_serializer/src/core/string.rs +++ b/androscalpel_serializer/src/core/string.rs @@ -36,8 +36,9 @@ const VALUE_TRAYLING_BYTE_PREFIX: u8 = 0b1000_0000; impl Ord for StringDataItem { fn cmp(&self, other: &Self) -> Ordering { - self.data - .cmp(&other.data) + self.get_aosp_utf16() + .unwrap() + .cmp(&other.get_aosp_utf16().unwrap()) .then(self.utf16_size.cmp(&other.utf16_size)) } } @@ -122,6 +123,57 @@ impl From<&str> for StringDataItem { } impl StringDataItem { + /// Return the utf-16 string used by google in the aosp for comparaison & other. + fn get_aosp_utf16(&self) -> Result> { + let mut utf16_string = vec![]; + let mut i = 0; + while i < self.data.len() { + let one = self.data[i] as u32; + i += 1; + if one & 0x80 == 0 { + utf16_string.push(one); + continue; + } + + if i >= self.data.len() { + return Err(Error::InvalidStringEncoding( + "String contains invalid caracters".into(), + )); + } + let two = self.data[i] as u32; + i += 1; + if one & 0x20 == 0 { + utf16_string.push((one & 0x1f) << 6 | (two & 0x3f)); + continue; + } + + if i >= self.data.len() { + return Err(Error::InvalidStringEncoding( + "String contains invalid caracters".into(), + )); + } + let three = self.data[i] as u32; + i += 1; + if one & 0x10 == 0 { + utf16_string.push((one & 0x0f) << 12 | (two & 0x3f) << 6 | (three & 0x3f)); + continue; + } + + if i >= self.data.len() { + return Err(Error::InvalidStringEncoding( + "String contains invalid caracters".into(), + )); + } + let four = self.data[i] as u32; + i += 1; + let code_point = + (one & 0x0f) << 18 | (two & 0x3f) << 12 | (three & 0x3f) << 6 | (four & 0x3f); + let mut pair = ((code_point >> 10) + 0xd7c0) & 0xffff; + pair |= ((code_point & 0x03ff) + 0xdc00) << 16; + utf16_string.push(pair); + } + Ok(utf16_string) + } fn get_string(&self) -> Result { let mut string = String::new(); let mut i = 0; @@ -481,4 +533,25 @@ mod test { assert_eq!(encoded, expected.as_str().into()); } } + + /// Apparently I don't know how to code an order relation so here it is... + #[test] + fn test_ord_relation() { + let s1: StringDataItem = "Landroidx/lifecycle/WithLifecycleStateKt$suspendWithStateAtLeastUnchecked$2$observer$1;".into(); + let s2: StringDataItem = "Lcom/google/android/material/search/SearchBarAnimationHelper$$ExternalSyntheticLambda4;".into(); + let s1_utf16 = s1.get_aosp_utf16().unwrap(); + let s2_utf16 = s2.get_aosp_utf16().unwrap(); + assert_eq!(s1_utf16 < s2_utf16, true); + assert_eq!(s1_utf16 == s2_utf16, false); + assert_eq!(s1_utf16 > s2_utf16, false); + assert_eq!(s2_utf16 < s1_utf16, false); + assert_eq!(s2_utf16 == s1_utf16, false); + assert_eq!(s2_utf16 > s1_utf16, true); + assert_eq!(s1 < s2, true); + assert_eq!(s1 == s2, false); + assert_eq!(s1 > s2, false); + assert_eq!(s2 < s1, false); + assert_eq!(s2 == s1, false); + assert_eq!(s2 > s1, true); + } } diff --git a/test.py b/test.py index d6f8d8e..b635456 100644 --- a/test.py +++ b/test.py @@ -22,43 +22,43 @@ with z.ZipFile(APK_NAME) as zipf: apk = Apk() apk.add_dex_file(dex) -# clazz_id = IdType("Lcom/example/testapplication/ui/home/HomeViewModel;") -# proto_id = IdMethodType(IdType("Ljava/lang/String;"), []) -# method_id = IdMethod("text_gen", proto_id, clazz_id) -# -# clazz = apk.classes[clazz_id] -# method = clazz.virtual_methods[method_id] -# code = method.code -# +clazz_id = IdType("Lcom/example/testapplication/ui/home/HomeViewModel;") +proto_id = IdMethodType(IdType("Ljava/lang/String;"), []) +method_id = IdMethod("text_gen", proto_id, clazz_id) + +clazz = apk.classes[clazz_id] +method = clazz.virtual_methods[method_id] +code = method.code + logging.getLogger().setLevel(logging.WARNING) -# -# print(f"[+] Code of {method_id} ") -# for i in code.insns: -# print(f" {i}") -# print("[+] Modify code") -# -# new_insns = [] -# for i in code.insns: -# if isinstance(i, ins.ConstString): -# if i.lit == "Hello": -# i = ins.ConstString(i.reg, DexString("Degemer Mat")) -# elif i.lit == "Bye": -# i = ins.ConstString(i.reg, DexString("Kenavo")) -# new_insns.append(i) -# -# # This need improving! -# code = Code(code.registers_size, code.ins_size, code.outs_size, new_insns) -# apk.set_method_code(method_id, code) -# # apk.set_method_code(method.descriptor, code) -# -# -# clazz = apk.classes[clazz_id] -# method = clazz.virtual_methods[method_id] -# code = method.code -# print(f"[+] New code of {method_id} ") -# for i in code.insns: -# print(f" {i}") -# + +print(f"[+] Code of {method_id} ") +for i in code.insns: + print(f" {i}") +print("[+] Modify code") + +new_insns = [] +for i in code.insns: + if isinstance(i, ins.ConstString): + if i.lit == "Hello": + i = ins.ConstString(i.reg, DexString("Degemer Mat")) + elif i.lit == "Bye": + i = ins.ConstString(i.reg, DexString("Kenavo")) + new_insns.append(i) + +# This need improving! +code = Code(code.registers_size, code.ins_size, code.outs_size, new_insns) +apk.set_method_code(method_id, code) +# apk.set_method_code(method.descriptor, code) + + +clazz = apk.classes[clazz_id] +method = clazz.virtual_methods[method_id] +code = method.code +print(f"[+] New code of {method_id} ") +for i in code.insns: + print(f" {i}") + # # Strip class for debugging # classes = list( # filter( @@ -80,22 +80,22 @@ print("[+] Recompile") dex_raw = apk.gen_raw_dex() -new_apk = Apk() -for dex in dex_raw: - new_apk.add_dex_file(dex) +# new_apk = Apk() +# 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 MAX_REQ = 1 @@ -167,8 +167,8 @@ def cmp_list(a, b, req=0): cmp(a[i], b[i], req + 1) -apk_eq = new_apk == apk -print(f"[+] apk are equals: {nice_bool(apk_eq)}") +# apk_eq = new_apk == apk +# print(f"[+] apk are equals: {nice_bool(apk_eq)}") # if not apk_eq: # cmp(new_apk, apk)