add test
This commit is contained in:
parent
d47494f8f6
commit
ef6a2196a7
6 changed files with 580 additions and 9 deletions
2
TODO.md
2
TODO.md
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
324
androscalpel/src/tests/test_class.json
Normal file
324
androscalpel/src/tests/test_class.json
Normal 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": []
|
||||
}
|
||||
|
|
@ -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}"))
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue