fix string order
This commit is contained in:
parent
0b8dce9266
commit
3a7208f1b5
4 changed files with 227 additions and 56 deletions
|
|
@ -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)
|
||||||
///
|
///
|
||||||
|
|
|
||||||
|
|
@ -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()]);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
104
test.py
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue