add parsing of smali id
This commit is contained in:
parent
112ddc615b
commit
6d77df2b79
2 changed files with 325 additions and 17 deletions
|
|
@ -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, ¶meters);
|
||||
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!(
|
||||
"{}->{}({}){}",
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue