fix switch and array alignement
This commit is contained in:
parent
cd6c638080
commit
a0ecb1a18d
5 changed files with 165 additions and 51 deletions
6
TODO.md
6
TODO.md
|
|
@ -1,8 +1,4 @@
|
||||||
- sanity checks
|
- sanity checks
|
||||||
- tests
|
- tests
|
||||||
- https://source.android.com/docs/core/runtime/dex-format#system-annotation
|
- https://source.android.com/docs/core/runtime/dex-format#system-annotation
|
||||||
-
|
- goto size computation
|
||||||
- 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
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -721,22 +721,6 @@ impl Apk {
|
||||||
let code = if code_off == 0 {
|
let code = if code_off == 0 {
|
||||||
None
|
None
|
||||||
} else {
|
} 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::<CodeItem>(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(|| {
|
Some(Self::get_code_from_off(code_off, dex).with_context(|| {
|
||||||
format!("Failed to parse code of method {}", descriptor.__str__())
|
format!("Failed to parse code of method {}", descriptor.__str__())
|
||||||
})?)
|
})?)
|
||||||
|
|
|
||||||
|
|
@ -466,6 +466,9 @@ impl DexWriter {
|
||||||
let mut payloads = vec![];
|
let mut payloads = vec![];
|
||||||
let mut goto_idx = 0;
|
let mut goto_idx = 0;
|
||||||
let mut payload_addr = addr;
|
let mut payload_addr = addr;
|
||||||
|
if payload_addr % 2 != 0 {
|
||||||
|
payload_addr += 1; // For switch and array table alignment
|
||||||
|
}
|
||||||
addr = 0;
|
addr = 0;
|
||||||
for ins in &code.insns {
|
for ins in &code.insns {
|
||||||
match ins {
|
match ins {
|
||||||
|
|
@ -544,6 +547,14 @@ impl DexWriter {
|
||||||
elt_width: ins.elt_width,
|
elt_width: ins.elt_width,
|
||||||
data: ins.data.clone(),
|
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;
|
let data_offset = payload_addr as i32 - addr as i32;
|
||||||
payload_addr += payload.size() / 2;
|
payload_addr += payload.size() / 2;
|
||||||
payloads.push(payload);
|
payloads.push(payload);
|
||||||
|
|
@ -578,7 +589,7 @@ impl DexWriter {
|
||||||
key_targets.sort_by_key(|(key, _)| *key);
|
key_targets.sort_by_key(|(key, _)| *key);
|
||||||
let payload = if ins.is_packed() {
|
let payload = if ins.is_packed() {
|
||||||
let (first_key, _) = *key_targets.first().ok_or(anyhow!(
|
let (first_key, _) = *key_targets.first().ok_or(anyhow!(
|
||||||
"Found empty swithc in code of {}",
|
"Found empty swith in code of {}",
|
||||||
method_id.__repr__()
|
method_id.__repr__()
|
||||||
))?;
|
))?;
|
||||||
let targets: Vec<_> =
|
let targets: Vec<_> =
|
||||||
|
|
@ -587,6 +598,14 @@ impl DexWriter {
|
||||||
} else {
|
} else {
|
||||||
InsFormat::FormatSparseSwitchPayload { key_targets }
|
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;
|
let data_offset = payload_addr as i32 - addr as i32;
|
||||||
payload_addr += payload.size() / 2;
|
payload_addr += payload.size() / 2;
|
||||||
payloads.push(payload);
|
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);
|
insns.extend(payloads);
|
||||||
|
|
||||||
for try_ in &mut tries {
|
for try_ in &mut tries {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use androscalpel_serializer::Instruction as InsFormat;
|
||||||
use androscalpel_serializer::*;
|
use androscalpel_serializer::*;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io;
|
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())
|
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<CodeItem> {
|
||||||
|
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::<ClassDataItem>(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::<CodeItem>(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::<CodeItem>(meth.code_off.0)
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_generated_data_size() {
|
fn test_generated_data_size() {
|
||||||
let dex_bin = get_hello_world_recompilled();
|
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;-><init>()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]
|
#[test]
|
||||||
fn test_parse_type_list() {
|
fn test_parse_type_list() {
|
||||||
let types = IdType::get_list_from_str("IILjavalangObject;LjavalangObject;Z").unwrap();
|
let types = IdType::get_list_from_str("IILjavalangObject;LjavalangObject;Z").unwrap();
|
||||||
|
|
@ -147,6 +255,7 @@ fn test_parse_method_smali() {
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_field_smali() {
|
fn test_parse_field_smali() {
|
||||||
let proto = IdField::from_smali(
|
let proto = IdField::from_smali(
|
||||||
|
|
@ -174,7 +283,7 @@ fn test_sort_fields() {
|
||||||
"Lcom/google/android/material/search/SearchBarAnimationHelper$$ExternalSyntheticLambda4;\
|
"Lcom/google/android/material/search/SearchBarAnimationHelper$$ExternalSyntheticLambda4;\
|
||||||
->f$0:Landroid/animation/Animator;",
|
->f$0:Landroid/animation/Animator;",
|
||||||
)
|
)
|
||||||
.unwrap();list1
|
.unwrap();
|
||||||
let mut list1 = vec![f2.clone(), f1.clone()];
|
let mut list1 = vec![f2.clone(), f1.clone()];
|
||||||
let mut list2 = vec![f1.clone(), f2.clone()];
|
let mut list2 = vec![f1.clone(), f2.clone()];
|
||||||
list1.sort();
|
list1.sort();
|
||||||
|
|
|
||||||
56
test.py
56
test.py
|
|
@ -59,24 +59,24 @@ 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(
|
||||||
lambda x: x
|
# lambda x: x
|
||||||
not in [
|
# not in [
|
||||||
# IdType("Lcom/example/testapplication/ui/home/HomeViewModel;"),
|
# IdType("Lcom/example/testapplication/ui/home/HomeViewModel;"),
|
||||||
# IdType("Landroidx/navigation/NavDeepLink$Builder;"),
|
# IdType("Landroidx/navigation/NavDeepLink$Builder;"),
|
||||||
# IdType("Landroidx/constraintlayout/core/widgets/ConstraintWidget$1;"),
|
# IdType("Landroidx/constraintlayout/core/widgets/ConstraintWidget$1;"),
|
||||||
# IdType("Landroidx/appcompat/app/ActionBar;"),
|
# IdType("Landroidx/appcompat/app/ActionBar;"),
|
||||||
# IdType("Landroidx/constraintlayout/core/state/WidgetFrame;"),
|
# IdType("Landroidx/constraintlayout/core/state/WidgetFrame;"),
|
||||||
IdType("Landroidx/appcompat/app/AppCompatViewInflater;"),
|
# IdType("Landroidx/appcompat/app/AppCompatViewInflater;"),
|
||||||
],
|
# ],
|
||||||
apk.classes.keys(),
|
# apk.classes.keys(),
|
||||||
)
|
# )
|
||||||
)
|
# )
|
||||||
for cls in classes:
|
# for cls in classes:
|
||||||
apk.remove_class(cls)
|
# apk.remove_class(cls)
|
||||||
|
#
|
||||||
print("[+] Recompile")
|
print("[+] Recompile")
|
||||||
|
|
||||||
dex_raw = apk.gen_raw_dex()
|
dex_raw = apk.gen_raw_dex()
|
||||||
|
|
@ -86,16 +86,16 @@ 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
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue