fix switch and array alignement

This commit is contained in:
Jean-Marie 'Histausse' Mineau 2024-02-16 11:58:34 +01:00
parent cd6c638080
commit a0ecb1a18d
Signed by: histausse
GPG key ID: B66AEEDA9B645AD2
5 changed files with 165 additions and 51 deletions

View file

@ -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

View file

@ -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::<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(|| {
format!("Failed to parse code of method {}", descriptor.__str__())
})?)

View file

@ -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 {

View file

@ -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<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]
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;-><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]
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();

46
test.py
View file

@ -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 [
# # 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)
# 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