From c84e3e36ccc89fdc63c7886a89b5afd5297453cb Mon Sep 17 00:00:00 2001 From: Jean-Marie 'Histausse' Mineau Date: Fri, 1 Sep 2023 16:50:46 +0200 Subject: [PATCH] start implementing field --- androscalpel/src/apk.rs | 72 +++++++++++++--------- androscalpel/src/class.rs | 19 +++++- androscalpel/src/field.rs | 63 +++++++++++++++++++ androscalpel/src/lib.rs | 2 + androscalpel_serializer/src/file_reader.rs | 21 +++++++ 5 files changed, 145 insertions(+), 32 deletions(-) create mode 100644 androscalpel/src/field.rs diff --git a/androscalpel/src/apk.rs b/androscalpel/src/apk.rs index 89cec20..4f0cb6e 100644 --- a/androscalpel/src/apk.rs +++ b/androscalpel/src/apk.rs @@ -2,7 +2,7 @@ use log::info; use pyo3::prelude::*; -use crate::{Class, DexString, Error, Result}; +use crate::{Class, DexString, Field, Result}; use androscalpel_serializer::*; /// Represent an apk. @@ -18,38 +18,27 @@ impl Apk { pub fn add_dex_file(&mut self, data: &[u8]) -> Result<()> { let dex = DexFileReader::new(data)?; for class in dex.get_class_defs() { - self.add_class_from_dex_file(class, &dex)?; + self.classes + .push(self.get_class_from_dex_file(class, &dex)?); } Ok(()) } - /// Add a class from a dex file reader. - fn add_class_from_dex_file( - &mut self, + /// Extract a class from a dex file reader. + fn get_class_from_dex_file( + &self, class_item: &ClassDefItem, dex: &DexFileReader, - ) -> Result<()> { + ) -> Result { let name_idx = dex - .get_type_ids() - .get(class_item.class_idx as usize) - .ok_or(Error::InconsistantStruct(format!( - "type idx {} out of bound of type_ids (|type_ids| = {})", - class_item.class_idx, - dex.get_type_ids().len() - )))? + .get_type_id(class_item.class_idx as usize)? .descriptor_idx; let name: DexString = dex.get_string(name_idx)?.into(); let superclass = if class_item.class_idx == NO_INDEX.0 { None } else { let superclass_idx = dex - .get_type_ids() - .get(class_item.class_idx as usize) - .ok_or(Error::InconsistantStruct(format!( - "type idx {} out of bound of type_ids (|type_ids| = {})", - class_item.class_idx, - dex.get_type_ids().len() - )))? + .get_type_id(class_item.class_idx as usize)? .descriptor_idx; Some(dex.get_string(superclass_idx)?.into()) }; @@ -59,13 +48,7 @@ impl Apk { let type_list = dex.get_struct_at_offset::(class_item.interfaces_off)?; let mut list = vec![]; for ty in type_list.list { - let ty = dex.get_type_ids().get(ty.type_idx as usize).ok_or( - Error::InconsistantStruct(format!( - "type idx {} out of bound of type_ids (|type_ids| = {})", - ty.type_idx, - dex.get_type_ids().len() - )), - )?; + let ty = dex.get_type_id(ty.type_idx as usize)?; let ty = dex.get_string(ty.descriptor_idx)?.into(); list.push(ty); } @@ -99,7 +82,15 @@ impl Apk { class_item.access_flags ); } - self.classes.push(Class { + let mut static_fields = vec![]; + let mut instance_fields = vec![]; + let data_off = class_item.class_data_off; + if data_off != 0 { + let data = dex.get_struct_at_offset::(data_off)?; + static_fields = self.get_field_list_from_dex(&data.static_fields, dex)?; + instance_fields = self.get_field_list_from_dex(&data.instance_fields, dex)?; + } + Ok(Class { name, superclass, interfaces, @@ -111,8 +102,29 @@ impl Apk { is_synthetic, is_annotation, is_enum, - }); - Ok(()) + instance_fields, + static_fields, + }) + } + + fn get_field_list_from_dex( + &self, + encoded_fields: &[EncodedField], + dex: &DexFileReader, + ) -> Result> { + let mut idx = 0; + let mut fields = vec![]; + for field in encoded_fields { + idx += field.field_idx_diff.0; + // TODO: flags + let id_item = dex.get_field_id(idx as usize)?; + // Check class_idx == class? ¯\_(ツ)/¯ + let ty = dex.get_type_id(id_item.type_idx as usize)?; + let ty = dex.get_string(ty.descriptor_idx)?.into(); + let name = dex.get_string(id_item.name_idx)?.into(); + fields.push(Field { name, type_: ty }) + } + Ok(fields) } } diff --git a/androscalpel/src/class.rs b/androscalpel/src/class.rs index ceccf57..177d21f 100644 --- a/androscalpel/src/class.rs +++ b/androscalpel/src/class.rs @@ -2,7 +2,7 @@ use pyo3::prelude::*; -use crate::DexString; +use crate::{DexString, Field}; /// Represent an apk #[pyclass] @@ -45,6 +45,18 @@ pub struct Class { /// Name of the source file where this class is defined. #[pyo3(get, set)] pub source_file: Option, + + /// The static fields + #[pyo3(get, set)] + pub static_fields: Vec, + /// The instance fields + #[pyo3(get, set)] + pub instance_fields: Vec, + // /// The static methods + // #[pyo3(get, set)] + // pub methods: Vec<()>, + // Dont think we need to distinguish direct (static + private) and virtual (all the other) methods + // // pub annotations: Option<()> // TODO // pub data: Option<()> // TODO // pub static_values: Option<()> // TODO @@ -67,6 +79,8 @@ impl Class { is_synthetic: false, is_annotation: false, is_enum: false, + static_fields: vec![], + instance_fields: vec![], } } @@ -99,6 +113,7 @@ impl Class { } pub fn __repr__(&self) -> String { - (&self.name).into() + let name: String = (&self.name).into(); + format!("Class({name})") } } diff --git a/androscalpel/src/field.rs b/androscalpel/src/field.rs new file mode 100644 index 0000000..5b67a69 --- /dev/null +++ b/androscalpel/src/field.rs @@ -0,0 +1,63 @@ +//! Representation of the fields of a class. + +use pyo3::prelude::*; + +use crate::DexString; + +/// Represent a field. +#[pyclass] +#[derive(Debug, Clone)] +pub struct Field { + /// The name of the field, format described at + /// + #[pyo3(get, set)] + pub name: DexString, + /// The type of the field, format described at <> + #[pyo3(get, set)] + pub type_: DexString, +} + +#[pymethods] +impl Field { + #[new] + pub fn new(name: DexString, type_: DexString) -> Self { + Self { name, type_ } + } + + pub fn __str__(&self) -> String { + /* + let name: String = (&self.name).into(); + let file = if let Some(file) = &self.source_file { + let file: String = file.into(); + format!(" defined in {file}\n") + } else { + "".into() + }; + let superclass = if let Some(spcl) = &self.superclass { + let spcl: String = spcl.into(); + format!(" extends: {spcl}\n") + } else { + "".into() + }; + let interfaces = if self.interfaces.is_empty() { + "".into() + } else { + let mut interfaces: String = " implements:\n".into(); + for it in &self.interfaces { + let it: String = it.into(); + interfaces += &format!(" {it}\n"); + } + interfaces + }; + + format!("{name}\n{file}{superclass}{interfaces}") + */ + self.__repr__() + } + + pub fn __repr__(&self) -> String { + let name: String = (&self.name).into(); + let ty: String = (&self.type_).into(); + format!("Field({name}, {ty})") + } +} diff --git a/androscalpel/src/lib.rs b/androscalpel/src/lib.rs index b20ddb6..4e5e975 100644 --- a/androscalpel/src/lib.rs +++ b/androscalpel/src/lib.rs @@ -4,9 +4,11 @@ use pyo3::prelude::*; pub mod apk; pub mod class; +pub mod field; pub use apk::*; pub use class::*; +pub use field::*; #[derive(Debug)] pub enum Error { diff --git a/androscalpel_serializer/src/file_reader.rs b/androscalpel_serializer/src/file_reader.rs index ccff92b..d16e0db 100644 --- a/androscalpel_serializer/src/file_reader.rs +++ b/androscalpel_serializer/src/file_reader.rs @@ -143,6 +143,27 @@ impl<'a> DexFileReader<'a> { }) } + /// Return a [`&TypeIdItem`] from its idx. + pub fn get_type_id(&self, idx: usize) -> Result<&TypeIdItem> { + self.type_ids + .get(idx) + .ok_or(Error::InconsistantStruct(format!( + "type idx {} out of bound of type_ids (|type_ids| = {})", + idx, + self.type_ids.len() + ))) + } + + /// Return a [`&FieldIdItem`] from its idx. + pub fn get_field_id(&self, idx: usize) -> Result<&FieldIdItem> { + self.field_ids + .get(idx) + .ok_or(Error::InconsistantStruct(format!( + "field idx {idx} is out of bound (|field_ids|={})", + self.field_ids.len() + ))) + } + fn sanity_check(&self) -> Result<()> { if self.header.magic.version != [0x30, 0x33, 0x39] { warn!(