add map layout feature for dex file
This commit is contained in:
parent
4b1cc379a4
commit
d991ac4dcd
8 changed files with 188 additions and 6 deletions
|
|
@ -16,6 +16,7 @@ androscalpel_platform_api_list = { version = "0.1.0", path = "../androscalpel_pl
|
||||||
anyhow = { version = "1.0.75", features = ["backtrace"] }
|
anyhow = { version = "1.0.75", features = ["backtrace"] }
|
||||||
apk_frauder = { version = "0.1.0", path = "../apk_frauder" }
|
apk_frauder = { version = "0.1.0", path = "../apk_frauder" }
|
||||||
log = "0.4.20"
|
log = "0.4.20"
|
||||||
|
# TODO: remove python support
|
||||||
pyo3 = { version = "0.23.4", features = ["anyhow", "abi3-py38", "extension-module"], optional = true}
|
pyo3 = { version = "0.23.4", features = ["anyhow", "abi3-py38", "extension-module"], optional = true}
|
||||||
pyo3-log = { version = "0.12.1", optional = true}
|
pyo3-log = { version = "0.12.1", optional = true}
|
||||||
rayon = "1.9.0"
|
rayon = "1.9.0"
|
||||||
|
|
@ -32,11 +33,12 @@ env_logger = "0.11.6"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["code-analysis", "platform-list"]
|
default = ["code-analysis", "platform-list"]
|
||||||
# TODO: need refactoring to https://github.com/PyO3/pyo3/issues/2935#issuecomment-2560930677 or cfg_eval https://github.com/rust-lang/rust/issues/82679
|
# TODO: remove python support
|
||||||
python = ["pyo3", "pyo3-log"] # Currently not supported
|
python = ["pyo3", "pyo3-log"] # Currently not supported
|
||||||
external-zip-reader = ["zip"]
|
external-zip-reader = ["zip"]
|
||||||
platform-list = ["androscalpel_platform_api_list"]
|
platform-list = ["androscalpel_platform_api_list"]
|
||||||
code-analysis = []
|
code-analysis = []
|
||||||
|
map_dex_file = ["androscalpel_serializer/map_dex_file"]
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "list-method"
|
name = "list-method"
|
||||||
|
|
|
||||||
40
androscalpel/examples/map_layout.rs
Normal file
40
androscalpel/examples/map_layout.rs
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use androscalpel::Apk;
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
|
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
#[command(version, about, long_about = None, arg_required_else_help = true)]
|
||||||
|
struct Cli {
|
||||||
|
#[arg(short, long)]
|
||||||
|
dex: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
if cfg!(not(feature = "map_dex_file")) {
|
||||||
|
panic!("This program must be compiled with the map_dex_file feature: `cargo run --example map_layout --features map_dex_file`");
|
||||||
|
}
|
||||||
|
env_logger::init();
|
||||||
|
let cli = Cli::parse();
|
||||||
|
let mut apk = Apk::new();
|
||||||
|
let data = std::fs::read(&cli.dex).unwrap();
|
||||||
|
apk.add_dex_file(
|
||||||
|
&cli.dex.into_os_string().into_string().unwrap(),
|
||||||
|
&data,
|
||||||
|
|_, _, _| None,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
//load_apk(File::open(&cli.apk).unwrap(), |_, _, _| None, false).unwrap();
|
||||||
|
for (name, dex) in &apk.dex_files {
|
||||||
|
println!("{name}:");
|
||||||
|
#[cfg(feature = "map_dex_file")]
|
||||||
|
for (off, size, dscr) in dex.layout_map.iter() {
|
||||||
|
let end = off + *size as u32;
|
||||||
|
let mut dscr = dscr.clone();
|
||||||
|
dscr.truncate(100);
|
||||||
|
println!("0x{off:x} -> 0x{end:x}: {dscr}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -33,6 +33,11 @@ pub struct DexFile {
|
||||||
/// The binary of the dexfile.
|
/// The binary of the dexfile.
|
||||||
#[serde(skip_serializing)]
|
#[serde(skip_serializing)]
|
||||||
pub(crate) bin_cache: Option<Vec<u8>>, // TODO: invalidate the cache !!!
|
pub(crate) bin_cache: Option<Vec<u8>>, // TODO: invalidate the cache !!!
|
||||||
|
/// Map chunks of the file to as string describing the struct.
|
||||||
|
/// Usefull to examine malformed file but slow down the parsing
|
||||||
|
/// and consume a lot of memory.
|
||||||
|
#[cfg(feature = "map_dex_file")]
|
||||||
|
pub layout_map: Vec<(u32, usize, String)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DexFile {
|
impl DexFile {
|
||||||
|
|
@ -115,11 +120,15 @@ impl DexFile {
|
||||||
classes: classes0,
|
classes: classes0,
|
||||||
not_referenced_strings: self.not_referenced_strings,
|
not_referenced_strings: self.not_referenced_strings,
|
||||||
bin_cache: None,
|
bin_cache: None,
|
||||||
|
#[cfg(feature = "map_dex_file")]
|
||||||
|
layout_map: vec![],
|
||||||
},
|
},
|
||||||
Self {
|
Self {
|
||||||
classes: classes1,
|
classes: classes1,
|
||||||
not_referenced_strings: HashSet::new(),
|
not_referenced_strings: HashSet::new(),
|
||||||
bin_cache: None,
|
bin_cache: None,
|
||||||
|
#[cfg(feature = "map_dex_file")]
|
||||||
|
layout_map: vec![],
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -155,6 +164,8 @@ impl<V: VisitorMut> VisitableMut<V> for DexFile {
|
||||||
classes
|
classes
|
||||||
},
|
},
|
||||||
bin_cache: None,
|
bin_cache: None,
|
||||||
|
#[cfg(feature = "map_dex_file")]
|
||||||
|
layout_map: vec![],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2943,6 +2954,8 @@ impl Apk {
|
||||||
classes,
|
classes,
|
||||||
not_referenced_strings,
|
not_referenced_strings,
|
||||||
bin_cache,
|
bin_cache,
|
||||||
|
#[cfg(feature = "map_dex_file")]
|
||||||
|
layout_map: _,
|
||||||
},
|
},
|
||||||
)|
|
)|
|
||||||
{
|
{
|
||||||
|
|
@ -3220,7 +3233,10 @@ impl Apk {
|
||||||
.map(DexString)
|
.map(DexString)
|
||||||
.collect(),
|
.collect(),
|
||||||
bin_cache: if cache { Some(data.to_vec()) } else { None },
|
bin_cache: if cache { Some(data.to_vec()) } else { None },
|
||||||
|
#[cfg(feature = "map_dex_file")]
|
||||||
|
layout_map: dex.get_chunks(),
|
||||||
};
|
};
|
||||||
|
|
||||||
self.dex_files.insert(name, dex_file);
|
self.dex_files.insert(name, dex_file);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,3 +7,9 @@ license = "AGPL-3.0-or-later"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
androscalpel_serializer_derive = { path = "../androscalpel_serializer_derive" }
|
androscalpel_serializer_derive = { path = "../androscalpel_serializer_derive" }
|
||||||
log = "0.4.20"
|
log = "0.4.20"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
# Map sections of the binary dex file to the parsed value.
|
||||||
|
# Aims to explore malformated / strange dex files, but slows
|
||||||
|
# the parsing and consumes a lot of memory.
|
||||||
|
map_dex_file = []
|
||||||
|
|
|
||||||
57
androscalpel_serializer/src/file_reader/map_dex_file.rs
Normal file
57
androscalpel_serializer/src/file_reader/map_dex_file.rs
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
//! Most of the logic for the map_dex_file feature.
|
||||||
|
|
||||||
|
use crate::DexFileReader;
|
||||||
|
|
||||||
|
impl<'a> DexFileReader<'a> {
|
||||||
|
/// List the different structures of the dex file, in order of
|
||||||
|
/// offset, including holes.
|
||||||
|
pub fn get_chunks(&self) -> Vec<(u32, usize, String)> {
|
||||||
|
let dex_size = self.data.len();
|
||||||
|
let mut structs: Vec<_> = self
|
||||||
|
.layout_map
|
||||||
|
.lock()
|
||||||
|
.expect("Failed to acquire mutex lock on layout_map")
|
||||||
|
.iter()
|
||||||
|
.map(|((off, size), desc)| (*off, *size, desc.clone()))
|
||||||
|
.collect();
|
||||||
|
structs.sort();
|
||||||
|
let mut chunks = vec![];
|
||||||
|
let mut last_off = 0;
|
||||||
|
for (off, size, desc) in structs.into_iter() {
|
||||||
|
if off > last_off {
|
||||||
|
let size = (off - last_off) as usize;
|
||||||
|
// ignore padding
|
||||||
|
if (size < 4)
|
||||||
|
&& self.data[last_off as usize..off as usize]
|
||||||
|
.iter()
|
||||||
|
.all(|&b| b == 0)
|
||||||
|
{
|
||||||
|
chunks.push((
|
||||||
|
last_off,
|
||||||
|
size,
|
||||||
|
format!(
|
||||||
|
"Padding: {:x?}",
|
||||||
|
&self.data[last_off as usize..off as usize]
|
||||||
|
),
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
chunks.push((last_off, size, "Unreferenced Data".into()));
|
||||||
|
}
|
||||||
|
last_off = off;
|
||||||
|
}
|
||||||
|
// TODO: do something with overlapping struct?
|
||||||
|
if off + size as u32 > last_off {
|
||||||
|
last_off = off + size as u32;
|
||||||
|
}
|
||||||
|
chunks.push((off, size, desc));
|
||||||
|
}
|
||||||
|
if (last_off as usize) < dex_size {
|
||||||
|
chunks.push((
|
||||||
|
last_off,
|
||||||
|
dex_size - last_off as usize,
|
||||||
|
"Unreferenced Data".into(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
chunks
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -9,6 +9,12 @@ use log::{error, info, warn};
|
||||||
use std::io::{Cursor, Seek, SeekFrom};
|
use std::io::{Cursor, Seek, SeekFrom};
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
|
||||||
|
#[cfg(feature = "map_dex_file")]
|
||||||
|
use std::{collections::HashMap, sync::Mutex};
|
||||||
|
|
||||||
|
#[cfg(feature = "map_dex_file")]
|
||||||
|
pub mod map_dex_file;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct DexFileReader<'a> {
|
pub struct DexFileReader<'a> {
|
||||||
// Ideally, this would be a Read+Seek, but Read+Seek is not thread safe, while we can
|
// Ideally, this would be a Read+Seek, but Read+Seek is not thread safe, while we can
|
||||||
|
|
@ -32,6 +38,8 @@ pub struct DexFileReader<'a> {
|
||||||
method_handles: Vec<MethodHandleItem>,
|
method_handles: Vec<MethodHandleItem>,
|
||||||
hiddenapi_class_data: Option<HiddenapiClassDataItem>,
|
hiddenapi_class_data: Option<HiddenapiClassDataItem>,
|
||||||
map_list: MapList,
|
map_list: MapList,
|
||||||
|
#[cfg(feature = "map_dex_file")]
|
||||||
|
layout_map: Mutex<HashMap<(u32, usize), String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> DexFileReader<'a> {
|
impl<'a> DexFileReader<'a> {
|
||||||
|
|
@ -53,7 +61,18 @@ impl<'a> DexFileReader<'a> {
|
||||||
method_handles: vec![],
|
method_handles: vec![],
|
||||||
hiddenapi_class_data: None,
|
hiddenapi_class_data: None,
|
||||||
map_list: MapList { list: vec![] },
|
map_list: MapList { list: vec![] },
|
||||||
|
#[cfg(feature = "map_dex_file")]
|
||||||
|
layout_map: Mutex::new(HashMap::new()),
|
||||||
};
|
};
|
||||||
|
#[cfg(feature = "map_dex_file")]
|
||||||
|
tmp_file
|
||||||
|
.layout_map
|
||||||
|
.lock()
|
||||||
|
.expect("Failed to acquire mutex lock on layout_map")
|
||||||
|
.insert(
|
||||||
|
(0, tmp_file.header.size()),
|
||||||
|
format!("{:x?}", tmp_file.header),
|
||||||
|
);
|
||||||
if tmp_file.header.map_off != 0 {
|
if tmp_file.header.map_off != 0 {
|
||||||
tmp_file.map_list = tmp_file.get_struct_at_offset(tmp_file.header.map_off)?;
|
tmp_file.map_list = tmp_file.get_struct_at_offset(tmp_file.header.map_off)?;
|
||||||
}
|
}
|
||||||
|
|
@ -381,7 +400,11 @@ impl<'a> DexFileReader<'a> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_item_list<T: Serializable>(&self, offset: u32, size: u32) -> Result<Vec<T>> {
|
fn get_item_list<T: Serializable + std::fmt::Debug>(
|
||||||
|
&self,
|
||||||
|
offset: u32,
|
||||||
|
size: u32,
|
||||||
|
) -> Result<Vec<T>> {
|
||||||
if offset == 0 {
|
if offset == 0 {
|
||||||
return Ok(vec![]);
|
return Ok(vec![]);
|
||||||
}
|
}
|
||||||
|
|
@ -401,6 +424,17 @@ impl<'a> DexFileReader<'a> {
|
||||||
pos
|
pos
|
||||||
))
|
))
|
||||||
})?);
|
})?);
|
||||||
|
#[cfg(feature = "map_dex_file")]
|
||||||
|
if let Some(ref item) = list.last() {
|
||||||
|
let size = item.size();
|
||||||
|
// Assume two != structs cannot be at the same offset with the same time
|
||||||
|
// maybe add struct name to index?
|
||||||
|
self.layout_map
|
||||||
|
.lock()
|
||||||
|
.expect("Failed to acquire mutex lock on layout_map")
|
||||||
|
.entry((pos as u32, size))
|
||||||
|
.or_insert_with(|| format!("{item:x?}"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(list)
|
Ok(list)
|
||||||
}
|
}
|
||||||
|
|
@ -410,7 +444,10 @@ impl<'a> DexFileReader<'a> {
|
||||||
/// # Warning
|
/// # Warning
|
||||||
///
|
///
|
||||||
/// If the offset is invalid, UB.
|
/// If the offset is invalid, UB.
|
||||||
pub fn get_struct_at_offset<T: Serializable>(&self, offset: u32) -> Result<T> {
|
pub fn get_struct_at_offset<T: Serializable + std::fmt::Debug>(
|
||||||
|
&self,
|
||||||
|
offset: u32,
|
||||||
|
) -> Result<T> {
|
||||||
let mut buffer = Cursor::new(self.data);
|
let mut buffer = Cursor::new(self.data);
|
||||||
buffer.seek(SeekFrom::Start(offset as u64)).unwrap();
|
buffer.seek(SeekFrom::Start(offset as u64)).unwrap();
|
||||||
let r = T::deserialize(&mut buffer).map_err(|err| {
|
let r = T::deserialize(&mut buffer).map_err(|err| {
|
||||||
|
|
@ -433,6 +470,17 @@ impl<'a> DexFileReader<'a> {
|
||||||
buffer.position()
|
buffer.position()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "map_dex_file")]
|
||||||
|
if let Ok(ref r) = r {
|
||||||
|
let size = r.size();
|
||||||
|
// Assume two != structs cannot be at the same offset with the same time
|
||||||
|
// maybe add struct name to index?
|
||||||
|
self.layout_map
|
||||||
|
.lock()
|
||||||
|
.expect("Failed to acquire mutex lock on layout_map")
|
||||||
|
.entry((offset, size))
|
||||||
|
.or_insert_with(|| format!("{r:x?}"));
|
||||||
|
}
|
||||||
r
|
r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1,3 +1,16 @@
|
||||||
|
//! This crate parse/write dalvik structures from binary to rust strucs.
|
||||||
|
//! Those strucs are close to the binary representation and follow the dalvik as
|
||||||
|
//! define by google: <https://source.android.com/docs/core/runtime/dex-format>
|
||||||
|
//!
|
||||||
|
//!
|
||||||
|
//! ## Features
|
||||||
|
//!
|
||||||
|
//! ### map_dex_file
|
||||||
|
//!
|
||||||
|
//! Map sections of the binary dex file to the parsed value.
|
||||||
|
//! Aims to explore malformated / strange dex files, but slows
|
||||||
|
//! the parsing and consumes a lot of memory.
|
||||||
|
|
||||||
pub mod annotation;
|
pub mod annotation;
|
||||||
pub mod array;
|
pub mod array;
|
||||||
pub mod consts;
|
pub mod consts;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue