add parsing of smali id

This commit is contained in:
Jean-Marie 'Histausse' Mineau 2024-02-14 17:59:10 +01:00
parent 112ddc615b
commit 6d77df2b79
Signed by: histausse
GPG key ID: B66AEEDA9B645AD2
2 changed files with 325 additions and 17 deletions

View file

@ -6,7 +6,7 @@ use std::collections::hash_map::DefaultHasher;
use std::collections::HashSet;
use std::hash::{Hash, Hasher};
use anyhow::anyhow;
use anyhow::{anyhow, bail, Context};
use pyo3::prelude::*;
use crate::{scalar::*, DexString, DexValue, Result};
@ -59,6 +59,53 @@ impl IdMethodType {
}
}
/// Try to parse a smali representation of a prototype into a IdMethodType.
///
/// ```
/// use androscalpel::IdMethodType;
///
/// let proto = IdMethodType::from_smali("(Landroidx/core/util/Predicate;Landroidx/core/util/Predicate;Ljava/lang/Object;)Z").unwrap();
/// assert_eq!(
/// proto,
/// IdMethodType(
/// IdType::boolean(),
/// vec![
/// IdType::class("androidx/core/util/Predicate"),
/// IdType::class("androidx/core/util/Predicate"),
/// IdType::class("java/lang/Object")
/// ]
/// )
/// );
/// ```
#[staticmethod]
pub fn from_smali(smali_repr: &str) -> Result<Self> {
if smali_repr.len() < 2 {
bail!("'{smali_repr}' is to short to be a prototype");
}
if &smali_repr[0..1] != "(" {
bail!("'{smali_repr}' does not begin by a '('");
}
let closing_par_i = smali_repr.find(')');
if closing_par_i.is_none() {
bail!("'{smali_repr}' does not contians a ')'");
}
let closing_par_i = closing_par_i.unwrap();
let parameters =
IdType::get_list_from_str(&smali_repr[1..closing_par_i]).with_context(|| {
format!("Failed to parse smali repr of parameters of prototype {smali_repr}")
})?;
let return_type =
IdType::new((&smali_repr[closing_par_i + 1..]).into()).with_context(|| {
format!("Failed to parse smali repr of return type of {smali_repr}")
})?;
let shorty = Self::compute_shorty(&return_type, &parameters);
Ok(Self {
shorty,
return_type,
parameters,
})
}
pub fn __str__(&self) -> String {
format!(
"({}){}",
@ -180,6 +227,76 @@ impl IdType {
Self(ty)
}
/// Return a list of types from a string of concatenated types (like the ones between
/// parentheses in the repr of a prototype)
///
/// # Example
///
/// ```
/// use androscalpel::IdType;
///
/// let types = IdType::get_list_from_str("IILjava/lang/Object;Ljava/lang/Object;Z").unwrap();
/// assert_eq!(types, vec![
/// IdType::int(),
/// IdType::int(),
/// IdType::class("java/lang/Object"),
/// IdType::class("java/lang/Object"),
/// IdType::boolean(),
/// ])
/// ```
#[staticmethod]
pub fn get_list_from_str(type_list: &str) -> Result<Vec<Self>> {
let mut lst = vec![];
let mut chars = type_list.chars();
let mut array_dimmention = 0;
while let Some(c) = chars.next() {
let new_type = match c {
'V' => Some(Self::void()),
'Z' => Some(Self::boolean()),
'B' => Some(Self::byte()),
'S' => Some(Self::short()),
'C' => Some(Self::char()),
'I' => Some(Self::int()),
'J' => Some(Self::long()),
'F' => Some(Self::float()),
'D' => Some(Self::double()),
'[' => { array_dimmention += 1; None },
'L' => {
let mut class_name = String::new();
for cc in chars.by_ref(){
if cc == ';' { break;}
else {
class_name.push(cc);
}
}
Some(Self::class(&class_name))
}
_ => bail!(
"Could not parse {type_list} as a list of types, found invalid char {c} outside a class name"
),
};
if let Some(mut new_type) = new_type {
if array_dimmention != 0 {
let mut data = vec![0u8; array_dimmention + new_type.0 .0.data.len()];
for c in &mut data[..array_dimmention] {
*c = 0x5b;
}
data[array_dimmention..].copy_from_slice(&new_type.0 .0.data);
new_type.0 .0.data = data;
new_type.0 .0.utf16_size.0 += array_dimmention as u32;
}
lst.push(new_type);
array_dimmention = 0;
}
}
if array_dimmention != 0 {
bail!("Could not parse {type_list}: finishing with [")
}
Ok(lst)
}
/// Return the void type (for return type)
#[staticmethod]
pub fn void() -> Self {
@ -236,7 +353,7 @@ impl IdType {
/// Return the type for the class of fully qualified name `name`
#[staticmethod]
pub fn class(name: &str) -> Self {
Self(format!("L{name}").into())
Self(format!("L{name};").into())
}
/// Return the type for an array of the specify `type_`
@ -491,6 +608,46 @@ impl IdField {
}
}
/// Try to parse a smali representation of a field into a IdField.
///
/// ```
/// use androscalpel::IdField;
///
/// let proto = IdField::from_smali("Ljava/lang/annotation/ElementType;->METHOD:Ljava/lang/annotation/ElementType;").unwrap();
/// assert_eq!(
/// proto,
/// IdField::new(
/// "METHOD".into(),
/// IdType::class("java/lang/annotation/ElementType"),
/// IdType::class("java/lang/annotation/ElementType"),
/// )
/// );
/// ```
#[staticmethod]
pub fn from_smali(smali_repr: &str) -> Result<Self> {
let arrow_i = smali_repr.find("->");
if arrow_i.is_none() {
bail!("'{smali_repr}' does not contains a '->'");
}
let arrow_i = arrow_i.unwrap();
let dots_i = smali_repr.find(':');
if dots_i.is_none() {
bail!("'{smali_repr}' does not contains a ':(");
}
let dots_i = dots_i.unwrap();
let type_ = IdType::new((&smali_repr[dots_i + 1..]).into())
.with_context(|| format!("Failed to parse smali repr of type of field {smali_repr}"))?;
let class_ = IdType::new((&smali_repr[..arrow_i]).into()).with_context(|| {
format!("Failed to parse smali repr of class type of method {smali_repr}")
})?;
let name = (&smali_repr[arrow_i + 2..dots_i]).into();
Ok(Self {
name,
type_,
class_,
})
}
pub fn __str__(&self) -> String {
let class: String = self.class_.get_name().into();
let name: String = (&self.name).into();
@ -594,6 +751,57 @@ impl IdMethod {
}
}
/// Try to parse a smali representation of method into a IdMethod.
///
/// ```
/// use androscalpel::IdMethod;
///
/// let id_method = IdMethod::from_smali(
/// "Landroidx/core/util/Predicate;->lambda$and$0(Landroidx/core/util/Predicate;Landroidx/core/util/Predicate;Ljava/lang/Object;)Z"
/// ).unwrap();
///
/// assert_eq!(
/// id_method,
/// IdMethod::new(
/// "lambda$and$0".into(),
/// IdMethodType::new(
/// IdType::boolean(),
/// vec![
/// IdType::class("androidx/core/util/Predicate"),
/// IdType::class("androidx/core/util/Predicate"),
/// IdType::class("java/lang/Object")
/// ]
/// ),
/// IdType::class("androidx/core/util/Predicate")
/// )
/// );
/// ```
#[staticmethod]
pub fn from_smali(smali_repr: &str) -> Result<Self> {
let arrow_i = smali_repr.find("->");
if arrow_i.is_none() {
bail!("'{smali_repr}' does not contains a '->'");
}
let arrow_i = arrow_i.unwrap();
let openning_par_i = smali_repr.find('(');
if openning_par_i.is_none() {
bail!("'{smali_repr}' does not contains a '('");
}
let openning_par_i = openning_par_i.unwrap();
let proto = IdMethodType::from_smali(&smali_repr[openning_par_i..]).with_context(|| {
format!("Failed to parse smali repr of prototype of method {smali_repr}")
})?;
let class_ = IdType::new((&smali_repr[..arrow_i]).into()).with_context(|| {
format!("Failed to parse smali repr of class type of method {smali_repr}")
})?;
let name = (&smali_repr[arrow_i + 2..openning_par_i]).into();
Ok(Self {
name,
proto,
class_,
})
}
pub fn __str__(&self) -> String {
format!(
"{}->{}({}){}",

View file

@ -2,6 +2,7 @@ use super::*;
use androscalpel_serializer::*;
use std::fs::File;
use std::io;
use std::sync::OnceLock;
fn get_dex(filename: &str) -> Vec<u8> {
let hello_world_dex = format!("{}/src/tests/{}", env!("CARGO_MANIFEST_DIR"), filename);
@ -11,19 +12,33 @@ fn get_dex(filename: &str) -> Vec<u8> {
data
}
fn get_hello_world_dex() -> &'static [u8] {
static HELLO_WORLD_DEX: OnceLock<Vec<u8>> = OnceLock::new();
HELLO_WORLD_DEX.get_or_init(|| get_dex("classes_hello_world.dex"))
}
fn get_hello_world_apk() -> &'static Apk {
static HELLO_WORLD_APK: OnceLock<Apk> = OnceLock::new();
HELLO_WORLD_APK.get_or_init(|| {
let mut apk = Apk::new();
apk.add_dex_file(get_hello_world_dex()).unwrap();
apk
})
}
fn get_hello_world_recompilled() -> &'static [u8] {
static HELLO_WORLD_RECOMP: OnceLock<Vec<u8>> = OnceLock::new();
HELLO_WORLD_RECOMP.get_or_init(|| get_hello_world_apk().gen_raw_dex().unwrap().pop().unwrap())
}
#[test]
fn test_generated_data_size() {
let mut apk = Apk::new();
let dex_data = get_dex("classes_hello_world.dex");
apk.add_dex_file(&dex_data).unwrap();
let new_dex = apk.gen_raw_dex().unwrap();
assert_eq!(new_dex.len(), 1);
let new_dex = new_dex.first().unwrap();
let dex = DexFileReader::new(&new_dex).unwrap();
let dex_bin = get_hello_world_recompilled();
let dex = DexFileReader::new(dex_bin).unwrap();
assert_eq!(
dex.get_header().data_off + dex.get_header().data_size,
new_dex.len() as u32
dex_bin.len() as u32
)
// TODO: check for all pool concerned if the pool span outside the data section?
//for item in dex.get_map_list().list() {}
@ -31,13 +46,98 @@ fn test_generated_data_size() {
#[test]
fn test_generated_apk_equivalence() {
let mut apk = Apk::new();
let dex_data = get_dex("classes_hello_world.dex");
apk.add_dex_file(&dex_data).unwrap();
let new_dex = apk.gen_raw_dex().unwrap();
assert_eq!(new_dex.len(), 1);
let new_dex = new_dex.first().unwrap();
let new_dex = get_hello_world_recompilled();
let mut new_apk = Apk::new();
new_apk.add_dex_file(&new_dex).unwrap();
assert_eq!(apk, new_apk);
assert_eq!(get_hello_world_apk(), &new_apk);
}
#[test]
fn test_string_order() {
use std::collections::HashSet;
let dex = DexFileReader::new(get_hello_world_dex()).unwrap();
let new_dex = DexFileReader::new(get_hello_world_recompilled()).unwrap();
let mut string_set = HashSet::new();
for i in 0..new_dex.get_string_ids().len() {
string_set.insert(DexString(new_dex.get_string(i as u32).unwrap()));
}
for i in 0..dex.get_string_ids().len() {
let string = DexString(dex.get_string(i as u32).unwrap());
if !string_set.contains(&string) {
println!("{}", string.__str__());
}
}
assert_eq!(dex.get_string_ids().len(), new_dex.get_string_ids().len());
}
#[test]
fn test_parse_type_list() {
let types = IdType::get_list_from_str("IILjavalangObject;LjavalangObject;Z").unwrap();
assert_eq!(
types,
vec![
IdType::int(),
IdType::int(),
IdType::class("javalangObject"),
IdType::class("javalangObject"),
IdType::boolean(),
]
)
}
#[test]
fn test_parse_proto_smali() {
let proto = IdMethodType::from_smali(
"(LandroidxcoreutilPredicate;LandroidxcoreutilPredicate;LjavalangObject;)Z",
)
.unwrap();
assert_eq!(
proto,
IdMethodType::new(
IdType::boolean(),
vec![
IdType::class("androidxcoreutilPredicate"),
IdType::class("androidxcoreutilPredicate"),
IdType::class("javalangObject")
]
)
);
}
#[test]
fn test_parse_method_smali() {
let id_method = IdMethod::from_smali(
"Landroidx/core/util/Predicate;->lambda$and$0(Landroidx/core/util/Predicate;Landroidx/core/util/Predicate;Ljava/lang/Object;)Z"
).unwrap();
assert_eq!(
id_method,
IdMethod::new(
"lambda$and$0".into(),
IdMethodType::new(
IdType::boolean(),
vec![
IdType::class("androidx/core/util/Predicate"),
IdType::class("androidx/core/util/Predicate"),
IdType::class("java/lang/Object")
]
),
IdType::class("androidx/core/util/Predicate")
)
);
}
#[test]
fn test_parse_field_smali() {
let proto = IdField::from_smali(
"Ljava/lang/annotation/ElementType;->METHOD:Ljava/lang/annotation/ElementType;",
)
.unwrap();
assert_eq!(
proto,
IdField::new(
"METHOD".into(),
IdType::class("java/lang/annotation/ElementType"),
IdType::class("java/lang/annotation/ElementType"),
)
);
}