fix string order

This commit is contained in:
Jean-Marie 'Histausse' Mineau 2024-02-15 16:36:52 +01:00
parent 0b8dce9266
commit 3a7208f1b5
Signed by: histausse
GPG key ID: B66AEEDA9B645AD2
4 changed files with 227 additions and 56 deletions

View file

@ -227,6 +227,28 @@ impl IdType {
Self(ty) 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> {
Self::new(smali_repr.into())
}
/// Return a list of types from a string of concatenated types (like the ones between /// Return a list of types from a string of concatenated types (like the ones between
/// parentheses in the repr of a prototype) /// parentheses in the repr of a prototype)
/// ///

View file

@ -53,7 +53,7 @@ fn test_generated_apk_equivalence() {
} }
#[test] #[test]
fn test_string_order() { fn test_string_order_in_dex() {
use std::collections::HashSet; use std::collections::HashSet;
let dex = DexFileReader::new(get_hello_world_dex()).unwrap(); let dex = DexFileReader::new(get_hello_world_dex()).unwrap();
let new_dex = DexFileReader::new(get_hello_world_recompilled()).unwrap(); let new_dex = DexFileReader::new(get_hello_world_recompilled()).unwrap();
@ -67,7 +67,28 @@ fn test_string_order() {
println!("{}", string.__str__()); 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] #[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()]);
}

View file

@ -36,8 +36,9 @@ const VALUE_TRAYLING_BYTE_PREFIX: u8 = 0b1000_0000;
impl Ord for StringDataItem { impl Ord for StringDataItem {
fn cmp(&self, other: &Self) -> Ordering { fn cmp(&self, other: &Self) -> Ordering {
self.data self.get_aosp_utf16()
.cmp(&other.data) .unwrap()
.cmp(&other.get_aosp_utf16().unwrap())
.then(self.utf16_size.cmp(&other.utf16_size)) .then(self.utf16_size.cmp(&other.utf16_size))
} }
} }
@ -122,6 +123,57 @@ impl From<&str> for StringDataItem {
} }
impl StringDataItem { impl StringDataItem {
/// Return the utf-16 string used by google in the aosp for comparaison & other.
fn get_aosp_utf16(&self) -> Result<Vec<u32>> {
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<String> { fn get_string(&self) -> Result<String> {
let mut string = String::new(); let mut string = String::new();
let mut i = 0; let mut i = 0;
@ -481,4 +533,25 @@ mod test {
assert_eq!(encoded, expected.as_str().into()); 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);
}
} }

104
test.py
View file

@ -22,43 +22,43 @@ with z.ZipFile(APK_NAME) as zipf:
apk = Apk() apk = Apk()
apk.add_dex_file(dex) apk.add_dex_file(dex)
# clazz_id = IdType("Lcom/example/testapplication/ui/home/HomeViewModel;") clazz_id = IdType("Lcom/example/testapplication/ui/home/HomeViewModel;")
# proto_id = IdMethodType(IdType("Ljava/lang/String;"), []) proto_id = IdMethodType(IdType("Ljava/lang/String;"), [])
# method_id = IdMethod("text_gen", proto_id, clazz_id) method_id = IdMethod("text_gen", proto_id, clazz_id)
#
# clazz = apk.classes[clazz_id] clazz = apk.classes[clazz_id]
# method = clazz.virtual_methods[method_id] method = clazz.virtual_methods[method_id]
# code = method.code code = method.code
#
logging.getLogger().setLevel(logging.WARNING) logging.getLogger().setLevel(logging.WARNING)
#
# print(f"[+] Code of {method_id} ") print(f"[+] Code of {method_id} ")
# for i in code.insns: for i in code.insns:
# print(f" {i}") print(f" {i}")
# print("[+] Modify code") print("[+] Modify code")
#
# new_insns = [] new_insns = []
# for i in code.insns: for i in code.insns:
# if isinstance(i, ins.ConstString): if isinstance(i, ins.ConstString):
# if i.lit == "Hello": if i.lit == "Hello":
# i = ins.ConstString(i.reg, DexString("Degemer Mat")) i = ins.ConstString(i.reg, DexString("Degemer Mat"))
# elif i.lit == "Bye": elif i.lit == "Bye":
# i = ins.ConstString(i.reg, DexString("Kenavo")) i = ins.ConstString(i.reg, DexString("Kenavo"))
# new_insns.append(i) new_insns.append(i)
#
# # This need improving! # This need improving!
# code = Code(code.registers_size, code.ins_size, code.outs_size, new_insns) 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_id, code)
# # apk.set_method_code(method.descriptor, code) # apk.set_method_code(method.descriptor, code)
#
#
# clazz = apk.classes[clazz_id] clazz = apk.classes[clazz_id]
# method = clazz.virtual_methods[method_id] method = clazz.virtual_methods[method_id]
# code = method.code code = method.code
# print(f"[+] New code of {method_id} ") print(f"[+] New code of {method_id} ")
# for i in code.insns: for i in code.insns:
# print(f" {i}") print(f" {i}")
#
# # Strip class for debugging # # Strip class for debugging
# classes = list( # classes = list(
# filter( # filter(
@ -80,22 +80,22 @@ print("[+] Recompile")
dex_raw = apk.gen_raw_dex() dex_raw = apk.gen_raw_dex()
new_apk = Apk() # new_apk = Apk()
for dex in dex_raw: # for dex in dex_raw:
new_apk.add_dex_file(dex) # new_apk.add_dex_file(dex)
# print("[+] Repackage") print("[+] Repackage")
#
# utils.replace_dex( utils.replace_dex(
# APK_NAME, APK_NAME,
# APK_NAME.parent / (APK_NAME.name.removesuffix(".apk") + "-instrumented.apk"), APK_NAME.parent / (APK_NAME.name.removesuffix(".apk") + "-instrumented.apk"),
# dex_raw, dex_raw,
# Path().parent / "my-release-key.jks", Path().parent / "my-release-key.jks",
# zipalign=Path.home() / "Android" / "Sdk" / "build-tools" / "34.0.0" / "zipalign", zipalign=Path.home() / "Android" / "Sdk" / "build-tools" / "34.0.0" / "zipalign",
# apksigner=Path.home() / "Android" / "Sdk" / "build-tools" / "34.0.0" / "apksigner", apksigner=Path.home() / "Android" / "Sdk" / "build-tools" / "34.0.0" / "apksigner",
# ) )
#
last_id = None last_id = None
MAX_REQ = 1 MAX_REQ = 1
@ -167,8 +167,8 @@ def cmp_list(a, b, req=0):
cmp(a[i], b[i], req + 1) cmp(a[i], b[i], req + 1)
apk_eq = new_apk == apk # apk_eq = new_apk == apk
print(f"[+] apk are equals: {nice_bool(apk_eq)}") # print(f"[+] apk are equals: {nice_bool(apk_eq)}")
# if not apk_eq: # if not apk_eq:
# cmp(new_apk, apk) # cmp(new_apk, apk)