This commit is contained in:
Jean-Marie Mineau 2024-04-15 15:13:30 +02:00
parent d47494f8f6
commit ef6a2196a7
Signed by: histausse
GPG key ID: B66AEEDA9B645AD2
6 changed files with 580 additions and 9 deletions

View file

@ -1,4 +1,6 @@
- sanity checks
- SANITY CHECK (https://cs.android.com/android/platform/superproject/main/+/main:art/libdexfile/dex/dex_file_verifier.cc if check failed, .dex is not loaded but apk do not crash !!!)
- V41 https://cs.android.com/android/platform/superproject/main/+/main:art/libdexfile/dex/dex_file_verifier.cc;drc=e8da7cd1d0e7d3535c82f8d05adcef3edd43cd40;l=634
- tests
- https://source.android.com/docs/core/runtime/dex-format#system-annotation
- goto size computation

View file

@ -2380,19 +2380,47 @@ impl DexWriter {
debug!("Link the header section");
self.header.map_off = self.section_manager.get_offset(Section::MapList);
self.header.string_ids_size = self.section_manager.get_nb_elt(Section::StringIdItem) as u32;
self.header.string_ids_off = self.section_manager.get_offset(Section::StringIdItem);
self.header.string_ids_off = if self.header.string_ids_size != 0 {
self.section_manager.get_offset(Section::StringIdItem)
} else {
0
};
self.header.type_ids_size = self.section_manager.get_nb_elt(Section::TypeIdItem) as u32;
self.header.type_ids_off = self.section_manager.get_offset(Section::TypeIdItem);
self.header.type_ids_off = if self.header.type_ids_size != 0 {
self.section_manager.get_offset(Section::TypeIdItem)
} else {
0
};
self.header.proto_ids_size = self.section_manager.get_nb_elt(Section::ProtoIdItem) as u32;
self.header.proto_ids_off = self.section_manager.get_offset(Section::ProtoIdItem);
self.header.proto_ids_off = if self.header.proto_ids_size != 0 {
self.section_manager.get_offset(Section::ProtoIdItem)
} else {
0
};
self.header.field_ids_size = self.section_manager.get_nb_elt(Section::FieldIdItem) as u32;
self.header.field_ids_off = self.section_manager.get_offset(Section::FieldIdItem);
self.header.field_ids_off = if self.header.field_ids_size != 0 {
self.section_manager.get_offset(Section::FieldIdItem)
} else {
0
};
self.header.method_ids_size = self.section_manager.get_nb_elt(Section::MethodIdItem) as u32;
self.header.method_ids_off = self.section_manager.get_offset(Section::MethodIdItem);
self.header.method_ids_off = if self.header.method_ids_size != 0 {
self.section_manager.get_offset(Section::MethodIdItem)
} else {
0
};
self.header.class_defs_size = self.section_manager.get_nb_elt(Section::ClassDefItem) as u32;
self.header.class_defs_off = self.section_manager.get_offset(Section::ClassDefItem);
self.header.class_defs_off = if self.header.class_defs_size != 0 {
self.section_manager.get_offset(Section::ClassDefItem)
} else {
0
};
self.header.data_size = self.section_manager.get_unaligned_size(Section::Data);
self.header.data_off = self.section_manager.get_offset(Section::Data);
self.header.data_off = if self.header.data_size != 0 {
self.section_manager.get_offset(Section::Data)
} else {
0
};
}
/// Link the offsets in the call site id items.

View file

@ -1,9 +1,10 @@
use super::*;
use androscalpel_serializer::Instruction as InsFormat;
use androscalpel_serializer::*;
use std::collections::HashSet;
use std::fs::File;
use std::io;
use std::io::Write;
use std::io::{Read, Write};
use std::ops::Deref;
use std::sync::{Mutex, OnceLock};
use std::time::Instant;
@ -352,3 +353,184 @@ fn test_sort_strings() {
assert_eq!(list1, list2);
assert_eq!(list1, vec![s1.clone(), s2.clone()]);
}
/// Emulate test https://cs.android.com/android/platform/superproject/main/+/main:art/libdexfile/dex/dex_file_verifier.cc;drc=e8da7cd1d0e7d3535c82f8d05adcef3edd43cd40;l=581
fn check_valid_offset_and_size(
dex: &DexFileReader,
offset: u32,
size: u32,
alignment: u32,
label: &str,
) {
if size == 0 {
if offset != 0 {
panic!("Offset 0x{offset:x} should be zero when size is zero for {label}");
}
return;
}
// offset < hdr_offset is not relevent (we index from hdr_offset=0)
let file_size = dex.get_header().file_size;
if file_size <= offset {
panic!("Offset 0x{offset:x} sould be within file size 0x{file_size:x} for {label}");
}
if file_size - offset < size {
// size + offset could overflow
panic!(
"Section end 0x{:x} should be within file size 0x{file_size:x} for {label}",
size + offset
);
}
if alignment != 0 && !(offset & (alignment - 1) == 0) {
panic!("Offset 0x{offset:x} sould be aligned by {alignment} for {label}");
}
}
#[test]
fn test_load_from_json() {
let filename = "test_class.json";
let hello_world_dex = format!("{}/src/tests/{}", env!("CARGO_MANIFEST_DIR"), filename);
let mut file = File::open(&hello_world_dex).expect(&format!("{} not found", filename));
let mut json = String::new();
file.read_to_string(&mut json).unwrap();
let test_a: Class = serde_json::from_str(&json).unwrap();
let mut apk = Apk::new();
apk.add_class(test_a).unwrap();
let dex = apk.gen_raw_dex().unwrap().pop().unwrap();
let dex = DexFileReader::new(&dex).unwrap();
check_valid_offset_and_size(
&dex,
dex.get_header().link_off,
dex.get_header().link_size,
0,
"link",
);
check_valid_offset_and_size(&dex, dex.get_header().map_off, 4, 4, "map");
check_valid_offset_and_size(
&dex,
dex.get_header().string_ids_off,
dex.get_header().string_ids_size,
4,
"string-ids",
);
check_valid_offset_and_size(
&dex,
dex.get_header().type_ids_off,
dex.get_header().type_ids_size,
4,
"type-ids",
);
check_valid_offset_and_size(
&dex,
dex.get_header().proto_ids_off,
dex.get_header().proto_ids_size,
4,
"proto-ids",
);
check_valid_offset_and_size(
&dex,
dex.get_header().field_ids_off,
dex.get_header().field_ids_size,
4,
"field-ids",
);
check_valid_offset_and_size(
&dex,
dex.get_header().method_ids_off,
dex.get_header().method_ids_size,
4,
"method-ids",
);
check_valid_offset_and_size(
&dex,
dex.get_header().class_defs_off,
dex.get_header().class_defs_size,
4,
"class-defs",
);
check_valid_offset_and_size(
&dex,
dex.get_header().data_off,
dex.get_header().data_size,
0,
"data",
);
if dex.get_header().type_ids_size > (u16::MAX as u32) {
panic!(
"Size 0x{:x} should not exceed limit 0x{:x} for type-ids",
dex.get_header().type_ids_size,
u16::MAX
)
}
if dex.get_header().proto_ids_size > (u16::MAX as u32) {
panic!(
"Size 0x{:x} should not exceed limit 0x{:x} for proto-ids",
dex.get_header().proto_ids_size,
u16::MAX
)
}
let map = dex.get_map_list();
let mut last_offset = 0;
let mut last_type = MapItemType::UnkownType(0);
let mut data_item_count = 0;
let mut data_items_left = dex.get_header().data_size;
let file_size = dex.get_header().file_size;
let mut used_types = HashSet::new();
for (i, item) in map.list.iter().enumerate() {
if last_offset >= item.offset && i != 0 {
panic!(
"Out of order map item: 0x{:x} then 0x{:x} for type {:?} (last type was {:?})",
last_offset, item.offset, item.type_, last_type
)
}
if item.offset >= file_size {
panic!(
"Map item for type {:?} ends after file: 0x{:x} >= 0x{:x}",
item.type_, item.offset, file_size
);
}
if item.type_.is_data_section_type() {
if item.size > data_items_left {
panic!(
"Too many items in data section: {} > {} (item type: {:?}",
item.size + data_item_count,
dex.get_header().data_size,
item.type_
);
}
data_items_left -= item.size;
data_item_count += item.size;
}
if used_types.contains(&item.type_) {
panic!("Duplicate map section of type {:?}", item.type_);
}
used_types.insert(item.type_);
last_offset = item.offset;
last_type = item.type_;
}
if !used_types.contains(&MapItemType::HeaderItem) {
panic!("Map is missing Header entry");
}
if !used_types.contains(&MapItemType::MapList) {
panic!("Map is missing Map List entry");
}
if !used_types.contains(&MapItemType::StringIdItem) && (dex.get_header().string_ids_off != 0) {
panic!("Map is missing String Id entry");
}
if !used_types.contains(&MapItemType::TypeIdItem) && (dex.get_header().type_ids_off != 0) {
panic!("Map is missing Type Id entry");
}
if !used_types.contains(&MapItemType::ProtoIdItem) && (dex.get_header().proto_ids_off != 0) {
panic!("Map is missing Proto Id entry");
}
if !used_types.contains(&MapItemType::FieldIdItem) && (dex.get_header().field_ids_off != 0) {
panic!("Map is missing Field Id entry");
}
if !used_types.contains(&MapItemType::MethodIdItem) && (dex.get_header().method_ids_off != 0) {
panic!("Map is missing Method Id entry");
}
if !used_types.contains(&MapItemType::ClassDefItem) && (dex.get_header().class_defs_off != 0) {
panic!("Map is missing Class Def entry");
}
}

View file

@ -0,0 +1,324 @@
{
"descriptor": {
"String": "Lcom/example/testclassloader/TestB;"
},
"is_public": true,
"is_final": false,
"is_interface": false,
"is_abstract": false,
"is_synthetic": false,
"is_annotation": false,
"is_enum": false,
"superclass": {
"String": "Ljava/lang/Object;"
},
"interfaces": [],
"source_file": {
"String": "TestB.java"
},
"static_fields": [],
"instance_fields": [],
"direct_methods": [
[
{
"class_": {
"String": "Lcom/example/testclassloader/TestB;"
},
"proto": {
"shorty": {
"String": "V"
},
"return_type": {
"String": "V"
},
"parameters": []
},
"name": {
"String": "<init>"
}
},
{
"descriptor": {
"class_": {
"String": "Lcom/example/testclassloader/TestB;"
},
"proto": {
"shorty": {
"String": "V"
},
"return_type": {
"String": "V"
},
"parameters": []
},
"name": {
"String": "<init>"
}
},
"visibility": "Public",
"is_static": false,
"is_final": false,
"is_synchronized": false,
"is_bridge": false,
"is_varargs": false,
"is_native": false,
"is_abstract": false,
"is_strictfp": false,
"is_synthetic": false,
"is_constructor": true,
"is_declared_syncrhonized": false,
"annotations": [],
"parameters_annotations": [],
"code": {
"registers_size": 1,
"ins_size": 1,
"outs_size": 1,
"debug_info": [
3,
[
14,
0
]
],
"parameter_names": [],
"insns": [
{
"Label": {
"name": "label_00000000"
}
},
{
"InvokeDirect": {
"method": {
"class_": {
"String": "Ljava/lang/Object;"
},
"proto": {
"shorty": {
"String": "V"
},
"return_type": {
"String": "V"
},
"parameters": []
},
"name": {
"String": "<init>"
}
},
"args": [
0
]
}
},
{
"Label": {
"name": "label_00000003"
}
},
{
"ReturnVoid": null
}
]
}
}
]
],
"virtual_methods": [
[
{
"class_": {
"String": "Lcom/example/testclassloader/TestB;"
},
"proto": {
"shorty": {
"String": "L"
},
"return_type": {
"String": "Ljava/lang/String;"
},
"parameters": []
},
"name": {
"String": "val"
}
},
{
"descriptor": {
"class_": {
"String": "Lcom/example/testclassloader/TestB;"
},
"proto": {
"shorty": {
"String": "L"
},
"return_type": {
"String": "Ljava/lang/String;"
},
"parameters": []
},
"name": {
"String": "val"
}
},
"visibility": "Public",
"is_static": false,
"is_final": false,
"is_synchronized": false,
"is_bridge": false,
"is_varargs": false,
"is_native": false,
"is_abstract": false,
"is_strictfp": false,
"is_synthetic": false,
"is_constructor": false,
"is_declared_syncrhonized": false,
"annotations": [],
"parameters_annotations": [],
"code": {
"registers_size": 2,
"ins_size": 1,
"outs_size": 1,
"debug_info": [
6,
[
14,
0
]
],
"parameter_names": [],
"insns": [
{
"Label": {
"name": "label_00000000"
}
},
{
"InvokeVirtual": {
"method": {
"class_": {
"String": "Ljava/lang/Object;"
},
"proto": {
"shorty": {
"String": "L"
},
"return_type": {
"String": "Ljava/lang/Class;"
},
"parameters": []
},
"name": {
"String": "getClass"
}
},
"args": [
1
]
}
},
{
"Label": {
"name": "label_00000003"
}
},
{
"MoveResultObject": {
"to": 0
}
},
{
"Label": {
"name": "label_00000004"
}
},
{
"InvokeVirtual": {
"method": {
"class_": {
"String": "Ljava/lang/Class;"
},
"proto": {
"shorty": {
"String": "L"
},
"return_type": {
"String": "Ljava/lang/ClassLoader;"
},
"parameters": []
},
"name": {
"String": "getClassLoader"
}
},
"args": [
0
]
}
},
{
"Label": {
"name": "label_00000007"
}
},
{
"MoveResultObject": {
"to": 0
}
},
{
"Label": {
"name": "label_00000008"
}
},
{
"InvokeVirtual": {
"method": {
"class_": {
"String": "Ljava/lang/Object;"
},
"proto": {
"shorty": {
"String": "L"
},
"return_type": {
"String": "Ljava/lang/String;"
},
"parameters": []
},
"name": {
"String": "toString"
}
},
"args": [
0
]
}
},
{
"Label": {
"name": "label_0000000B"
}
},
{
"MoveResultObject": {
"to": 0
}
},
{
"Label": {
"name": "label_0000000C"
}
},
{
"ReturnObject": {
"reg": 0
}
}
]
}
}
]
],
"annotations": []
}

View file

@ -50,7 +50,9 @@ impl<'a> DexFileReader<'a> {
method_handles: vec![],
map_list: MapList { list: vec![] },
};
tmp_file.map_list = tmp_file.get_struct_at_offset(tmp_file.header.map_off)?;
if tmp_file.header.map_off != 0 {
tmp_file.map_list = tmp_file.get_struct_at_offset(tmp_file.header.map_off)?;
}
tmp_file.string_ids = tmp_file.get_item_list::<StringIdItem>(
tmp_file.header.string_ids_off,
tmp_file.header.string_ids_size,
@ -365,6 +367,9 @@ impl<'a> DexFileReader<'a> {
}
fn get_item_list<T: Serializable>(&self, offset: u32, size: u32) -> Result<Vec<T>> {
if offset == 0 {
return Ok(vec![]);
}
let mut buffer = Cursor::new(self.data);
buffer.seek(SeekFrom::Start(offset as u64)).map_err(|err| {
Error::DeserializationError(format!("Failed to seek 0x{offset:x} position: {err}"))

View file

@ -71,6 +71,36 @@ pub enum MapItemType {
UnkownType(u16),
}
impl MapItemType {
/// If data of this type is stored in the data section
pub fn is_data_section_type(&self) -> bool {
match self {
Self::HeaderItem => false,
Self::StringIdItem => false,
Self::TypeIdItem => false,
Self::ProtoIdItem => false,
Self::FieldIdItem => false,
Self::MethodIdItem => false,
Self::ClassDefItem => false,
Self::CallSiteIdItem => true,
Self::MethodHandleItem => true,
Self::MapList => true,
Self::TypeList => true,
Self::AnnotationSetRefList => true,
Self::AnnotationSetItem => true,
Self::ClassDataItem => true,
Self::CodeItem => true,
Self::StringDataItem => true,
Self::DebugInfoItem => true,
Self::AnnotationItem => true,
Self::EncodedArrayItem => true,
Self::AnnotationsDirectoryItem => true,
Self::HiddenapiClassDataItem => true,
Self::UnkownType(_) => true, // Most likely
}
}
}
impl MapList {
/// The size field of a MapList.
pub fn size_field(&self) -> u32 {