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::collections::HashSet;
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
|
|
||||||
use anyhow::anyhow;
|
use anyhow::{anyhow, bail, Context};
|
||||||
use pyo3::prelude::*;
|
use pyo3::prelude::*;
|
||||||
|
|
||||||
use crate::{scalar::*, DexString, DexValue, Result};
|
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 {
|
pub fn __str__(&self) -> String {
|
||||||
format!(
|
format!(
|
||||||
"({}){}",
|
"({}){}",
|
||||||
|
|
@ -180,6 +227,76 @@ impl IdType {
|
||||||
Self(ty)
|
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)
|
/// Return the void type (for return type)
|
||||||
#[staticmethod]
|
#[staticmethod]
|
||||||
pub fn void() -> Self {
|
pub fn void() -> Self {
|
||||||
|
|
@ -236,7 +353,7 @@ impl IdType {
|
||||||
/// Return the type for the class of fully qualified name `name`
|
/// Return the type for the class of fully qualified name `name`
|
||||||
#[staticmethod]
|
#[staticmethod]
|
||||||
pub fn class(name: &str) -> Self {
|
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_`
|
/// 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 {
|
pub fn __str__(&self) -> String {
|
||||||
let class: String = self.class_.get_name().into();
|
let class: String = self.class_.get_name().into();
|
||||||
let name: String = (&self.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 {
|
pub fn __str__(&self) -> String {
|
||||||
format!(
|
format!(
|
||||||
"{}->{}({}){}",
|
"{}->{}({}){}",
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ use super::*;
|
||||||
use androscalpel_serializer::*;
|
use androscalpel_serializer::*;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io;
|
use std::io;
|
||||||
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
fn get_dex(filename: &str) -> Vec<u8> {
|
fn get_dex(filename: &str) -> Vec<u8> {
|
||||||
let hello_world_dex = format!("{}/src/tests/{}", env!("CARGO_MANIFEST_DIR"), filename);
|
let hello_world_dex = format!("{}/src/tests/{}", env!("CARGO_MANIFEST_DIR"), filename);
|
||||||
|
|
@ -11,19 +12,33 @@ fn get_dex(filename: &str) -> Vec<u8> {
|
||||||
data
|
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]
|
#[test]
|
||||||
fn test_generated_data_size() {
|
fn test_generated_data_size() {
|
||||||
let mut apk = Apk::new();
|
let dex_bin = get_hello_world_recompilled();
|
||||||
let dex_data = get_dex("classes_hello_world.dex");
|
let dex = DexFileReader::new(dex_bin).unwrap();
|
||||||
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();
|
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
dex.get_header().data_off + dex.get_header().data_size,
|
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?
|
// TODO: check for all pool concerned if the pool span outside the data section?
|
||||||
//for item in dex.get_map_list().list() {}
|
//for item in dex.get_map_list().list() {}
|
||||||
|
|
@ -31,13 +46,98 @@ fn test_generated_data_size() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_generated_apk_equivalence() {
|
fn test_generated_apk_equivalence() {
|
||||||
let mut apk = Apk::new();
|
let new_dex = get_hello_world_recompilled();
|
||||||
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 mut new_apk = Apk::new();
|
let mut new_apk = Apk::new();
|
||||||
new_apk.add_dex_file(&new_dex).unwrap();
|
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