Compare commits

..

1 commit

63 changed files with 32217 additions and 669353 deletions

1129
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -4,6 +4,5 @@ members = [
"apk_frauder",
"androscalpel_serializer",
"androscalpel_serializer_derive",
"androscalpel", "androscalpel_platform_api_list",
"androscalpel",
]
resolver = "2"

View file

@ -1,17 +0,0 @@
# Androscalpel
Androscalpel is a rust crate to manipulate Android application bytecode (dalvik).
This developed between 2022 and 2025 by Jean-Marie Mineau for their PhD thesis, and release under the GPLv3 licence with the permission of CentraleSupelec.
# Documentation
The documentation can be generated with `cargo doc`.
It will be generated at `target/doc/androscalpel/index.html`
Right now, the recompilation process is a little complexe, an example can be found [here](./androscalpel/examples/count_ins_and_genapk.rs).
# Note
The Dalvik v41 format (concatenated DEX files) is not currently supported.
Support is planned in the future.

View file

@ -1,12 +1,8 @@
- sanity checks
- SANITY CHECK (https://cs.android.com/android/platform/superproject/main/+/main:art/libdexfile/dex/dex_file_verifier.cc if check failed, .dex is not loaded but apk do not crash !!!)
- V41 https://cs.android.com/android/platform/superproject/main/+/main:art/libdexfile/dex/dex_file_verifier.cc;drc=e8da7cd1d0e7d3535c82f8d05adcef3edd43cd40;l=634
- tests
- https://source.android.com/docs/core/runtime/dex-format#system-annotation
- goto size computation
- Check label duplication (maybe allow identical labels at the same address only?)
- no nop when no payload
- option to get label at every code addresses
- name register / parameters
- fix flake
- fix python binding or remove
- clean repo
- ord in python

View file

@ -1,48 +1,22 @@
[package]
name = "androscalpel"
version = "0.1.0"
edition = "2024"
license = "GPL-3.0-or-later"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
name = "androscalpel"
crate-type = ["cdylib", "lib"]
crate-type = ["cdylib"]
[dependencies]
adler = "1.0.2"
androscalpel_serializer = { version = "0.1.0", path = "../androscalpel_serializer" }
androscalpel_platform_api_list = { version = "0.1.0", path = "../androscalpel_platform_api_list", optional = true }
anyhow = { version = "1.0.75", features = ["backtrace"] }
apk_frauder = { version = "0.1.0", path = "../apk_frauder" }
log = "0.4.20"
pyo3 = { version = "0.23.4", features = ["anyhow", "abi3-py38", "extension-module"], optional = true}
pyo3-log = { version = "0.12.1", optional = true}
pyo3 = { version = "0.20.0", features = ["anyhow"] }
pyo3-log = "0.8.3"
rayon = "1.9.0"
serde = { version = "1.0.195", features = ["derive"] }
serde_json = "1.0.111"
sha1 = "0.10.6"
zip = {version = "2.2.2", optional = true}
[dev-dependencies]
pretty_assertions = "1.4.1"
# For examples
clap = { version = "4.5.27", features = ["derive"] }
env_logger = "0.11.6"
[features]
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
python = ["pyo3", "pyo3-log"] # Currently not supported
external-zip-reader = ["zip"]
platform-list = ["androscalpel_platform_api_list"]
code-analysis = []
[[example]]
name = "list-method"
[[example]]
name = "count_ins_and_genapk"
[[example]]
name = "dump_cfg"

View file

@ -1,56 +0,0 @@
use androscalpel::{Apk, Instruction, Result, VisitableMut, VisitorMut};
use apk_frauder::replace_dex_unsigned;
use std::collections::HashMap;
use std::env;
use std::io::Cursor;
#[derive(Default)]
struct InsCounter {
pub n: u64,
}
impl VisitorMut for InsCounter {
fn visit_instruction(&mut self, ins: Instruction) -> Result<Instruction> {
if !ins.is_pseudo_ins() {
self.n += 1;
}
ins.default_visit_mut(self)
}
}
fn main() {
let args: Vec<_> = env::args().collect();
assert!(args.len() > 2, "usage: {} input.apk output.apk", args[0]);
let mut cnt = InsCounter::default();
let apk = cnt
.visit_apk(Apk::load_apk_path((&args[1]).into(), false, false).unwrap())
.unwrap();
println!("Nb INS: {}", cnt.n);
// This should be streamlined
let mut dex_files = vec![];
let mut files = apk.gen_raw_dex().unwrap();
let mut i = 0;
loop {
let name = if i == 0 {
"classes.dex".into()
} else {
format!("classes{}.dex", i + 1)
};
if let Some(file) = files.remove(&name) {
dex_files.push(Cursor::new(file))
} else {
break;
}
i += 1;
}
replace_dex_unsigned(
&args[1],
&args[2],
&mut dex_files,
None::<HashMap<_, Option<Cursor<&[u8]>>>>,
)
.unwrap();
}

View file

@ -1,31 +0,0 @@
use std::fs::File;
use std::path::PathBuf;
use androscalpel::{Apk, IdMethod, MethodCFG};
use clap::Parser;
#[derive(Parser, Debug)]
#[command(version, about, long_about = None, arg_required_else_help = true)]
struct Cli {
#[arg(short, long)]
apk: PathBuf,
#[arg(short, long)]
method: String,
}
fn main() {
env_logger::init();
let cli = Cli::parse();
let mut apk = Apk::load_apk(File::open(&cli.apk).unwrap(), |_, _, _| None, false).unwrap();
let mid = IdMethod::from_smali(&cli.method).unwrap();
let class = apk.get_class_mut(&mid.class_).unwrap();
let method = if let Some(method) = class.virtual_methods.get(&mid) {
method
} else {
class.direct_methods.get(&mid).unwrap()
};
let cfg = MethodCFG::new(method).unwrap();
print!("{}", cfg.to_dot(true));
}

View file

@ -1,20 +0,0 @@
use androscalpel::{Apk, Method, Result, SmaliName, Visitable, Visitor};
use std::env;
#[derive(Default)]
struct MethodPrinter {}
impl Visitor for MethodPrinter {
fn visit_method(&mut self, method: &Method) -> Result<()> {
println!("Method: {}", method.descriptor.try_to_smali()?);
method.default_visit(self)?;
Ok(())
}
}
fn main() {
let args: Vec<_> = env::args().collect();
assert!(args.len() > 1, "Need one argument");
let apk = Apk::load_apk_path((&args[1]).into(), false, false).unwrap();
MethodPrinter::default().visit_apk(&apk).unwrap();
}

View file

@ -0,0 +1,16 @@
[build-system]
requires = ["maturin>=1.2,<2.0"]
build-backend = "maturin"
[project]
name = "androscalpel"
requires-python = ">=3.7"
classifiers = [
"Programming Language :: Rust",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
]
[tool.maturin]
features = ["pyo3/extension-module"]

View file

@ -2,38 +2,41 @@
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
#[cfg(feature = "python")]
use pyo3::prelude::*;
use crate::hashmap_vectorize;
use crate::{
DexString, IdField, IdMethod, IdMethodType, MethodHandle, Result, Visitable, VisitableMut,
Visitor, VisitorMut, dex_id::IdType, value::DexValue,
dex_id::IdType, value::DexValue, DexString, IdField, IdMethod, IdMethodType, MethodHandle,
Result,
};
/// Annotation with a visibility
#[cfg_attr(feature = "python", pyclass(eq))]
#[pyclass]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct DexAnnotationItem {
// TODO: the get/set will probably be wonky on the python side when edditing
/// The actual annotation
#[cfg_attr(feature = "python", pyo3(get))]
#[pyo3(get)]
pub annotation: DexAnnotation,
// TODO: enforce exclusivity
/// If the annotation visibility is set to build
#[cfg_attr(feature = "python", pyo3(get))]
#[pyo3(get)]
pub visibility_build: bool,
/// If the annotation visibility is set to runtime
#[cfg_attr(feature = "python", pyo3(get))]
#[pyo3(get)]
pub visibility_runtime: bool,
/// If the annotation visibility is set to system
#[cfg_attr(feature = "python", pyo3(get))]
#[pyo3(get)]
pub visibility_system: bool,
}
#[cfg_attr(feature = "python", pymethods)]
#[pymethods]
impl DexAnnotationItem {
#[cfg_attr(feature = "python", new)]
pub fn __eq__(&self, other: &Self) -> bool {
self == other
}
#[new]
pub fn new(annotation: DexAnnotation) -> Self {
Self {
annotation,
@ -102,44 +105,34 @@ impl DexAnnotationItem {
Ok(serde_json::to_string(self)?)
}
#[cfg_attr(feature = "python", staticmethod)]
#[staticmethod]
pub fn from_json(json: &str) -> Result<Self> {
Ok(serde_json::from_str(json)?)
}
}
impl<V: Visitor> Visitable<V> for DexAnnotationItem {
fn default_visit(&self, v: &mut V) -> Result<()> {
v.visit_annotation(&self.annotation)
}
}
impl<V: VisitorMut> VisitableMut<V> for DexAnnotationItem {
fn default_visit_mut(self, v: &mut V) -> Result<Self> {
Ok(Self {
annotation: v.visit_annotation(self.annotation)?,
..self
})
}
}
/// An annotation.
#[cfg_attr(feature = "python", pyclass(eq))]
#[pyclass]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct DexAnnotation {
// TODO: check the relation between type and encoded_value.
/// The type of the annotation.
#[cfg_attr(feature = "python", pyo3(get))]
#[pyo3(get)]
pub type_: IdType,
// TODO: the get/set will probably be wonky on the python side when edditing
/// The annotation elements.
#[cfg_attr(feature = "python", pyo3(get))]
#[pyo3(get)]
#[serde(with = "hashmap_vectorize")]
pub elements: HashMap<DexString, DexValue>, // TODO: check MemberName syntax?
}
#[cfg_attr(feature = "python", pymethods)]
#[pymethods]
impl DexAnnotation {
#[cfg_attr(feature = "python", new)]
pub fn __eq__(&self, other: &Self) -> bool {
self == other
}
#[new]
pub fn new(type_: IdType, elements: HashMap<DexString, DexValue>) -> Self {
Self { type_, elements }
}
@ -219,32 +212,8 @@ impl DexAnnotation {
Ok(serde_json::to_string(self)?)
}
#[cfg_attr(feature = "python", staticmethod)]
#[staticmethod]
pub fn from_json(json: &str) -> Result<Self> {
Ok(serde_json::from_str(json)?)
}
}
impl<V: Visitor> Visitable<V> for DexAnnotation {
fn default_visit(&self, v: &mut V) -> Result<()> {
v.visit_type(&self.type_)?;
for (id, val) in &self.elements {
v.visit_string(id)?;
v.visit_value(val)?;
}
Ok(())
}
}
impl<V: VisitorMut> VisitableMut<V> for DexAnnotation {
fn default_visit_mut(self, v: &mut V) -> Result<Self> {
let type_ = v.visit_type(self.type_)?;
let mut elements = HashMap::new();
for (id, val) in self.elements {
let id = v.visit_string(id)?;
let val = v.visit_value(val)?;
elements.insert(id, val);
}
Ok(Self { type_, elements })
}
}

File diff suppressed because it is too large Load diff

View file

@ -3,90 +3,94 @@
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
#[cfg(feature = "python")]
use pyo3::prelude::*;
use crate::hashmap_vectorize;
use crate::{
DexAnnotationItem, DexString, Field, IdField, IdMethod, IdMethodType, IdType, Method,
MethodHandle, Result, Visitable, VisitableMut, Visitor, VisitorMut,
MethodHandle, Result,
};
use androscalpel_serializer::consts::*;
/// Represent an apk
#[cfg_attr(feature = "python", pyclass(eq))]
#[pyclass]
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
pub struct Class {
/// Type, format described at
/// <https://source.android.com/docs/core/runtime/dex-format#typedescriptor>
#[cfg_attr(feature = "python", pyo3(get))]
#[pyo3(get)]
pub descriptor: IdType,
/// If the class is visible everywhere
#[cfg_attr(feature = "python", pyo3(get))]
#[pyo3(get)]
pub is_public: bool,
/// If the class is subclassable
#[cfg_attr(feature = "python", pyo3(get))]
#[pyo3(get)]
pub is_final: bool,
/// If the class is a 'multipy-implementable abstract class' AKA an interface
#[cfg_attr(feature = "python", pyo3(get))]
#[pyo3(get)]
pub is_interface: bool,
/// If the class is instanciable
#[cfg_attr(feature = "python", pyo3(get))]
#[pyo3(get)]
pub is_abstract: bool,
/// If the class is not directly defined in the source code
#[cfg_attr(feature = "python", pyo3(get))]
#[pyo3(get)]
pub is_synthetic: bool,
/// If the class is an annotation
#[cfg_attr(feature = "python", pyo3(get))]
#[pyo3(get)]
pub is_annotation: bool,
/// If the class is an enum
#[cfg_attr(feature = "python", pyo3(get))]
#[pyo3(get)]
pub is_enum: bool,
/// Name of the superclass, format described at
/// <https://source.android.com/docs/core/runtime/dex-format#typedescriptor>
#[cfg_attr(feature = "python", pyo3(get))]
#[pyo3(get)]
pub superclass: Option<IdType>,
/// List of the interfaces that class implement, format of the interfaces
/// name is discribed at
/// <https://source.android.com/docs/core/runtime/dex-format#typedescriptor>
#[cfg_attr(feature = "python", pyo3(get))]
#[pyo3(get)]
pub interfaces: Vec<IdType>,
/// Name of the source file where this class is defined.
#[cfg_attr(feature = "python", pyo3(get))]
#[pyo3(get)]
pub source_file: Option<DexString>,
/// The static fields
#[cfg_attr(feature = "python", pyo3(get))]
#[pyo3(get)]
#[serde(with = "hashmap_vectorize")]
pub static_fields: HashMap<IdField, Field>,
/// The instance fields
#[cfg_attr(feature = "python", pyo3(get))]
#[pyo3(get)]
#[serde(with = "hashmap_vectorize")]
pub instance_fields: HashMap<IdField, Field>,
/// The direct (static, private or constructor) methods of the class
#[cfg_attr(feature = "python", pyo3(get))]
#[pyo3(get)]
#[serde(with = "hashmap_vectorize")]
pub direct_methods: HashMap<IdMethod, Method>,
/// The virtual (ie non direct) methods of the class
#[cfg_attr(feature = "python", pyo3(get))]
#[pyo3(get)]
#[serde(with = "hashmap_vectorize")]
pub virtual_methods: HashMap<IdMethod, Method>,
// Do we need to distinguish direct and virtual (all the other) methods?
// Maybe overlapping descriptor (same name, class and proto?)
/// The annotation related to this class (note: this does not include the
/// methods/field/parameters annotations, they are stored in the methods and fields
/// structutres)
#[cfg_attr(feature = "python", pyo3(get))]
#[pyo3(get)]
pub annotations: Vec<DexAnnotationItem>,
}
#[cfg_attr(feature = "python", pymethods)]
#[pymethods]
impl Class {
pub fn to_json(&self) -> Result<String> {
Ok(serde_json::to_string(self)?)
}
#[cfg_attr(feature = "python", staticmethod)]
#[staticmethod]
pub fn from_json(json: &str) -> Result<Self> {
Ok(serde_json::from_str(json)?)
}
#[cfg_attr(feature = "python", new)]
#[new]
pub fn new(name: DexString) -> Result<Self> {
Ok(Self {
descriptor: IdType::new(name)?,
@ -109,15 +113,15 @@ impl Class {
}
pub fn __str__(&self) -> String {
let name: String = self.descriptor.get_name().__str__();
let name: String = (&self.descriptor.get_name()).into();
let file = if let Some(file) = &self.source_file {
let file: String = file.__str__();
let file: String = file.into();
format!(" defined in {file}\n")
} else {
"".into()
};
let superclass = if let Some(spcl) = &self.superclass {
let spcl: String = spcl.get_name().__str__();
let spcl: String = spcl.get_name().into();
format!(" extends: {spcl}\n")
} else {
"".into()
@ -127,7 +131,7 @@ impl Class {
} else {
let mut interfaces: String = " implements:\n".into();
for it in &self.interfaces {
let it: String = it.get_name().__str__();
let it: String = it.get_name().into();
interfaces += &format!(" {it}\n");
}
interfaces
@ -137,7 +141,7 @@ impl Class {
}
pub fn __repr__(&self) -> String {
let name: String = self.descriptor.get_name().__str__();
let name: String = (&self.descriptor.get_name()).into();
format!("Class({name})")
}
@ -317,7 +321,7 @@ impl Class {
.any(|field| field.value.is_some())
}
/// Check if the class or its fields/methods have annotations
/// If the class or its fields/methods have annotations
pub fn has_annotations(&self) -> bool {
!self.annotations.is_empty()
|| self
@ -331,30 +335,11 @@ impl Class {
|| self
.direct_methods
.values()
.any(|method| method.has_annotations())
.any(|field| field.has_annotations())
|| self
.virtual_methods
.values()
.any(|method| method.has_annotations())
}
/// Check if the class have a field of method with hiddenapi information
pub fn has_hiddenapi(&self) -> bool {
self.static_fields
.values()
.any(|field| field.hiddenapi.is_some())
|| self
.instance_fields
.values()
.any(|field| field.hiddenapi.is_some())
|| self
.direct_methods
.values()
.any(|method| method.hiddenapi.is_some())
|| self
.virtual_methods
.values()
.any(|method| method.hiddenapi.is_some())
.any(|field| field.has_annotations())
}
/// Return the binary representation of access flags.
@ -384,98 +369,7 @@ impl Class {
flags
}
/// Check if the class is a platform class (ie in the android SDK or a hidden API).
#[cfg(feature = "platform-list")]
pub fn is_platform_class(&self) -> bool {
self.descriptor.is_platform_class()
}
}
impl<V: Visitor> Visitable<V> for Class {
fn default_visit(&self, v: &mut V) -> Result<()> {
v.visit_type(&self.descriptor)?;
if let Some(superclass) = &self.superclass {
v.visit_type(superclass)?;
}
for interface in &self.interfaces {
v.visit_type(interface)?;
}
if let Some(source_file) = &self.source_file {
v.visit_string(source_file)?;
}
for (id, field) in &self.static_fields {
v.visit_field_id(id)?;
v.visit_field(field)?;
}
for (id, field) in &self.instance_fields {
v.visit_field_id(id)?;
v.visit_field(field)?;
}
for (id, meth) in &self.direct_methods {
v.visit_method_id(id)?;
v.visit_method(meth)?;
}
for (id, meth) in &self.virtual_methods {
v.visit_method_id(id)?;
v.visit_method(meth)?;
}
for annot in &self.annotations {
v.visit_annotation_item(annot)?;
}
Ok(())
}
}
impl<V: VisitorMut> VisitableMut<V> for Class {
fn default_visit_mut(self, v: &mut V) -> Result<Self> {
Ok(Self {
descriptor: v.visit_type(self.descriptor)?,
superclass: self
.superclass
.map(|superclass| v.visit_type(superclass))
.transpose()?,
interfaces: self
.interfaces
.into_iter()
.map(|interface| v.visit_type(interface))
.collect::<Result<_>>()?,
source_file: self
.source_file
.map(|source_file| v.visit_string(source_file))
.transpose()?,
static_fields: {
let mut static_fields = HashMap::new();
for (id, field) in self.static_fields.into_iter() {
static_fields.insert(v.visit_field_id(id)?, v.visit_field(field)?);
}
static_fields
},
instance_fields: {
let mut instance_fields = HashMap::new();
for (id, field) in self.instance_fields.into_iter() {
instance_fields.insert(v.visit_field_id(id)?, v.visit_field(field)?);
}
instance_fields
},
direct_methods: {
let mut direct_methods = HashMap::new();
for (id, meth) in self.direct_methods.into_iter() {
direct_methods.insert(v.visit_method_id(id)?, v.visit_method(meth)?);
}
direct_methods
},
virtual_methods: {
let mut virtual_methods = HashMap::new();
for (id, meth) in self.virtual_methods.into_iter() {
virtual_methods.insert(v.visit_method_id(id)?, v.visit_method(meth)?);
}
virtual_methods
},
annotations: self
.annotations
.into_iter()
.map(|annotation| v.visit_annotation_item(annotation))
.collect::<Result<_>>()?,
..self
})
pub fn __eq__(&self, other: &Self) -> bool {
self == other
}
}

View file

@ -1,16 +1,13 @@
//! Representation of a method.
use anyhow::anyhow;
use log::debug;
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
#[cfg(feature = "python")]
use pyo3::prelude::*;
use crate::{
DexString, IdField, IdMethod, IdMethodType, IdType, Method, MethodHandle, Result, Visitable,
VisitableMut, Visitor, VisitorMut, ins::Instruction,
ins, ins::Instruction, DexString, IdField, IdMethod, IdMethodType, IdType, MethodHandle, Result,
};
// TODO: make this easy to edit/manipulate, maybe move to Method
@ -18,86 +15,76 @@ use crate::{
// type TmpHandlerType = (Vec<(IdType, u32)>, Option<u32>);
/// The code run by a method.
#[cfg_attr(feature = "python", pyclass(eq))]
#[pyclass]
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Code {
// TODO: remove and compute this value from code?
/// The number of registers used by the code
#[cfg_attr(feature = "python", pyo3(get))]
#[pyo3(get)]
pub registers_size: u16,
// TODO: what does it means? is it computable?
/// The number of words of incoming arguments to the method
#[pyo3(get)]
pub ins_size: u16,
// TODO: what does it means? is it computable?
/// The number of words of outgoing argument space
#[pyo3(get)]
pub outs_size: u16,
// TODO: implement
/// The debug info
#[pyo3(get)]
pub debug_info: (u32, Vec<u8>), // Should be stripped, copying like this just don't work
/// The names of the parameters if given
#[cfg_attr(feature = "python", pyo3(get))]
#[pyo3(get)]
pub parameter_names: Option<Vec<Option<DexString>>>,
/// The instructions.
#[cfg_attr(feature = "python", pyo3(get))]
#[pyo3(get)]
pub insns: Vec<Instruction>,
}
// TODO reimplement PartialEq: label should become address independant
impl PartialEq for Code {
fn eq(&self, other: &Self) -> bool {
let comparable_self = self.semantic_comparable().unwrap();
let comparable_other = other.semantic_comparable().unwrap();
(comparable_self.registers_size == comparable_other.registers_size)
&& (comparable_self.ins_size == comparable_other.ins_size)
&& (comparable_self.outs_size == comparable_other.outs_size)
&& (comparable_self.debug_info == comparable_other.debug_info)
&& (comparable_self.insns == comparable_other.insns)
}
}
#[cfg_attr(feature = "python", pymethods)]
// TODO reimplement PartialEq: label should become address independant
#[pymethods]
impl Code {
pub fn to_json(&self) -> Result<String> {
Ok(serde_json::to_string(self)?)
}
#[cfg_attr(feature = "python", staticmethod)]
#[staticmethod]
pub fn from_json(json: &str) -> Result<Self> {
Ok(serde_json::from_str(json)?)
}
#[cfg_attr(feature = "python", new)]
#[cfg_attr(feature = "python", pyo3(signature = (registers_size, ins_size, outs_size, insns, parameter_names=None)))]
#[new]
pub fn new(
registers_size: u16,
ins_size: u16,
outs_size: u16,
insns: Vec<Instruction>,
parameter_names: Option<Vec<Option<DexString>>>,
) -> Self {
Self {
registers_size,
ins_size,
outs_size,
insns,
parameter_names,
debug_info: (0, vec![]),
}
}
/// Compute the `ins_size` field. This is the number of register parameters, including the
/// `this` parameter for non-static methods.
/// This information is stored in the code item in dex files, but is not computable from the code
/// (as opposed to `outs_size`). The [`Method`] struct is needed to compute it.
pub fn ins_size(&self, method: &Method) -> u16 {
method.ins_size()
}
/// Compute the `outs_size` field. This is the number of registers needed to call other
/// function.
pub fn outs_size(&self) -> u16 {
let mut outs = 0;
for ins in &self.insns {
match ins {
Instruction::InvokeVirtual { args, .. }
| Instruction::InvokeSuper { args, .. }
| Instruction::InvokeDirect { args, .. }
| Instruction::InvokeStatic { args, .. }
| Instruction::InvokeInterface { args, .. }
| Instruction::InvokePolymorphic { args, .. }
| Instruction::InvokeCustom { args, .. } => {
if args.len() > outs {
outs = args.len();
}
}
_ => (),
}
}
outs as u16
}
pub fn __str__(&self) -> String {
self.__repr__()
}
@ -114,8 +101,10 @@ impl Code {
strings.extend(ins.get_all_strings());
}
if let Some(names) = &self.parameter_names {
for name in names.iter().flatten() {
strings.insert(name.clone());
for name in names {
if let Some(name) = name {
strings.insert(name.clone());
}
}
}
strings
@ -167,55 +156,59 @@ impl Code {
handles
}
pub fn __eq__(&self, other: &Self) -> bool {
self == other
}
/// Return all the labels used by instrutions in the code.
pub fn get_referenced_label(&self) -> HashSet<String> {
let mut used_labels = HashSet::new();
for ins in &self.insns {
match ins {
Instruction::Goto { label } => {
Instruction::Goto(ins::Goto { label }) => {
used_labels.insert(label.clone());
}
Instruction::IfEq { label, .. } => {
Instruction::IfEq(ins::IfEq { label, .. }) => {
used_labels.insert(label.clone());
}
Instruction::IfNe { label, .. } => {
Instruction::IfNe(ins::IfNe { label, .. }) => {
used_labels.insert(label.clone());
}
Instruction::IfLt { label, .. } => {
Instruction::IfLt(ins::IfLt { label, .. }) => {
used_labels.insert(label.clone());
}
Instruction::IfGe { label, .. } => {
Instruction::IfGe(ins::IfGe { label, .. }) => {
used_labels.insert(label.clone());
}
Instruction::IfGt { label, .. } => {
Instruction::IfGt(ins::IfGt { label, .. }) => {
used_labels.insert(label.clone());
}
Instruction::IfLe { label, .. } => {
Instruction::IfLe(ins::IfLe { label, .. }) => {
used_labels.insert(label.clone());
}
Instruction::IfEqZ { label, .. } => {
Instruction::IfEqZ(ins::IfEqZ { label, .. }) => {
used_labels.insert(label.clone());
}
Instruction::IfNeZ { label, .. } => {
Instruction::IfNeZ(ins::IfNeZ { label, .. }) => {
used_labels.insert(label.clone());
}
Instruction::IfLtZ { label, .. } => {
Instruction::IfLtZ(ins::IfLtZ { label, .. }) => {
used_labels.insert(label.clone());
}
Instruction::IfGeZ { label, .. } => {
Instruction::IfGeZ(ins::IfGeZ { label, .. }) => {
used_labels.insert(label.clone());
}
Instruction::IfGtZ { label, .. } => {
Instruction::IfGtZ(ins::IfGtZ { label, .. }) => {
used_labels.insert(label.clone());
}
Instruction::IfLeZ { label, .. } => {
Instruction::IfLeZ(ins::IfLeZ { label, .. }) => {
used_labels.insert(label.clone());
}
Instruction::Try {
Instruction::Try(ins::Try {
end_label,
handlers,
default_handler,
} => {
}) => {
used_labels.insert(end_label.clone());
for (_, label) in handlers {
used_labels.insert(label.clone());
@ -224,7 +217,7 @@ impl Code {
used_labels.insert(label.clone());
}
}
Instruction::Switch { branches, .. } => {
Instruction::Switch(ins::Switch { branches, .. }) => {
for label in branches.values() {
used_labels.insert(label.clone());
}
@ -248,8 +241,8 @@ impl Code {
for ins in &self.insns {
match ins {
Instruction::Label { name } => {
if !used_labels.contains(name) {
Instruction::Label(ins::Label { name }) => {
if used_labels.get(name).is_none() {
continue;
}
let new_label_id = if last_ins_was_a_label {
@ -267,8 +260,8 @@ impl Code {
}
for label in &used_labels {
if !new_labels.contains_key(label) {
debug!("{label} use but not in new_labels");
if new_labels.get(label).is_none() {
println!("{label} use but not in new_labels");
}
}
@ -276,191 +269,183 @@ impl Code {
last_ins_was_a_label = false;
for ins in self.insns.iter().cloned() {
match ins {
Instruction::Goto { label } => {
Instruction::Goto(mut instr) => {
last_ins_was_a_label = false;
let label = new_labels
.get(&label)
instr.label = new_labels
.get(&instr.label)
.ok_or(anyhow!(
"Internal error: {} not found in renamed label",
label
instr.label
))?
.clone();
new_insns.push(Instruction::Goto { label });
new_insns.push(Instruction::Goto(instr));
}
Instruction::IfEq { label, a, b } => {
Instruction::IfEq(mut instr) => {
last_ins_was_a_label = false;
let label = new_labels
.get(&label)
instr.label = new_labels
.get(&instr.label)
.ok_or(anyhow!(
"Internal error: {} not found in renamed label",
label
instr.label
))?
.clone();
new_insns.push(Instruction::IfEq { label, a, b });
new_insns.push(Instruction::IfEq(instr));
}
Instruction::IfNe { label, a, b } => {
Instruction::IfNe(mut instr) => {
last_ins_was_a_label = false;
let label = new_labels
.get(&label)
instr.label = new_labels
.get(&instr.label)
.ok_or(anyhow!(
"Internal error: {} not found in renamed label",
label
instr.label
))?
.clone();
new_insns.push(Instruction::IfNe { label, a, b });
new_insns.push(Instruction::IfNe(instr));
}
Instruction::IfLt { label, a, b } => {
Instruction::IfLt(mut instr) => {
last_ins_was_a_label = false;
let label = new_labels
.get(&label)
instr.label = new_labels
.get(&instr.label)
.ok_or(anyhow!(
"Internal error: {} not found in renamed label",
label
instr.label
))?
.clone();
new_insns.push(Instruction::IfLt { label, a, b });
new_insns.push(Instruction::IfLt(instr));
}
Instruction::IfGe { label, a, b } => {
Instruction::IfGe(mut instr) => {
last_ins_was_a_label = false;
let label = new_labels
.get(&label)
instr.label = new_labels
.get(&instr.label)
.ok_or(anyhow!(
"Internal error: {} not found in renamed label",
label
instr.label
))?
.clone();
new_insns.push(Instruction::IfGe { label, a, b });
new_insns.push(Instruction::IfGe(instr));
}
Instruction::IfGt { label, a, b } => {
Instruction::IfGt(mut instr) => {
last_ins_was_a_label = false;
let label = new_labels
.get(&label)
instr.label = new_labels
.get(&instr.label)
.ok_or(anyhow!(
"Internal error: {} not found in renamed label",
label
instr.label
))?
.clone();
new_insns.push(Instruction::IfGt { label, a, b });
new_insns.push(Instruction::IfGt(instr));
}
Instruction::IfLe { label, a, b } => {
Instruction::IfLe(mut instr) => {
last_ins_was_a_label = false;
let label = new_labels
.get(&label)
instr.label = new_labels
.get(&instr.label)
.ok_or(anyhow!(
"Internal error: {} not found in renamed label",
label
instr.label
))?
.clone();
new_insns.push(Instruction::IfLe { label, a, b });
new_insns.push(Instruction::IfLe(instr));
}
Instruction::IfEqZ { label, a } => {
Instruction::IfEqZ(mut instr) => {
last_ins_was_a_label = false;
let label = new_labels
.get(&label)
instr.label = new_labels
.get(&instr.label)
.ok_or(anyhow!(
"Internal error: {} not found in renamed label",
label
instr.label
))?
.clone();
new_insns.push(Instruction::IfEqZ { label, a });
new_insns.push(Instruction::IfEqZ(instr));
}
Instruction::IfNeZ { label, a } => {
Instruction::IfNeZ(mut instr) => {
last_ins_was_a_label = false;
let label = new_labels
.get(&label)
instr.label = new_labels
.get(&instr.label)
.ok_or(anyhow!(
"Internal error: {} not found in renamed label",
label
instr.label
))?
.clone();
new_insns.push(Instruction::IfNeZ { label, a });
new_insns.push(Instruction::IfNeZ(instr));
}
Instruction::IfLtZ { label, a } => {
Instruction::IfLtZ(mut instr) => {
last_ins_was_a_label = false;
let label = new_labels
.get(&label)
instr.label = new_labels
.get(&instr.label)
.ok_or(anyhow!(
"Internal error: {} not found in renamed label",
label
instr.label
))?
.clone();
new_insns.push(Instruction::IfLtZ { label, a });
new_insns.push(Instruction::IfLtZ(instr));
}
Instruction::IfGeZ { label, a } => {
Instruction::IfGeZ(mut instr) => {
last_ins_was_a_label = false;
let label = new_labels
.get(&label)
instr.label = new_labels
.get(&instr.label)
.ok_or(anyhow!(
"Internal error: {} not found in renamed label",
label
instr.label
))?
.clone();
new_insns.push(Instruction::IfGeZ { label, a });
new_insns.push(Instruction::IfGeZ(instr));
}
Instruction::IfGtZ { label, a } => {
Instruction::IfGtZ(mut instr) => {
last_ins_was_a_label = false;
let label = new_labels
.get(&label)
instr.label = new_labels
.get(&instr.label)
.ok_or(anyhow!(
"Internal error: {} not found in renamed label",
label
instr.label
))?
.clone();
new_insns.push(Instruction::IfGtZ { label, a });
new_insns.push(Instruction::IfGtZ(instr));
}
Instruction::IfLeZ { label, a } => {
Instruction::IfLeZ(mut instr) => {
last_ins_was_a_label = false;
let label = new_labels
.get(&label)
instr.label = new_labels
.get(&instr.label)
.ok_or(anyhow!(
"Internal error: {} not found in renamed label",
label
instr.label
))?
.clone();
new_insns.push(Instruction::IfLeZ { label, a });
new_insns.push(Instruction::IfLeZ(instr));
}
Instruction::Try {
end_label,
mut handlers,
default_handler,
} => {
Instruction::Try(mut instr) => {
last_ins_was_a_label = false;
let end_label = new_labels
.get(&end_label)
instr.end_label = new_labels
.get(&instr.end_label)
.ok_or(anyhow!(
"Internal error: {} not found in renamed label",
end_label
instr.end_label
))?
.clone();
for handler in &mut handlers {
handler.1 = new_labels
.get(&handler.1)
for i in 0..instr.handlers.len() {
instr.handlers[i].1 = new_labels
.get(&instr.handlers[i].1)
.ok_or(anyhow!(
"Internal error: {} not found in renamed label",
handler.1
instr.handlers[i].1
))?
.clone();
}
let default_handler = default_handler
.map(|label| {
if let Some(label) = instr.default_handler {
instr.default_handler = Some(
new_labels
.get(&label)
.ok_or(anyhow!(
"Internal error: {} not found in renamed label",
label
))
.cloned()
})
.transpose()?;
new_insns.push(Instruction::Try {
end_label,
handlers,
default_handler,
});
))?
.clone(),
);
}
new_insns.push(Instruction::Try(instr));
}
Instruction::Switch { mut branches, reg } => {
Instruction::Switch(mut instr) => {
last_ins_was_a_label = false;
for label in branches.values_mut() {
for label in instr.branches.values_mut() {
*label = new_labels
.get(label)
.ok_or(anyhow!(
@ -469,15 +454,15 @@ impl Code {
))?
.clone();
}
new_insns.push(Instruction::Switch { branches, reg });
new_insns.push(Instruction::Switch(instr));
}
Instruction::Label { name } => {
if !used_labels.contains(&name) {
Instruction::Label(ins::Label { name }) => {
if used_labels.get(&name).is_none() {
//println!("{name} not used");
continue;
}
if !last_ins_was_a_label {
new_insns.push(Instruction::Label {
new_insns.push(Instruction::Label(ins::Label {
name: new_labels
.get(&name)
.ok_or(anyhow!(
@ -485,11 +470,11 @@ impl Code {
name
))?
.clone(),
});
}));
}
last_ins_was_a_label = true;
}
Instruction::Nop {} => (),
Instruction::Nop(_) => (),
instr => {
last_ins_was_a_label = false;
new_insns.push(instr);
@ -502,38 +487,3 @@ impl Code {
})
}
}
impl<V: Visitor> Visitable<V> for Code {
fn default_visit(&self, v: &mut V) -> Result<()> {
for ins in &self.insns {
v.visit_instruction(ins)?;
}
Ok(())
}
}
impl<V: VisitorMut> VisitableMut<V> for Code {
fn default_visit_mut(self, v: &mut V) -> Result<Self> {
let parameter_names = if let Some(parameter_names) = self.parameter_names {
let mut new_param = vec![];
for param in parameter_names {
if let Some(param) = param {
new_param.push(Some(v.visit_string(param)?));
} else {
new_param.push(None);
}
}
Some(new_param)
} else {
None
};
Ok(Self {
parameter_names,
insns: self
.insns
.into_iter()
.map(|ins| v.visit_instruction(ins))
.collect::<Result<_>>()?,
..self
})
}
}

View file

@ -1,350 +0,0 @@
//! The Control Flow Graph for a method.
use crate::{Instruction, Method, Result};
use anyhow::Context;
use std::collections::HashMap;
const EMPTY_INSNS_SLICE: &[Instruction] = &[];
/// A basic block of code of a method.
#[derive(Debug, PartialEq)]
pub struct MethodCFGNode<'a> {
/// Code represented by the block
pub code_block: &'a [Instruction],
/// Labels at the begining of the node if they exists
pub labels: Vec<String>,
/// Indices in CodeGraph.nodes of the next nodes
pub next_nodes: Vec<usize>,
/// Indices in CodeGraph.nodes of the previous nodes
pub prev_nodes: Vec<usize>,
}
/// The CFG for a method, with potentially additionnal informations.
#[derive(Debug, PartialEq)]
pub struct MethodCFG<'a> {
pub method: &'a Method,
pub nodes: Vec<MethodCFGNode<'a>>,
}
impl Method {
pub fn get_cfg(&self) -> Result<MethodCFG> {
MethodCFG::new(self)
}
}
impl<'a> MethodCFG<'a> {
pub fn new(method: &'a Method) -> Result<Self> {
let insns: &'a [Instruction] = if let Some(code) = method.code.as_ref() {
&code.insns
} else {
EMPTY_INSNS_SLICE
};
let mut nodes = vec![MethodCFGNode {
code_block: &insns[0..0],
labels: vec![],
next_nodes: vec![],
prev_nodes: vec![],
}];
let mut nodes_next_label = vec![vec![]];
let nb_insns = insns.len();
if nb_insns != 0 {
nodes[0].next_nodes.push(1);
}
let mut start_last_block = 0;
let mut last_labels = vec![];
let mut block_started = false;
let mut try_block: Vec<(String, Vec<String>)> = vec![];
for (i, ins) in insns.iter().enumerate() {
match ins {
// TODO: handle error better: list ins that can throw exceptions better
Instruction::Throw { .. }
| Instruction::InvokeVirtual { .. }
| Instruction::InvokeSuper { .. }
| Instruction::InvokeDirect { .. }
| Instruction::InvokeDirect { .. }
| Instruction::InvokeInterface { .. }
| Instruction::InvokePolymorphic { .. }
| Instruction::InvokeCustom { .. }
if !try_block.is_empty() =>
{
nodes_next_label.push(try_block.last().unwrap().1.clone());
let next_nodes =
if i + 1 < nb_insns && !matches!(ins, Instruction::Throw { .. }) {
vec![nodes.len() + 1] // If no exception, continue to next ins
} else {
vec![]
};
nodes.push(MethodCFGNode {
code_block: &insns[start_last_block..i + 1],
labels: last_labels,
next_nodes,
prev_nodes: vec![],
});
start_last_block = i + 1;
last_labels = vec![];
block_started = false;
}
Instruction::Goto { label } => {
nodes_next_label.push(vec![label.clone()]);
nodes.push(MethodCFGNode {
code_block: &insns[start_last_block..i + 1],
labels: last_labels,
next_nodes: vec![], // Do not continue the execution at next ins
prev_nodes: vec![],
});
start_last_block = i + 1;
last_labels = vec![];
block_started = false;
}
Instruction::Switch { branches, .. } => {
nodes_next_label.push(branches.values().cloned().collect());
let next_nodes = if i + 1 < nb_insns {
vec![nodes.len() + 1] // If no branches match, continue execution
} else {
vec![]
};
nodes.push(MethodCFGNode {
code_block: &insns[start_last_block..i + 1],
labels: last_labels,
next_nodes,
prev_nodes: vec![],
});
start_last_block = i + 1;
last_labels = vec![];
block_started = false;
}
Instruction::IfEq { label, .. }
| Instruction::IfNe { label, .. }
| Instruction::IfLt { label, .. }
| Instruction::IfGe { label, .. }
| Instruction::IfGt { label, .. }
| Instruction::IfLe { label, .. }
| Instruction::IfEqZ { label, .. }
| Instruction::IfNeZ { label, .. }
| Instruction::IfLtZ { label, .. }
| Instruction::IfGeZ { label, .. }
| Instruction::IfGtZ { label, .. }
| Instruction::IfLeZ { label, .. } => {
nodes_next_label.push(vec![label.clone()]);
let next_nodes = if i + 1 < nb_insns {
vec![nodes.len() + 1] // depending on test, continue execution
} else {
vec![]
};
nodes.push(MethodCFGNode {
code_block: &insns[start_last_block..i + 1],
labels: last_labels,
next_nodes,
prev_nodes: vec![],
});
start_last_block = i + 1;
last_labels = vec![];
block_started = false;
}
Instruction::Try {
end_label,
handlers,
default_handler,
} => {
let mut branches: Vec<_> =
handlers.iter().map(|(_, label)| label.clone()).collect();
if let Some(default_handler) = default_handler.as_ref().cloned() {
branches.push(default_handler);
}
try_block.push((end_label.clone(), branches))
}
Instruction::Label { name } => {
if !block_started {
last_labels.push(name.clone());
} else {
nodes_next_label.push(vec![]);
nodes.push(MethodCFGNode {
code_block: &insns[start_last_block..i],
labels: last_labels,
next_nodes: vec![nodes.len() + 1],
prev_nodes: vec![],
});
start_last_block = i;
last_labels = vec![name.clone()];
}
}
Instruction::ReturnVoid {}
| Instruction::Return { .. }
| Instruction::ReturnWide { .. }
| Instruction::ReturnObject { .. }
| Instruction::Throw { .. } => {
nodes_next_label.push(vec![]);
nodes.push(MethodCFGNode {
code_block: &insns[start_last_block..i + 1],
labels: last_labels,
next_nodes: vec![], // Do not continue the execution at next ins
prev_nodes: vec![],
});
start_last_block = i + 1;
last_labels = vec![];
block_started = false;
}
_ => {
if !ins.is_pseudo_ins() {
block_started = true;
}
}
}
}
if start_last_block != nb_insns {
nodes_next_label.push(vec![]);
nodes.push(MethodCFGNode {
code_block: &insns[start_last_block..nb_insns],
labels: last_labels,
next_nodes: vec![],
prev_nodes: vec![],
});
}
let label_to_node: HashMap<String, usize> = nodes
.iter()
.enumerate()
.flat_map(|(i, node)| node.labels.clone().into_iter().map(move |lab| (lab, i)))
.collect();
for (node, labels) in nodes.iter_mut().zip(nodes_next_label) {
for label in labels {
node.next_nodes
.push(*label_to_node.get(&label).with_context(|| {
format!("found jumb to label '{}' but label not found", label)
})?);
}
}
for i in 0..nodes.len() {
let next_nodes = nodes[i].next_nodes.clone();
for j in &next_nodes {
nodes[*j].prev_nodes.push(i);
}
}
Ok(Self { method, nodes })
}
/// Serialize the graph to dot format.
pub fn to_dot(&self, add_reg_ty: bool) -> String {
let mut dot_string: String = "digraph {\n".into();
dot_string += "overlap=false;\n";
dot_string += &self.to_dot_subgraph();
dot_string += "\n";
if add_reg_ty {
dot_string += &self.reg_types_dot();
}
dot_string += "}";
dot_string
}
/// Compute a sanitized version of the method name
pub(crate) fn dot_sanitized_method_dscr(&self) -> String {
self.method
.descriptor
.__str__()
.replace("/", "_")
.replace(";", "")
.replace(">", "")
.replace("-", "_")
.replace("(", "_")
.replace(")", "_")
.replace("[", "T")
}
// method call: cluster_{mth}:node_{i}:i{j}:e -> cluster_{mth2}:n ?
/// Serialize the graph to dot format.
pub fn to_dot_subgraph(&self) -> String {
let mut dot_string = format!(
"subgraph \"cluster_{}\" {{\n",
self.dot_sanitized_method_dscr()
);
dot_string += " style=\"dashed\";\n";
dot_string += " color=\"black\";\n";
dot_string += &format!(" label=\"{}\";\n", self.method.descriptor.__str__());
for (i, node) in self.nodes.iter().enumerate() {
let block_name = if i == 0 {
"ENTRY".into()
} else if !node.labels.is_empty() {
format!(
"block '{}'",
node.labels[0]
.replace("&", "&amp;")
.replace(">", "&gt;")
.replace("<", "&lt;")
.replace("\"", "&quot;")
)
} else {
format!("block {i}")
};
let label = if node.code_block.is_empty() {
//format!("{{\\< {block_name} \\>}}")
format!("<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\"><TR><TD><B> {block_name} </B></TD></TR></TABLE>")
} else {
/*
let mut label = format!("{{\\< {block_name} \\>:\\l\\\n");
for (i, ins) in node.code_block.iter().enumerate() {
label += &format!("|<i{i}>");
label += ins
.__str__()
.replace(" ", "\\ ")
.replace(">", "\\>")
.replace("<", "\\<")
.replace("\"", "\\\"")
.replace("{", "\\{")
.replace("}", "\\}")
//.replace("[", "\\[")
.as_str();
label += "\\l\\\n";
}
label += "}";
*/
let mut label = format!("<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\">\n <TR><TD PORT=\"blkname\"><B> {block_name} </B></TD></TR>\n");
for (i, ins) in node.code_block.iter().enumerate() {
label += &format!(
" <TR><TD PORT=\"i{i}\">{}</TD></TR>\n",
ins.__str__()
//.replace(" ", "\\ ")
.replace("&", "&amp;")
.replace(">", "&gt;")
.replace("<", "&lt;")
.replace("\"", "&quot;")
//.replace("{", "\\{")
//.replace("}", "\\}")
//.replace("[", "\\[")
.as_str()
);
}
label += " </TABLE>";
label
};
dot_string += &format!(
//" node_{i} [shape=record,style=filled,fillcolor=lightgrey,label=\"{label}\"];\n\n"
" node_{i} [shape=plaintext,style=filled,fillcolor=lightgrey,label=<{label}>];\n\n"
);
}
//dot_string += " node_end [shape=record,style=filled,fillcolor=lightgrey,label=\"{\\< EXIT \\>}\"];\n\n";
dot_string += " node_end [shape=plaintext,style=filled,fillcolor=lightgrey,label=<<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\"><TR><TD><B> EXIT </B></TD></TR></TABLE>>];\n\n";
for (i, node) in self.nodes.iter().enumerate() {
for j in &node.next_nodes {
if *j == i + 1 {
dot_string += &format!(
" node_{i}:s -> node_{j}:n [style=\"solid,bold\",color=black,weight=100,constraint=true];\n"
);
} else {
dot_string += &format!(
" node_{i}:s -> node_{j}:n [style=\"solid,bold\",color=black,weight=10,constraint=true];\n"
);
}
}
if node.next_nodes.is_empty() {
dot_string += &format!(
" node_{i}:s -> node_end:n [style=\"solid,bold\",color=black,weight=10,constraint=true];\n"
);
}
}
dot_string += "}\n";
dot_string
}
}

View file

@ -1,8 +0,0 @@
//! Module for more advanced code analysis.
//!
//! This is module is quite experimental but can be usefull.
pub mod method_cfg;
pub mod register_type;
pub use method_cfg::*;
pub use register_type::RegType;

View file

@ -1,555 +0,0 @@
//! Compute the register types at each label of a method.
use super::{MethodCFG, MethodCFGNode};
use crate::Instruction;
use std::collections::HashMap;
/// The different possible types of a register
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum RegType {
/// The register content is not yet defined
Undefined,
/// The register contains a java object. It can be a classical object
/// an array, a type, etc
Object,
/// The register contains a 32 bit scalar
SimpleScalar,
/// The register contains the first 32 bits of a wide register. If not
/// followed by a `SecondWideScalar`, it should be considered as a `SimpleScalar`?
FirstWideScalar,
/// The register contains the last 32 bits of a wide register. If not
/// preceded by a `FirstWideScalar`, it should be considered as a `SimpleScalar`?
SecondWideScalar,
/// The register can be either a scalar or an object.
Any,
}
impl RegType {
pub fn to_str(&self) -> &'static str {
match self {
RegType::Undefined => "undef",
RegType::Object => "object",
RegType::SimpleScalar => "scalar",
RegType::FirstWideScalar => "wide1",
RegType::SecondWideScalar => "wide2",
RegType::Any => "any",
}
}
}
impl MethodCFG<'_> {
pub fn get_reg_types(&self) -> HashMap<String, Vec<RegType>> {
let code = if let Some(code) = self.method.code.as_ref() {
code
} else {
return HashMap::new();
};
let nb_reg = code.registers_size as usize;
let mut end_block_reg_tys: Vec<_> = self
.nodes
.iter()
.map(|_| vec![RegType::Undefined; nb_reg])
.collect();
if end_block_reg_tys.is_empty() {
return HashMap::new();
}
// Initialize the entry block from function signature:
let mut i = (code.registers_size - code.ins_size(self.method)) as usize;
if !self.method.is_static {
end_block_reg_tys[0][i] = RegType::Object; // 'this'
i += 1;
}
for arg in &self.method.descriptor.proto.get_parameters() {
if arg.is_class() || arg.is_array() {
end_block_reg_tys[0][i] = RegType::Object;
i += 1;
} else if arg.is_long() || arg.is_double() {
end_block_reg_tys[0][i] = RegType::FirstWideScalar;
i += 1;
end_block_reg_tys[0][i] = RegType::SecondWideScalar;
i += 1;
} else {
end_block_reg_tys[0][i] = RegType::SimpleScalar;
i += 1;
}
}
let mut changed = true;
while changed {
// The first node is empty and depend on the function signature, it must not change
let mut new_end_block_reg_tys = vec![end_block_reg_tys[0].clone()];
for node in self.nodes.iter().skip(1) {
new_end_block_reg_tys.push(transform_reg_ty(
&merge_input(node, nb_reg, &end_block_reg_tys),
node,
))
}
changed = end_block_reg_tys != new_end_block_reg_tys;
end_block_reg_tys = new_end_block_reg_tys;
}
let start_block_reg_tys = &self
.nodes
.iter()
.map(|node| merge_input(node, nb_reg, &end_block_reg_tys))
.collect::<Vec<_>>();
self.nodes
.iter()
.enumerate()
.flat_map(|(i, node)| {
node.labels
.iter()
.map(move |label| (label.clone(), start_block_reg_tys[i].clone()))
})
.collect()
}
pub(crate) fn reg_types_dot(&self) -> String {
let types = self.get_reg_types();
let mut dot_string = format!(
"subgraph \"cluster_reg_types_{}\" {{\n",
self.dot_sanitized_method_dscr()
);
dot_string += " style=\"dashed\";\n";
dot_string += " color=\"black\";\n";
dot_string += " rankdir=\"TB\";\n";
dot_string += &format!(
" label=\"Register Types for {}\";\n",
self.method.descriptor.__str__()
);
let mut labels: Vec<_> = types.keys().collect();
labels.sort(); // Order more or less by addresses, good enought
let mut prev = None;
for label in labels.into_iter() {
let regs = types.get(label).unwrap();
let mut node_label = String::new();
node_label += "<TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\">\n";
node_label += &format!(
" <TR><TD COLSPAN=\"{}\"><B>register types at {label}</B></TD></TR>\n <TR>", regs.len(),
);
for i in 0..regs.len() {
node_label += &format!(" <TD>{i}</TD>\n");
}
node_label += " </TR><TR>\n";
for reg in regs {
//node_label += "|";
//node_label += reg.to_str();
node_label += &format!(" <TD>{}</TD>\n", reg.to_str());
}
//node_label += "|";
node_label += " </TR></TABLE>";
dot_string += &format!(
//" node_{label} [shape=record,style=filled,fillcolor=lightgrey,label=\"{node_label}\"];\n"
" node_{label} [shape=plaintext,style=filled,fillcolor=lightgrey,label=<{node_label}>];\n"
);
if let Some(prev) = prev {
// Add invisible edge to ensure the nodes are verticals
dot_string += &format!(" {prev}:s -> node_{label}:n [style=\"invis\"];\n");
}
prev = Some(format!("node_{label}"));
}
dot_string += "}\n";
dot_string += "\n";
for (i, node) in self.nodes.iter().enumerate() {
for (j, ins) in node.code_block.iter().enumerate() {
if let Instruction::Label { name } = ins {
let mid = self.dot_sanitized_method_dscr();
dot_string += &format!(
" node_{i}:i{j}:e -> node_{name} \
[ltail=cluster_{mid},lhead=cluster_reg_types_{mid},style=\"solid,bold\",color=grey,weight=10,constraint=true];\n",
);
}
}
}
dot_string
}
}
fn merge_input(node: &MethodCFGNode, nb_reg: usize, inputs: &[Vec<RegType>]) -> Vec<RegType> {
use RegType::*;
let mut reg_tys = vec![Undefined; nb_reg];
for i in &node.prev_nodes {
for (r, ty) in inputs[*i].iter().enumerate() {
reg_tys[r] = match (reg_tys[r], ty) {
(Undefined, _) => *ty,
(_, Undefined) => reg_tys[r],
(_, Any) => Any,
(Any, _) => Any,
(Object, Object) => Object,
(Object, SimpleScalar) => Any,
(Object, FirstWideScalar) => Any,
(Object, SecondWideScalar) => Any,
(SimpleScalar, Object) => Any,
(SimpleScalar, SimpleScalar) => SimpleScalar,
(SimpleScalar, FirstWideScalar) => SimpleScalar,
(SimpleScalar, SecondWideScalar) => SimpleScalar,
(FirstWideScalar, Object) => Any,
(FirstWideScalar, SimpleScalar) => SimpleScalar,
(FirstWideScalar, FirstWideScalar) => FirstWideScalar,
(FirstWideScalar, SecondWideScalar) => SimpleScalar,
(SecondWideScalar, Object) => Any,
(SecondWideScalar, SimpleScalar) => SimpleScalar,
(SecondWideScalar, FirstWideScalar) => SimpleScalar,
(SecondWideScalar, SecondWideScalar) => SecondWideScalar,
}
}
}
reg_tys
}
fn transform_reg_ty(input_types: &[RegType], cfg: &MethodCFGNode) -> Vec<RegType> {
use Instruction::*;
use RegType::*;
let mut types = input_types.to_vec();
for ins in cfg.code_block {
match ins {
Move { to, .. } => types[*to as usize] = SimpleScalar, // E: mism
MoveWide { to, .. } => {
types[*to as usize] = FirstWideScalar;
types[*to as usize + 1] = SecondWideScalar;
}
MoveObject { to, .. } => types[*to as usize] = Object,
MoveResult { to: reg } | Return { reg } | Const { reg, .. } | Switch { reg, .. } => {
types[*reg as usize] = SimpleScalar
}
MoveResultWide { to: reg } | ReturnWide { reg } | ConstWide { reg, .. } => {
types[*reg as usize] = FirstWideScalar;
types[*reg as usize + 1] = SecondWideScalar;
}
MoveResultObject { to: reg }
| MoveException { to: reg }
| ReturnObject { reg }
| ConstString { reg, .. }
| ConstClass { reg, .. }
| NewInstance { reg, .. }
| Throw { reg }
| ConstMethodHandle { to: reg, .. }
| ConstMethodType { to: reg, .. } => types[*reg as usize] = Object,
InstanceOf { dest, obj: _, .. } => {
// types[*obj as usize] = Object; not sure about this one
types[*dest as usize] = SimpleScalar;
}
ArrayLength { dest, arr } => {
types[*arr as usize] = Object;
types[*dest as usize] = SimpleScalar;
}
NewArray { reg, size_reg, .. } => {
types[*reg as usize] = Object;
types[*size_reg as usize] = SimpleScalar;
}
FilledNewArray { type_, reg_values } => {
let reg_ty = if type_.is_class() || type_.is_array() {
Object
//} else if type_.is_long() || type_.is_double() {
// SimpleScalar // Not supposed to happend so default to simple scalar just in
// case
} else {
SimpleScalar
};
for i in reg_values {
types[*i as usize] = reg_ty;
}
}
CmpLFloat { dest, b, c } | CmpGFloat { dest, b, c } => {
types[*dest as usize] = SimpleScalar;
types[*b as usize] = SimpleScalar;
types[*c as usize] = SimpleScalar;
}
CmpLDouble { dest, b, c } | CmpGDouble { dest, b, c } | CmpLong { dest, b, c } => {
types[*dest as usize] = SimpleScalar;
types[*b as usize] = FirstWideScalar;
types[*b as usize + 1] = SecondWideScalar;
types[*c as usize] = FirstWideScalar;
types[*c as usize + 1] = SecondWideScalar;
}
IfEq { a, b, .. }
| IfNe { a, b, .. }
| IfLt { a, b, .. }
| IfGe { a, b, .. }
| IfGt { a, b, .. }
| IfLe { a, b, .. } => {
types[*a as usize] = SimpleScalar;
types[*b as usize] = SimpleScalar;
}
IfEqZ { a, .. }
| IfNeZ { a, .. }
| IfLtZ { a, .. }
| IfGeZ { a, .. }
| IfGtZ { a, .. }
| IfLeZ { a, .. } => {
types[*a as usize] = SimpleScalar;
}
AGet {
dest: r,
arr: obj,
idx,
}
| AGetBoolean {
dest: r,
arr: obj,
idx,
}
| AGetByte {
dest: r,
arr: obj,
idx,
}
| AGetChar {
dest: r,
arr: obj,
idx,
}
| AGetShort {
dest: r,
arr: obj,
idx,
}
| APut {
from: r,
arr: obj,
idx,
}
| APutBoolean {
from: r,
arr: obj,
idx,
}
| APutByte {
from: r,
arr: obj,
idx,
}
| APutChar {
from: r,
arr: obj,
idx,
}
| APutShort {
from: r,
arr: obj,
idx,
} => {
types[*r as usize] = SimpleScalar;
types[*obj as usize] = Object;
types[*idx as usize] = SimpleScalar;
}
AGetWide {
dest: r,
arr: obj,
idx,
}
| APutWide {
from: r,
arr: obj,
idx,
} => {
types[*r as usize] = FirstWideScalar;
types[*r as usize + 1] = SecondWideScalar;
types[*obj as usize] = Object;
types[*idx as usize] = SimpleScalar;
}
AGetObject {
dest: r,
arr: obj,
idx,
}
| APutObject {
from: r,
arr: obj,
idx,
} => {
types[*r as usize] = Object;
types[*obj as usize] = Object;
types[*idx as usize] = SimpleScalar;
}
IGet { to: r, obj, .. }
| IGetBoolean { to: r, obj, .. }
| IGetByte { to: r, obj, .. }
| IGetChar { to: r, obj, .. }
| IGetShort { to: r, obj, .. }
| IPut { from: r, obj, .. }
| IPutBoolean { from: r, obj, .. }
| IPutByte { from: r, obj, .. }
| IPutChar { from: r, obj, .. }
| IPutShort { from: r, obj, .. } => {
types[*r as usize] = SimpleScalar;
types[*obj as usize] = Object;
}
IGetWide { to: r, obj, .. } | IPutWide { from: r, obj, .. } => {
types[*r as usize] = FirstWideScalar;
types[*r as usize] = SecondWideScalar;
types[*obj as usize] = Object;
}
IGetObject { to: r, obj, .. } | IPutObject { from: r, obj, .. } => {
types[*r as usize] = Object;
types[*obj as usize] = Object;
}
SGet { to: r, .. }
| SGetBoolean { to: r, .. }
| SGetByte { to: r, .. }
| SGetChar { to: r, .. }
| SGetShort { to: r, .. }
| SPut { from: r, .. }
| SPutBoolean { from: r, .. }
| SPutByte { from: r, .. }
| SPutChar { from: r, .. }
| SPutShort { from: r, .. } => {
types[*r as usize] = SimpleScalar;
}
SGetWide { to: r, .. } | SPutWide { from: r, .. } => {
types[*r as usize] = FirstWideScalar;
types[*r as usize] = SecondWideScalar;
}
SGetObject { to: r, .. } | SPutObject { from: r, .. } => {
types[*r as usize] = Object;
}
/* They are information to get from the method type, but, meh, this is not
* necessary (we should have the type from when the reg was initialized)
InvokeVirtual { method: _, args: _ }
| InvokeSuper { method: _, args: _ }
| InvokeDirect { method: _, args: _ }
| InvokeStatic { method: _, args: _ }
| InvokeInterface { method: _, args: _ } => todo!(),
InvokePolymorphic { method:_, proto: _, args: _ } => todo!(),
InvokeCustom { call_site: _, args: _ } => todo!(),
*/
NegInt { dest, val }
| NotInt { dest, val }
| NegFloat { dest, val }
| NegDouble { dest, val }
| IntToFloat { dest, val }
| FloatToInt { dest, val }
| IntToByte { dest, val }
| IntToChar { dest, val }
| IntToShort { dest, val } => {
types[*dest as usize] = SimpleScalar;
types[*val as usize] = SimpleScalar;
}
NegLong { dest, val }
| NotLong { dest, val }
| LongToDouble { dest, val }
| DoubleToLong { dest, val } => {
types[*dest as usize] = FirstWideScalar;
types[*dest as usize + 1] = SecondWideScalar;
types[*val as usize] = FirstWideScalar;
types[*val as usize + 1] = SecondWideScalar;
}
IntToLong { dest, val }
| IntToDouble { dest, val }
| FloatToLong { dest, val }
| FloatToDouble { dest, val } => {
types[*dest as usize] = FirstWideScalar;
types[*dest as usize + 1] = SecondWideScalar;
types[*val as usize] = SimpleScalar;
}
LongToInt { dest, val }
| LongToFloat { dest, val }
| DoubleToInt { dest, val }
| DoubleToFloat { dest, val } => {
types[*dest as usize] = SimpleScalar;
types[*val as usize] = FirstWideScalar;
types[*val as usize + 1] = SecondWideScalar;
}
AddInt { dest, b, c }
| SubInt { dest, b, c }
| MulInt { dest, b, c }
| DivInt { dest, b, c }
| RemInt { dest, b, c }
| AndInt { dest, b, c }
| OrInt { dest, b, c }
| XorInt { dest, b, c }
| ShlInt { dest, b, c }
| ShrInt { dest, b, c }
| UshrInt { dest, b, c }
| AddFloat { dest, b, c }
| SubFloat { dest, b, c }
| MulFloat { dest, b, c }
| DivFloat { dest, b, c }
| RemFloat { dest, b, c } => {
types[*dest as usize] = SimpleScalar;
types[*b as usize] = SimpleScalar;
types[*c as usize] = SimpleScalar;
}
AddLong { dest, b, c }
| SubLong { dest, b, c }
| MulLong { dest, b, c }
| DivLong { dest, b, c }
| RemLong { dest, b, c }
| AndLong { dest, b, c }
| OrLong { dest, b, c }
| XorLong { dest, b, c }
| ShlLong { dest, b, c }
| ShrLong { dest, b, c }
| UshrLong { dest, b, c }
| AddDouble { dest, b, c }
| SubDouble { dest, b, c }
| MulDouble { dest, b, c }
| DivDouble { dest, b, c }
| RemDouble { dest, b, c } => {
types[*dest as usize] = FirstWideScalar;
types[*dest as usize + 1] = SecondWideScalar;
types[*b as usize] = FirstWideScalar;
types[*b as usize + 1] = SecondWideScalar;
types[*c as usize] = FirstWideScalar;
types[*c as usize + 1] = SecondWideScalar;
}
AddInt2Addr { dest, b }
| SubInt2Addr { dest, b }
| MulInt2Addr { dest, b }
| DivInt2Addr { dest, b }
| RemInt2Addr { dest, b }
| AndInt2Addr { dest, b }
| OrInt2Addr { dest, b }
| XorInt2Addr { dest, b }
| ShlInt2Addr { dest, b }
| ShrInt2Addr { dest, b }
| UshrInt2Addr { dest, b }
| AddFloat2Addr { dest, b }
| SubFloat2Addr { dest, b }
| MulFloat2Addr { dest, b }
| DivFloat2Addr { dest, b }
| RemFloat2Addr { dest, b }
| AddIntLit { dest, b, .. }
| RsubIntLit { dest, b, .. }
| MulIntLit { dest, b, .. }
| DivIntLit { dest, b, .. }
| RemIntLit { dest, b, .. }
| AndIntLit { dest, b, .. }
| OrIntLit { dest, b, .. }
| XorIntLit { dest, b, .. }
| ShlIntLit { dest, b, .. }
| ShrIntLit { dest, b, .. }
| UshrIntLit { dest, b, .. } => {
types[*dest as usize] = SimpleScalar;
types[*b as usize] = SimpleScalar;
}
AddLong2Addr { dest, b }
| SubLong2Addr { dest, b }
| MulLong2Addr { dest, b }
| DivLong2Addr { dest, b }
| RemLong2Addr { dest, b }
| AndLong2Addr { dest, b }
| OrLong2Addr { dest, b }
| XorLong2Addr { dest, b }
| ShlLong2Addr { dest, b }
| ShrLong2Addr { dest, b }
| UshrLong2Addr { dest, b }
| AddDouble2Addr { dest, b }
| SubDouble2Addr { dest, b }
| MulDouble2Addr { dest, b }
| DivDouble2Addr { dest, b }
| RemDouble2Addr { dest, b } => {
types[*dest as usize] = FirstWideScalar;
types[*dest as usize + 1] = SecondWideScalar;
types[*b as usize] = FirstWideScalar;
types[*b as usize + 1] = SecondWideScalar;
}
_ => (),
}
}
types
}

View file

@ -2,20 +2,21 @@
use serde::{Deserialize, Serialize};
use std::cmp::{Ord, Ordering, PartialOrd};
use std::collections::hash_map::DefaultHasher;
use std::collections::HashSet;
use std::hash::Hash;
use std::hash::{Hash, Hasher};
use anyhow::{anyhow, bail, Context};
#[cfg(feature = "python")]
use pyo3::class::basic::CompareOp;
use pyo3::prelude::*;
use crate::{scalar::*, DexString, DexValue, Result, Visitable, VisitableMut, Visitor, VisitorMut};
use crate::{scalar::*, DexString, DexValue, Result};
use androscalpel_serializer::{StringDataItem, Uleb128};
/// The type of a method. The shorty is formated as described in
/// <https://source.android.com/docs/core/runtime/dex-format#shortydescriptor>
#[cfg_attr(feature = "python", pyclass(eq, ord, hash, frozen))]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[pyclass]
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
pub struct IdMethodType {
/// Type formated as described by <https://source.android.com/docs/core/runtime/dex-format#shortydescriptor>
pub(crate) shorty: DexString, // Redondant, but same as in the encoding, keep it in case we ever
@ -24,30 +25,6 @@ pub struct IdMethodType {
pub(crate) parameters: Vec<IdType>,
}
impl<V: Visitor> Visitable<V> for IdMethodType {
fn default_visit(&self, v: &mut V) -> Result<()> {
v.visit_string(&self.shorty)?;
v.visit_type(&self.return_type)?;
for ty in &self.parameters {
v.visit_type(ty)?;
}
Ok(())
}
}
impl<V: VisitorMut> VisitableMut<V> for IdMethodType {
fn default_visit_mut(mut self, v: &mut V) -> Result<Self> {
self.shorty = v.visit_string(self.shorty)?;
self.return_type = v.visit_type(self.return_type)?;
self.parameters = self
.parameters
.into_iter()
.map(|ty| v.visit_type(ty))
.collect::<Result<_>>()?;
Ok(self)
}
}
impl Ord for IdMethodType {
fn cmp(&self, other: &Self) -> Ordering {
self.return_type
@ -63,18 +40,18 @@ impl PartialOrd for IdMethodType {
}
}
#[cfg_attr(feature = "python", pymethods)]
#[pymethods]
impl IdMethodType {
pub fn to_json(&self) -> Result<String> {
Ok(serde_json::to_string(self)?)
}
#[cfg_attr(feature = "python", staticmethod)]
#[staticmethod]
pub fn from_json(json: &str) -> Result<Self> {
Ok(serde_json::from_str(json)?)
}
#[cfg_attr(feature = "python", new)]
#[new]
pub fn new(return_type: IdType, parameters: Vec<IdType>) -> Self {
Self {
shorty: Self::compute_shorty(&return_type, &parameters),
@ -86,12 +63,12 @@ impl IdMethodType {
/// Try to parse a smali representation of a prototype into a IdMethodType.
///
/// ```
/// use androscalpel::{IdMethodType, IdType};
/// 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::new(
/// IdMethodType(
/// IdType::boolean(),
/// vec![
/// IdType::class("androidx/core/util/Predicate"),
@ -101,7 +78,7 @@ impl IdMethodType {
/// )
/// );
/// ```
#[cfg_attr(feature = "python", staticmethod)]
#[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");
@ -166,6 +143,12 @@ impl IdMethodType {
self.parameters.clone()
}
pub fn __hash__(&self) -> u64 {
let mut hasher = DefaultHasher::new();
self.hash(&mut hasher);
hasher.finish()
}
/// Return all strings referenced in the Id.
pub fn get_all_strings(&self) -> HashSet<DexString> {
let mut strings = HashSet::new();
@ -191,24 +174,9 @@ impl IdMethodType {
protos.insert(self.clone());
protos
}
}
impl SmaliName for IdMethodType {
/// Convert a descriptor to its smali representation.
fn try_to_smali(&self) -> Result<String> {
Ok(format!(
"({}){}",
self.parameters
.iter()
.map(|param| param.try_to_smali())
.collect::<Result<Vec<String>>>()?
.join(""),
self.return_type.try_to_smali()?
))
}
/// Convert a smali representation to its descriptor.
fn try_from_smali(smali: &str) -> Result<Self> {
Self::from_smali(smali)
fn __richcmp__(&self, other: &Self, op: CompareOp) -> bool {
op.matches(self.cmp(other))
}
}
@ -216,10 +184,9 @@ impl IdMethodType {
/// Compute the format for the shorty as described in
/// <https://source.android.com/docs/core/runtime/dex-format#shortydescriptor>
pub fn compute_shorty(return_type: &IdType, parameters: &[IdType]) -> DexString {
// TODO: computing on dex string instead of string? that a lot of doubious conversion
let mut shorty: String = return_type.get_shorty().__str__();
let mut shorty: String = return_type.get_shorty().into();
for ty in parameters {
let ty: String = ty.get_shorty().__str__();
let ty: String = ty.get_shorty().into();
shorty.push_str(&ty);
}
shorty.into()
@ -231,39 +198,23 @@ impl IdMethodType {
/// as described here <https://source.android.com/docs/core/runtime/dex-format#typedescriptor>
// Not a clean rust enum because we want to be compatible with python, and maybe support strange
// malware edge case?
#[cfg_attr(feature = "python", pyclass(eq, ord, hash, frozen))]
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[pyclass]
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Deserialize, Serialize)]
pub struct IdType(pub(crate) DexString);
impl<V: Visitor> Visitable<V> for IdType {
fn default_visit(&self, v: &mut V) -> Result<()> {
v.visit_string(&self.0)
}
}
impl<V: VisitorMut> VisitableMut<V> for IdType {
fn default_visit_mut(self, v: &mut V) -> Result<Self> {
Ok(Self(v.visit_string(self.0)?))
}
}
#[cfg_attr(feature = "python", pymethods)]
#[pymethods]
impl IdType {
pub fn to_json(&self) -> Result<String> {
Ok(serde_json::to_string(self)?)
}
#[cfg_attr(feature = "python", staticmethod)]
#[staticmethod]
pub fn from_json(json: &str) -> Result<Self> {
Ok(serde_json::from_str(json)?)
}
#[cfg_attr(feature = "python", new)]
#[new]
pub fn new(
#[cfg_attr(
feature = "python",
pyo3(from_py_with = "crate::dex_string::as_dex_string")
)]
ty: DexString,
#[pyo3(from_py_with = "crate::dex_string::as_dex_string")] ty: DexString,
) -> Result<Self> {
// TODO: check format
let ty = Self(ty);
@ -272,7 +223,7 @@ impl IdType {
}
/// Return a type from its string representation without checking its format
#[cfg_attr(feature = "python", staticmethod)]
#[staticmethod]
pub fn unchecked_new(ty: DexString) -> Self {
Self(ty)
}
@ -283,7 +234,7 @@ impl IdType {
/// id types.
///
/// ```
/// use androscalpel::IdType;
/// use androscalpel::IdType
///
/// let id_type = IdType::from_smali(
/// "Landroidx/core/util/Predicate;"
@ -294,7 +245,7 @@ impl IdType {
/// IdType::class("androidx/core/util/Predicate")
/// );
/// ```
#[cfg_attr(feature = "python", staticmethod)]
#[staticmethod]
pub fn from_smali(smali_repr: &str) -> Result<Self> {
Self::new(smali_repr.into())
}
@ -316,7 +267,7 @@ impl IdType {
/// IdType::boolean(),
/// ])
/// ```
#[cfg_attr(feature = "python", staticmethod)]
#[staticmethod]
pub fn get_list_from_str(type_list: &str) -> Result<Vec<Self>> {
let mut lst = vec![];
let mut chars = type_list.chars();
@ -332,18 +283,15 @@ impl IdType {
'J' => Some(Self::long()),
'F' => Some(Self::float()),
'D' => Some(Self::double()),
'[' => {
array_dimmention += 1;
None
}
'[' => { array_dimmention += 1; None },
'L' => {
let mut class_name = String::new();
for cc in chars.by_ref() {
if cc == ';' {
break;
} else {
for cc in chars.by_ref(){
if cc == ';' { break;}
else {
class_name.push(cc);
}
}
Some(Self::class(&class_name))
}
@ -373,77 +321,66 @@ impl IdType {
}
/// Return the void type (for return type)
#[cfg_attr(feature = "python", staticmethod)]
#[staticmethod]
pub fn void() -> Self {
Self("V".into())
}
/// Return the boolean type
#[cfg_attr(feature = "python", staticmethod)]
#[staticmethod]
pub fn boolean() -> Self {
Self("Z".into())
}
/// Return the byte type
#[cfg_attr(feature = "python", staticmethod)]
#[staticmethod]
pub fn byte() -> Self {
Self("B".into())
}
/// Return the short type
#[cfg_attr(feature = "python", staticmethod)]
#[staticmethod]
pub fn short() -> Self {
Self("S".into())
}
/// Return the char type
#[cfg_attr(feature = "python", staticmethod)]
#[staticmethod]
pub fn char() -> Self {
Self("C".into())
}
/// Return the int type
#[cfg_attr(feature = "python", staticmethod)]
#[staticmethod]
pub fn int() -> Self {
Self("I".into())
}
/// Return the long type
#[cfg_attr(feature = "python", staticmethod)]
#[staticmethod]
pub fn long() -> Self {
Self("J".into())
}
/// Return the float type
#[cfg_attr(feature = "python", staticmethod)]
#[staticmethod]
pub fn float() -> Self {
Self("F".into())
}
/// Return the double type
#[cfg_attr(feature = "python", staticmethod)]
#[staticmethod]
pub fn double() -> Self {
Self("D".into())
}
/// Return the type for the class of fully qualified name `name`
#[cfg_attr(feature = "python", staticmethod)]
#[staticmethod]
pub fn class(name: &str) -> Self {
Self(format!("L{name};").into())
}
/// Return the type for the class of fully qualified name `name`.
/// Same as [`IdType::class`] but with a [`DexString`] name.
#[cfg_attr(feature = "python", staticmethod)]
pub fn class_from_dex_string(name: &DexString) -> Self {
let mut repr = name.clone();
repr.0.utf16_size.0 += 2;
repr.0.data.insert(0, b'L');
repr.0.data.push(b';');
Self(repr)
}
/// Return the type for an array of the specify `type_`
#[cfg_attr(feature = "python", staticmethod)]
#[staticmethod]
pub fn array(type_: &IdType) -> Self {
let mut ty = type_.clone();
ty.0 .0.utf16_size.0 += 1;
@ -456,11 +393,11 @@ impl IdType {
}
pub fn __str__(&self) -> String {
self.0.__str__()
(&self.0).into()
}
pub fn __repr__(&self) -> String {
let name: String = self.0.__str__();
let name: String = (&self.0).into();
format!("IdType(\"{name}\")")
}
@ -577,7 +514,7 @@ impl IdType {
{
Ok(())
} else {
let format: String = self.0.__str__();
let format: String = (&self.0).into();
Err(anyhow!("{format} is not a valid type"))
}
}
@ -628,6 +565,12 @@ impl IdType {
}
}
pub fn __hash__(&self) -> u64 {
let mut hasher = DefaultHasher::new();
self.hash(&mut hasher);
hasher.finish()
}
/// Return all strings referenced in the Id.
pub fn get_all_strings(&self) -> HashSet<DexString> {
let mut strings = HashSet::new();
@ -642,81 +585,41 @@ impl IdType {
types
}
/// Check if the class is a platform class (ie in the android SDK or a hidden API).
#[cfg(feature = "platform-list")]
pub fn is_platform_class(&self) -> bool {
match self.try_to_smali() {
Ok(smali) => androscalpel_platform_api_list::is_platform_class(&smali),
Err(_) => false,
}
fn __richcmp__(&self, other: &Self, op: CompareOp) -> bool {
op.matches(self.cmp(other))
}
// TODO: TESTS
}
impl SmaliName for IdType {
/// Convert a descriptor to its smali representation.
fn try_to_smali(&self) -> Result<String> {
let r = (&self.0 .0).try_into()?; // Anyhow conversion stuff
Ok(r)
}
/// Convert a smali representation to its descriptor.
fn try_from_smali(smali: &str) -> Result<Self> {
Self::from_smali(smali)
}
}
#[cfg_attr(feature = "python", pyclass(eq, ord, hash, frozen))]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[pyclass]
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
pub struct IdField {
/// The name of the field, format described at
/// <https://source.android.com/docs/core/runtime/dex-format#membername>
#[cfg_attr(feature = "python", pyo3(get))]
#[pyo3(get)]
pub name: DexString,
/// The type of the field.
#[cfg_attr(feature = "python", pyo3(get))]
#[pyo3(get)]
pub type_: IdType,
/// The class that own the field.
#[cfg_attr(feature = "python", pyo3(get))]
#[pyo3(get)]
pub class_: IdType,
}
impl<V: Visitor> Visitable<V> for IdField {
fn default_visit(&self, v: &mut V) -> Result<()> {
v.visit_string(&self.name)?;
v.visit_type(&self.type_)?;
v.visit_type(&self.class_)?;
Ok(())
}
}
impl<V: VisitorMut> VisitableMut<V> for IdField {
fn default_visit_mut(mut self, v: &mut V) -> Result<Self> {
self.name = v.visit_string(self.name)?;
self.type_ = v.visit_type(self.type_)?;
self.class_ = v.visit_type(self.class_)?;
Ok(self)
}
}
#[cfg_attr(feature = "python", pymethods)]
#[pymethods]
impl IdField {
pub fn to_json(&self) -> Result<String> {
Ok(serde_json::to_string(self)?)
}
#[cfg_attr(feature = "python", staticmethod)]
#[staticmethod]
pub fn from_json(json: &str) -> Result<Self> {
Ok(serde_json::from_str(json)?)
}
#[cfg_attr(feature = "python", new)]
#[new]
pub fn new(
#[cfg_attr(
feature = "python",
pyo3(from_py_with = "crate::dex_string::as_dex_string")
)]
name: DexString,
#[pyo3(from_py_with = "crate::dex_string::as_dex_string")] name: DexString,
type_: IdType,
class_: IdType,
) -> Self {
@ -731,19 +634,19 @@ impl IdField {
/// Try to parse a smali representation of a field into a IdField.
///
/// ```
/// use androscalpel::{IdField, IdType};
/// use androscalpel::IdField;
///
/// let proto = IdField::from_smali("Ljava/lang/annotation/ElementType;->FIELD:Ljava/lang/annotation/ElementType;").unwrap();
/// let proto = IdField::from_smali("Ljava/lang/annotation/ElementType;->METHOD:Ljava/lang/annotation/ElementType;").unwrap();
/// assert_eq!(
/// proto,
/// IdField::new(
/// "FIELD".into(),
/// "METHOD".into(),
/// IdType::class("java/lang/annotation/ElementType"),
/// IdType::class("java/lang/annotation/ElementType"),
/// )
/// );
/// ```
#[cfg_attr(feature = "python", staticmethod)]
#[staticmethod]
pub fn from_smali(smali_repr: &str) -> Result<Self> {
let arrow_i = smali_repr.find("->");
if arrow_i.is_none() {
@ -769,19 +672,25 @@ impl IdField {
}
pub fn __str__(&self) -> String {
let class: String = self.class_.get_name().__str__();
let name: String = self.name.__str__();
let ty: String = self.type_.get_name().__str__();
let class: String = self.class_.get_name().into();
let name: String = (&self.name).into();
let ty: String = self.type_.get_name().into();
format!("{class}->{name}:{ty}")
}
pub fn __repr__(&self) -> String {
let class: String = self.class_.__repr__();
let name: String = self.name.__str__();
let name: String = (&self.name).into();
let ty: String = self.type_.__repr__();
format!("IdField(\"{name}\", {ty}, {class})")
}
pub fn __hash__(&self) -> u64 {
let mut hasher = DefaultHasher::new();
self.hash(&mut hasher);
hasher.finish()
}
/// Return all strings referenced in the Id.
pub fn get_all_strings(&self) -> HashSet<DexString> {
let mut strings = HashSet::new();
@ -806,13 +715,8 @@ impl IdField {
fields
}
/// Check if the field is a platform field (ie in the android SDK or a hidden API).
#[cfg(feature = "platform-list")]
pub fn is_platform_field(&self) -> bool {
match self.try_to_smali() {
Ok(smali) => androscalpel_platform_api_list::is_platform_field(&smali),
Err(_) => false,
}
fn __richcmp__(&self, other: &Self, op: CompareOp) -> bool {
op.matches(self.cmp(other))
}
}
@ -831,71 +735,35 @@ impl PartialOrd for IdField {
}
}
impl SmaliName for IdField {
/// Convert a descriptor to its smali representation.
fn try_to_smali(&self) -> Result<String> {
let class: String = self.class_.try_to_smali()?;
let name: String = (&self.name.0).try_into()?;
let ty: String = self.type_.try_to_smali()?;
Ok(format!("{class}->{name}:{ty}"))
}
/// Convert a smali representation to its descriptor.
fn try_from_smali(smali: &str) -> Result<Self> {
Self::from_smali(smali)
}
}
/// The Id of a method.
#[cfg_attr(feature = "python", pyclass(eq, ord, hash, frozen))]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[pyclass]
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
pub struct IdMethod {
/// The class containing the method.
#[cfg_attr(feature = "python", pyo3(get))]
#[pyo3(get)]
pub class_: IdType,
/// The prototype (aka type or signature) of the method.
#[cfg_attr(feature = "python", pyo3(get))]
#[pyo3(get)]
pub proto: IdMethodType,
/// The name of the method.
#[cfg_attr(feature = "python", pyo3(get))]
#[pyo3(get)]
pub name: DexString,
}
impl<V: Visitor> Visitable<V> for IdMethod {
fn default_visit(&self, v: &mut V) -> Result<()> {
v.visit_string(&self.name)?;
v.visit_method_type(&self.proto)?;
v.visit_type(&self.class_)?;
Ok(())
}
}
impl<V: VisitorMut> VisitableMut<V> for IdMethod {
fn default_visit_mut(mut self, v: &mut V) -> Result<Self> {
self.name = v.visit_string(self.name)?;
self.proto = v.visit_method_type(self.proto)?;
self.class_ = v.visit_type(self.class_)?;
Ok(self)
}
}
#[cfg_attr(feature = "python", pymethods)]
#[pymethods]
impl IdMethod {
pub fn to_json(&self) -> Result<String> {
Ok(serde_json::to_string(self)?)
}
#[cfg_attr(feature = "python", staticmethod)]
#[staticmethod]
pub fn from_json(json: &str) -> Result<Self> {
Ok(serde_json::from_str(json)?)
}
#[cfg_attr(feature = "python", new)]
#[new]
pub fn new(
#[cfg_attr(
feature = "python",
pyo3(from_py_with = "crate::dex_string::as_dex_string")
)]
name: DexString,
#[pyo3(from_py_with = "crate::dex_string::as_dex_string")] name: DexString,
proto: IdMethodType,
class_: IdType,
) -> Self {
@ -909,7 +777,7 @@ impl IdMethod {
/// Try to parse a smali representation of method into a IdMethod.
///
/// ```
/// use androscalpel::{IdType, IdMethod, IdMethodType};
/// 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"
@ -931,7 +799,7 @@ impl IdMethod {
/// )
/// );
/// ```
#[cfg_attr(feature = "python", staticmethod)]
#[staticmethod]
pub fn from_smali(smali_repr: &str) -> Result<Self> {
let arrow_i = smali_repr.find("->");
if arrow_i.is_none() {
@ -981,6 +849,12 @@ impl IdMethod {
)
}
pub fn __hash__(&self) -> u64 {
let mut hasher = DefaultHasher::new();
self.hash(&mut hasher);
hasher.finish()
}
/// Return all strings referenced in the Id.
pub fn get_all_strings(&self) -> HashSet<DexString> {
let mut strings = HashSet::new();
@ -1012,13 +886,8 @@ impl IdMethod {
method_ids
}
/// Check if the method is a platform method (ie in the android SDK or a hidden API).
#[cfg(feature = "platform-list")]
pub fn is_platform_method(&self) -> bool {
match self.try_to_smali() {
Ok(smali) => androscalpel_platform_api_list::is_platform_method(&smali),
Err(_) => false,
}
fn __richcmp__(&self, other: &Self, op: CompareOp) -> bool {
op.matches(self.cmp(other))
}
}
@ -1037,51 +906,22 @@ impl PartialOrd for IdMethod {
}
}
impl SmaliName for IdMethod {
/// Convert a descriptor to its smali representation.
fn try_to_smali(&self) -> Result<String> {
let name: String = (&self.name.0).try_into()?;
Ok(format!(
"{}->{}{}",
self.class_.try_to_smali()?,
name,
self.proto.try_to_smali()?,
))
}
/// Convert a smali representation to its descriptor.
fn try_from_smali(smali: &str) -> Result<Self> {
Self::from_smali(smali)
}
}
#[cfg_attr(feature = "python", pyclass(eq, ord, hash, frozen))]
#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
#[pyclass]
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, Ord, PartialOrd)]
pub struct IdEnum(pub IdField);
impl<V: Visitor> Visitable<V> for IdEnum {
fn default_visit(&self, v: &mut V) -> Result<()> {
v.visit_field_id(&self.0)
}
}
impl<V: VisitorMut> VisitableMut<V> for IdEnum {
fn default_visit_mut(self, v: &mut V) -> Result<Self> {
Ok(Self(v.visit_field_id(self.0)?))
}
}
#[cfg_attr(feature = "python", pymethods)]
#[pymethods]
impl IdEnum {
pub fn to_json(&self) -> Result<String> {
Ok(serde_json::to_string(self)?)
}
#[cfg_attr(feature = "python", staticmethod)]
#[staticmethod]
pub fn from_json(json: &str) -> Result<Self> {
Ok(serde_json::from_str(json)?)
}
#[cfg_attr(feature = "python", new)]
#[new]
pub fn new(val: IdField) -> Self {
Self(val)
}
@ -1112,62 +952,8 @@ impl IdEnum {
pub fn get_all_field_ids(&self) -> HashSet<IdField> {
self.0.get_all_field_ids()
}
}
// Not to sure about this one
impl SmaliName for IdEnum {
/// Convert a descriptor to its smali representation.
fn try_to_smali(&self) -> Result<String> {
self.0.try_to_smali()
}
/// Convert a smali representation to its descriptor.
fn try_from_smali(smali: &str) -> Result<Self> {
Ok(Self(IdField::from_smali(smali)?))
fn __richcmp__(&self, other: &Self, op: CompareOp) -> bool {
op.matches(self.cmp(other))
}
}
pub trait SmaliName: Sized {
/// Convert a descriptor to its smali representation.
fn try_to_smali(&self) -> Result<String>;
/// Convert a smali representation to its descriptor.
fn try_from_smali(smali: &str) -> Result<Self>;
}
macro_rules! serde_serialize_to_smali {
// This macro takes an argument of designator `ident` and
// implement Serialize and Deserialize for the type, assuming
// it implement SmaliName.
($type_name:ident) => {
impl serde::Serialize for $type_name {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
Serialize::serialize(
&self
.try_to_smali()
.expect(&format!("Failed to convert {} to smali", self.__str__())),
serializer,
)
}
}
impl<'de> serde::Deserialize<'de> for $type_name {
fn deserialize<D>(deserializer: D) -> Result<$type_name, D::Error>
where
D: serde::Deserializer<'de>,
{
<String as Deserialize>::deserialize(deserializer).map(|string| {
$type_name::try_from_smali(&string)
.expect(&format!("Failed to convert {string} as smali"))
})
}
}
};
}
serde_serialize_to_smali!(IdMethodType);
serde_serialize_to_smali!(IdType);
serde_serialize_to_smali!(IdMethod);
serde_serialize_to_smali!(IdField);
serde_serialize_to_smali!(IdEnum);

View file

@ -1,56 +1,41 @@
use crate::{Result, Visitable, VisitableMut, Visitor, VisitorMut};
use anyhow::{Context, Error};
use crate::Result;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::cmp::{Ord, PartialOrd};
use std::collections::hash_map::DefaultHasher;
use std::collections::HashSet;
use std::hash::Hash;
use std::hash::{Hash, Hasher};
#[cfg(feature = "python")]
use pyo3::{exceptions::PyTypeError, prelude::*};
use pyo3::class::basic::CompareOp;
use pyo3::exceptions::PyTypeError;
use pyo3::prelude::*;
#[cfg_attr(feature = "python", pyclass(eq, ord, frozen, hash))]
#[pyclass]
#[derive(Clone, PartialEq, Eq, Ord, PartialOrd)]
pub struct DexString(pub androscalpel_serializer::StringDataItem);
// Kinda useless
impl<V: Visitor> Visitable<V> for DexString {
fn default_visit(&self, _: &mut V) -> Result<()> {
Ok(())
}
}
impl<V: VisitorMut> VisitableMut<V> for DexString {
fn default_visit_mut(self, _: &mut V) -> Result<Self> {
Ok(self)
}
}
impl std::fmt::Debug for DexString {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
match TryInto::<String>::try_into(self) {
Ok(string) => {
f.write_str(&format!(
"DexString({}, {:#x})",
string, self.0.utf16_size.0
))
/*
f.debug_tuple("DexString")
.field(&string)
.field(&self.0.utf16_size.0)
.finish()
*/
}
_ => {
f.write_str(&format!(
"DexString({:?}, {:#x})",
self.0.data, self.0.utf16_size.0
))
/*f.debug_tuple("DexString")
.field(&self.0.data)
.field(&self.0.utf16_size.0)
.finish()
*/
}
if let Ok(string) = TryInto::<String>::try_into(self) {
f.write_str(&format!(
"DexString({}, {:#x})",
string, self.0.utf16_size.0
))
/*
f.debug_tuple("DexString")
.field(&string)
.field(&self.0.utf16_size.0)
.finish()
*/
} else {
f.write_str(&format!(
"DexString({:?}, {:#x})",
self.0.data, self.0.utf16_size.0
))
/*f.debug_tuple("DexString")
.field(&self.0.data)
.field(&self.0.utf16_size.0)
.finish()
*/
}
}
}
@ -120,19 +105,17 @@ impl From<androscalpel_serializer::StringDataItem> for DexString {
}
}
impl TryFrom<&DexString> for String {
type Error = Error;
fn try_from(DexString(string): &DexString) -> Result<Self> {
impl From<&DexString> for String {
fn from(DexString(string): &DexString) -> Self {
string
.try_into()
.with_context(|| format!("InvalidEncoding:{:x?}", string.data))
.unwrap_or(format!("InvalidEncoding:{:x?}", string.data))
}
}
impl TryFrom<DexString> for String {
type Error = Error;
fn try_from(string: DexString) -> Result<Self> {
(&string).try_into()
impl From<DexString> for String {
fn from(string: DexString) -> Self {
(&string).into()
}
}
@ -155,11 +138,10 @@ impl Hash for DexString {
}
}
#[cfg(feature = "python")]
pub fn as_dex_string(obj: &Bound<'_, PyAny>) -> PyResult<DexString> {
if let Ok(string) = DexString::extract_bound(obj) {
pub fn as_dex_string(obj: &PyAny) -> PyResult<DexString> {
if let Ok(string) = DexString::extract(obj) {
Ok(string)
} else if let Ok(string) = String::extract_bound(obj) {
} else if let Ok(string) = String::extract(obj) {
Ok(string.into())
} else {
Err(PyErr::new::<PyTypeError, _>(format!(
@ -169,18 +151,18 @@ pub fn as_dex_string(obj: &Bound<'_, PyAny>) -> PyResult<DexString> {
}
}
#[cfg_attr(feature = "python", pymethods)]
#[pymethods]
impl DexString {
pub fn to_json(&self) -> Result<String> {
Ok(serde_json::to_string(self)?)
}
#[cfg_attr(feature = "python", staticmethod)]
#[staticmethod]
pub fn from_json(json: &str) -> Result<Self> {
Ok(serde_json::from_str(json)?)
}
#[cfg_attr(feature = "python", new)]
#[new]
pub fn new(s: &str) -> Self {
s.into()
}
@ -196,16 +178,17 @@ impl DexString {
}
pub fn __str__(&self) -> String {
match TryInto::<String>::try_into(self) {
Ok(string) => string,
_ => {
format!("string{:02x?}", self.0.data)
}
}
self.into()
}
pub fn __repr__(&self) -> String {
format!("{:?}", self)
self.into()
}
fn __hash__(&self) -> u64 {
let mut hasher = DefaultHasher::new();
self.hash(&mut hasher);
hasher.finish()
}
/// Return all strings references in the value.
@ -215,19 +198,10 @@ impl DexString {
strings
}
/// Build the concatenation of two string.
pub fn concatenate(&self, other: &Self) -> Self {
let Self(androscalpel_serializer::StringDataItem {
utf16_size: androscalpel_serializer::Uleb128(size1),
data: data1,
}) = self.clone();
let Self(androscalpel_serializer::StringDataItem {
utf16_size: androscalpel_serializer::Uleb128(size2),
data: data2,
}) = other.clone();
Self(androscalpel_serializer::StringDataItem {
utf16_size: androscalpel_serializer::Uleb128(size1 + size2),
data: data1.into_iter().chain(data2).collect(),
})
fn __richcmp__(&self, other: &PyAny, op: CompareOp) -> PyResult<bool> {
let other: Self = other
.extract()
.or(<String as FromPyObject>::extract(other).map(|string| string.into()))?;
Ok(op.matches(self.0.cmp(&other.0)))
}
}

File diff suppressed because it is too large Load diff

View file

@ -3,55 +3,51 @@
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
#[cfg(feature = "python")]
use pyo3::prelude::*;
use crate::{
DexAnnotationItem, DexString, DexValue, HiddenApiData, IdField, IdMethod, IdMethodType, IdType,
MethodHandle, Result, Visitable, VisitableMut, Visitor, VisitorMut,
DexAnnotationItem, DexString, DexValue, IdField, IdMethod, IdMethodType, IdType, MethodHandle,
Result,
};
use androscalpel_serializer::consts::*;
/// Represent a field.
#[cfg_attr(feature = "python", pyclass(eq))]
#[pyclass]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct Field {
/// The structure used to reference this field.
#[cfg_attr(feature = "python", pyo3(get))]
#[pyo3(get)]
pub descriptor: IdField,
/// The field visibility
#[cfg_attr(feature = "python", pyo3(get))]
#[pyo3(get)]
pub visibility: FieldVisibility,
/// If the field is defined for the class globally
#[cfg_attr(feature = "python", pyo3(get))]
#[pyo3(get)]
pub is_static: bool,
/// If the field is immutable after construction
#[cfg_attr(feature = "python", pyo3(get))]
#[pyo3(get)]
pub is_final: bool,
/// For thread safety
#[cfg_attr(feature = "python", pyo3(get))]
#[pyo3(get)]
pub is_volatile: bool,
/// If the field should **not** be saved by default serialization
#[cfg_attr(feature = "python", pyo3(get))]
#[pyo3(get)]
pub is_transient: bool,
/// If the field is not defined in the source code
#[cfg_attr(feature = "python", pyo3(get))]
#[pyo3(get)]
pub is_synthetic: bool,
/// If the field is an enumerated value
#[cfg_attr(feature = "python", pyo3(get))]
#[pyo3(get)]
pub is_enum: bool,
/// The default value of this field
pub value: Option<DexValue>,
/// The annotations for this field
#[cfg_attr(feature = "python", pyo3(get))]
#[pyo3(get)]
pub annotations: Vec<DexAnnotationItem>,
/// Hidden Api data.
#[cfg_attr(feature = "python", pyo3(get))]
pub hiddenapi: Option<HiddenApiData>,
}
/// Represent the visibility of a field
#[cfg_attr(feature = "python", pyclass(eq, eq_int))]
#[pyclass]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
pub enum FieldVisibility {
Public,
@ -60,18 +56,18 @@ pub enum FieldVisibility {
None_, // Actually quite common
}
#[cfg_attr(feature = "python", pymethods)]
#[pymethods]
impl Field {
pub fn to_json(&self) -> Result<String> {
Ok(serde_json::to_string(self)?)
}
#[cfg_attr(feature = "python", staticmethod)]
#[staticmethod]
pub fn from_json(json: &str) -> Result<Self> {
Ok(serde_json::from_str(json)?)
}
#[cfg_attr(feature = "python", new)]
#[new]
pub fn new(descriptor: IdField) -> Self {
Self {
descriptor,
@ -84,7 +80,6 @@ impl Field {
is_enum: false,
value: None,
annotations: vec![],
hiddenapi: None,
}
}
@ -125,16 +120,14 @@ impl Field {
}
/// Return the value as a python object
#[cfg(feature = "python")]
#[getter]
pub fn get_value(&self) -> Option<DexValue> {
self.value.clone()
}
/// Set the value from a python object
#[cfg(feature = "python")]
#[setter]
pub fn set_value(&mut self, ob: &Bound<'_, PyAny>) -> PyResult<()> {
pub fn set_value(&mut self, ob: &PyAny) -> PyResult<()> {
self.value = Some(ob.extract()?);
// TODO: check type match
Ok(())
@ -256,46 +249,7 @@ impl Field {
!self.annotations.is_empty()
}
/// Check if the field is a platform field (ie in the android SDK or a hidden API).
#[cfg(feature = "platform-list")]
pub fn is_platform_field(&self) -> bool {
self.descriptor.is_platform_field()
}
}
impl<V: Visitor> Visitable<V> for Field {
fn default_visit(&self, v: &mut V) -> Result<()> {
v.visit_field_id(&self.descriptor)?;
v.visit_field_visibility(&self.visibility)?;
if let Some(val) = &self.value {
v.visit_value(val)?;
}
for annot in &self.annotations {
v.visit_annotation_item(annot)?;
}
if let Some(hiddenapi) = &self.hiddenapi {
v.visit_hidden_api_data(hiddenapi)?;
}
Ok(())
}
}
impl<V: VisitorMut> VisitableMut<V> for Field {
fn default_visit_mut(self, v: &mut V) -> Result<Self> {
Ok(Self {
descriptor: v.visit_field_id(self.descriptor)?,
visibility: v.visit_field_visibility(self.visibility)?,
value: self.value.map(|val| v.visit_value(val)).transpose()?,
annotations: self
.annotations
.into_iter()
.map(|annot| v.visit_annotation_item(annot))
.collect::<Result<_>>()?,
hiddenapi: self
.hiddenapi
.map(|hiddenapi| v.visit_hidden_api_data(hiddenapi))
.transpose()?,
..self
})
pub fn __eq__(&self, other: &Self) -> bool {
self == other
}
}

View file

@ -1,211 +0,0 @@
//! Representation of the hidden API data
use crate::{Result, Visitable, VisitableMut, Visitor, VisitorMut};
use log::warn;
#[cfg(feature = "python")]
use pyo3::prelude::*;
use serde::{Deserialize, Serialize};
#[cfg_attr(feature = "python", pyclass)]
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize, Serialize)]
pub struct HiddenApiData {
pub permission: HiddenApiPermission,
pub domain: HiddenApiDomain,
}
impl From<&androscalpel_serializer::HiddenApiFlags> for HiddenApiData {
fn from(flags: &androscalpel_serializer::HiddenApiFlags) -> Self {
Self {
permission: flags.value.into(),
domain: flags.domain_api.into(),
}
}
}
impl From<androscalpel_serializer::HiddenApiFlags> for HiddenApiData {
fn from(flags: androscalpel_serializer::HiddenApiFlags) -> Self {
(&flags).into()
}
}
impl From<&HiddenApiData> for androscalpel_serializer::HiddenApiFlags {
fn from(flags: &HiddenApiData) -> Self {
Self {
value: (&flags.permission).into(),
domain_api: (&flags.domain).into(),
}
}
}
impl From<HiddenApiData> for androscalpel_serializer::HiddenApiFlags {
fn from(flags: HiddenApiData) -> Self {
(&flags).into()
}
}
impl<V: Visitor> Visitable<V> for HiddenApiData {
fn default_visit(&self, v: &mut V) -> Result<()> {
v.visit_hidden_api_permission(&self.permission)?;
v.visit_hidden_api_domain(&self.domain)?;
Ok(())
}
}
impl<V: VisitorMut> VisitableMut<V> for HiddenApiData {
fn default_visit_mut(self, v: &mut V) -> Result<Self> {
Ok(Self {
permission: v.visit_hidden_api_permission(self.permission)?,
domain: v.visit_hidden_api_domain(self.domain)?,
})
}
}
#[cfg_attr(feature = "python", pyclass)]
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize, Serialize)]
pub enum HiddenApiPermission {
/// Interfaces that can be freely used and are supported as
/// part of the officially documented Android framework Package Index
Whitelist {},
/// Non-SDK interfaces that can be used regardless of the
/// application's target API level
Greylist {},
/// Non-SDK interfaces that cannot be used regardless of the
/// application's target API level. Accessing one of these
/// interfaces causes a runtime error.
Blacklist {},
/// Non-SDK interfaces that can be used for Android 8.x and
/// below unless they are restricted (targetSdkVersion <= 27 (O_MR1)).
GreylistMaxO {},
/// Non-SDK interfaces that can be used for Android 9.x unless
/// they are restricted.
GreylistMaxP {},
/// Non-SDK interfaces that can be used for Android 10.x unless
/// they are restricted.
GreylistMaxQ {},
/// Non-SDK interfaces that can be used for Android 11.x unless
/// they are restricted.
GreylistMaxR {},
GreylistMaxS {},
/// Unknown flag, either an error or this crate is out of date.
Unknwon {
value: u8,
},
}
impl HiddenApiPermission {
pub fn to_smali_name(&self) -> &'static str {
match self {
Self::Whitelist {} => "whitelist",
Self::Greylist {} => "greylist",
Self::Blacklist {} => "blacklist",
Self::GreylistMaxO {} => "greylist-max-o",
Self::GreylistMaxP {} => "greylist-max-p",
Self::GreylistMaxQ {} => "greylist-max-q",
Self::GreylistMaxR {} => "greylist-max-r",
Self::GreylistMaxS {} => "greylist-max-s",
Self::Unknwon { .. } => "???list",
}
}
}
impl From<&androscalpel_serializer::HiddenApiValue> for HiddenApiPermission {
fn from(permission: &androscalpel_serializer::HiddenApiValue) -> Self {
match permission {
androscalpel_serializer::HiddenApiValue::Whitelist => Self::Whitelist {},
androscalpel_serializer::HiddenApiValue::Greylist => Self::Greylist {},
androscalpel_serializer::HiddenApiValue::Blacklist => Self::Blacklist {},
androscalpel_serializer::HiddenApiValue::GreylistMaxO => Self::GreylistMaxO {},
androscalpel_serializer::HiddenApiValue::GreylistMaxP => Self::GreylistMaxP {},
androscalpel_serializer::HiddenApiValue::GreylistMaxQ => Self::GreylistMaxQ {},
androscalpel_serializer::HiddenApiValue::GreylistMaxR => Self::GreylistMaxR {},
androscalpel_serializer::HiddenApiValue::GreylistMaxS => Self::GreylistMaxS {},
androscalpel_serializer::HiddenApiValue::Unknwon(val) => Self::Unknwon { value: *val },
}
}
}
impl From<androscalpel_serializer::HiddenApiValue> for HiddenApiPermission {
fn from(permission: androscalpel_serializer::HiddenApiValue) -> Self {
(&permission).into()
}
}
impl From<&HiddenApiPermission> for androscalpel_serializer::HiddenApiValue {
fn from(permission: &HiddenApiPermission) -> Self {
match permission {
HiddenApiPermission::Whitelist {} => Self::Whitelist,
HiddenApiPermission::Greylist {} => Self::Greylist,
HiddenApiPermission::Blacklist {} => Self::Blacklist,
HiddenApiPermission::GreylistMaxO {} => Self::GreylistMaxO,
HiddenApiPermission::GreylistMaxP {} => Self::GreylistMaxP,
HiddenApiPermission::GreylistMaxQ {} => Self::GreylistMaxQ,
HiddenApiPermission::GreylistMaxR {} => Self::GreylistMaxR,
HiddenApiPermission::GreylistMaxS {} => Self::GreylistMaxS,
HiddenApiPermission::Unknwon { value } => Self::Unknwon(*value),
}
}
}
impl From<HiddenApiPermission> for androscalpel_serializer::HiddenApiValue {
fn from(permission: HiddenApiPermission) -> Self {
(&permission).into()
}
}
#[cfg_attr(feature = "python", pyclass)]
#[derive(Debug, PartialEq, Eq, Hash, Clone, Deserialize, Serialize)]
pub struct HiddenApiDomain {
pub is_core_platform_api: bool,
pub is_test_api: bool,
/// Unknown domains.
pub unknown_flags: u32,
}
impl HiddenApiDomain {
pub fn to_smali_name(&self) -> String {
let mut value = String::new();
if self.is_core_platform_api {
value += "core-platform-api";
}
if self.is_test_api {
if !value.is_empty() {
value += "|";
}
value += "test-api";
}
if self.unknown_flags != 0 {
warn!(
"This attribut has an unknown hiddenapi domain set, this will not be display for compatibility with apktool"
);
}
value
}
}
impl From<&androscalpel_serializer::HiddenApiDomain> for HiddenApiDomain {
fn from(domain: &androscalpel_serializer::HiddenApiDomain) -> Self {
Self {
is_core_platform_api: domain.is_core_platform_api,
is_test_api: domain.is_test_api,
unknown_flags: domain.unknown_flags,
}
}
}
impl From<androscalpel_serializer::HiddenApiDomain> for HiddenApiDomain {
fn from(domain: androscalpel_serializer::HiddenApiDomain) -> Self {
(&domain).into()
}
}
impl From<&HiddenApiDomain> for androscalpel_serializer::HiddenApiDomain {
fn from(domain: &HiddenApiDomain) -> androscalpel_serializer::HiddenApiDomain {
Self {
is_core_platform_api: domain.is_core_platform_api,
is_test_api: domain.is_test_api,
unknown_flags: domain.unknown_flags,
}
}
}
impl From<HiddenApiDomain> for androscalpel_serializer::HiddenApiDomain {
fn from(domain: HiddenApiDomain) -> androscalpel_serializer::HiddenApiDomain {
(&domain).into()
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,5 @@
pub use anyhow::Result;
use anyhow::Result;
#[cfg(feature = "python")]
use pyo3::prelude::*;
pub mod annotation;
@ -12,19 +11,12 @@ pub mod dex_string;
pub mod dex_writer;
pub mod field;
mod hashmap_vectorize;
pub mod hiddenapi;
pub mod instructions;
pub mod method;
pub mod method_handle;
#[cfg(feature = "python")]
pub mod py_utils;
pub mod scalar;
pub mod utils;
pub mod value;
pub mod visitor;
#[cfg(feature = "code-analysis")]
pub mod code_analysis;
pub use annotation::*;
pub use apk::*;
@ -34,26 +26,18 @@ pub use dex_id::*;
pub use dex_string::*;
pub use dex_writer::*;
pub use field::*;
pub use hiddenapi::*;
pub use instructions as ins;
pub use instructions::*;
pub use method::*;
pub use method_handle::*;
pub use scalar::*;
pub use utils::*;
pub use value::*;
pub use visitor::*;
#[cfg(feature = "code-analysis")]
pub use code_analysis::*;
#[cfg(test)]
mod tests;
/// Androscalpel.
#[cfg(feature = "python")]
#[pymodule]
fn androscalpel(py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> {
fn androscalpel(py: Python, m: &PyModule) -> PyResult<()> {
pyo3_log::init();
m.add_class::<DexNull>()?;
m.add_class::<DexBoolean>()?;
@ -72,9 +56,15 @@ fn androscalpel(py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<IdMethod>()?;
m.add_class::<IdEnum>()?;
m.add_class::<HiddenApiData>()?;
m.add_class::<HiddenApiPermission>()?;
m.add_class::<HiddenApiDomain>()?;
m.add_class::<StaticPut>()?;
m.add_class::<StaticGet>()?;
m.add_class::<InstancePut>()?;
m.add_class::<InstanceGet>()?;
m.add_class::<InvokeStatic>()?;
m.add_class::<InvokeInstance>()?;
m.add_class::<InvokeConstructor>()?;
m.add_class::<InvokeDirect>()?;
m.add_class::<InvokeInterface>()?;
m.add_class::<DexAnnotationItem>()?;
m.add_class::<DexAnnotation>()?;
@ -88,19 +78,211 @@ fn androscalpel(py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<Apk>()?;
let ins_module = PyModule::new(py, "ins")?;
androscalpel_ins(py, &ins_module)?;
m.add_submodule(&ins_module)?;
androscalpel_ins(py, ins_module)?;
m.add_submodule(ins_module)?;
let utils_module = PyModule::new(py, "utils")?;
py_utils::export_module(py, &utils_module)?;
m.add_submodule(&utils_module)?;
py_utils::export_module(py, utils_module)?;
m.add_submodule(utils_module)?;
Ok(())
}
/// Dalvik opcode for Androscalpel.
#[cfg(feature = "python")]
fn androscalpel_ins(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> {
fn androscalpel_ins(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_class::<ins::CallSite>()?;
m.add_class::<ins::Instruction>()?;
m.add_class::<ins::Nop>()?;
m.add_class::<ins::Move>()?;
m.add_class::<ins::MoveWide>()?;
m.add_class::<ins::MoveObject>()?;
m.add_class::<ins::MoveResult>()?;
m.add_class::<ins::MoveResultWide>()?;
m.add_class::<ins::MoveResultObject>()?;
m.add_class::<ins::MoveException>()?;
m.add_class::<ins::ReturnVoid>()?;
m.add_class::<ins::Return>()?;
m.add_class::<ins::ReturnWide>()?;
m.add_class::<ins::ReturnObject>()?;
m.add_class::<ins::Const>()?;
m.add_class::<ins::ConstWide>()?;
m.add_class::<ins::ConstString>()?;
m.add_class::<ins::ConstClass>()?;
m.add_class::<ins::MonitorEnter>()?;
m.add_class::<ins::MonitorExit>()?;
m.add_class::<ins::CheckCast>()?;
m.add_class::<ins::InstanceOf>()?;
m.add_class::<ins::ArrayLength>()?;
m.add_class::<ins::NewInstance>()?;
m.add_class::<ins::NewArray>()?;
m.add_class::<ins::FilledNewArray>()?;
m.add_class::<ins::FillArrayData>()?;
m.add_class::<ins::Throw>()?;
m.add_class::<ins::Goto>()?;
m.add_class::<ins::Switch>()?;
m.add_class::<ins::CmpLFloat>()?;
m.add_class::<ins::CmpGFloat>()?;
m.add_class::<ins::CmpLDouble>()?;
m.add_class::<ins::CmpGDouble>()?;
m.add_class::<ins::CmpLong>()?;
m.add_class::<ins::IfEq>()?;
m.add_class::<ins::IfNe>()?;
m.add_class::<ins::IfLt>()?;
m.add_class::<ins::IfGe>()?;
m.add_class::<ins::IfGt>()?;
m.add_class::<ins::IfLe>()?;
m.add_class::<ins::IfEqZ>()?;
m.add_class::<ins::IfNeZ>()?;
m.add_class::<ins::IfLtZ>()?;
m.add_class::<ins::IfGeZ>()?;
m.add_class::<ins::IfGtZ>()?;
m.add_class::<ins::IfLeZ>()?;
m.add_class::<ins::AGet>()?;
m.add_class::<ins::AGetWide>()?;
m.add_class::<ins::AGetObject>()?;
m.add_class::<ins::AGetBoolean>()?;
m.add_class::<ins::AGetByte>()?;
m.add_class::<ins::AGetChar>()?;
m.add_class::<ins::AGetShort>()?;
m.add_class::<ins::APut>()?;
m.add_class::<ins::APutWide>()?;
m.add_class::<ins::APutObject>()?;
m.add_class::<ins::APutBoolean>()?;
m.add_class::<ins::APutByte>()?;
m.add_class::<ins::APutChar>()?;
m.add_class::<ins::APutShort>()?;
m.add_class::<ins::IGet>()?;
m.add_class::<ins::IGetWide>()?;
m.add_class::<ins::IGetObject>()?;
m.add_class::<ins::IGetBoolean>()?;
m.add_class::<ins::IGetByte>()?;
m.add_class::<ins::IGetChar>()?;
m.add_class::<ins::IGetShort>()?;
m.add_class::<ins::IPut>()?;
m.add_class::<ins::IPutWide>()?;
m.add_class::<ins::IPutObject>()?;
m.add_class::<ins::IPutBoolean>()?;
m.add_class::<ins::IPutByte>()?;
m.add_class::<ins::IPutChar>()?;
m.add_class::<ins::IPutShort>()?;
m.add_class::<ins::SGet>()?;
m.add_class::<ins::SGetWide>()?;
m.add_class::<ins::SGetObject>()?;
m.add_class::<ins::SGetBoolean>()?;
m.add_class::<ins::SGetByte>()?;
m.add_class::<ins::SGetChar>()?;
m.add_class::<ins::SGetShort>()?;
m.add_class::<ins::SPut>()?;
m.add_class::<ins::SPutWide>()?;
m.add_class::<ins::SPutObject>()?;
m.add_class::<ins::SPutBoolean>()?;
m.add_class::<ins::SPutByte>()?;
m.add_class::<ins::SPutChar>()?;
m.add_class::<ins::SPutShort>()?;
m.add_class::<ins::InvokeVirtual>()?;
m.add_class::<ins::InvokeSuper>()?;
m.add_class::<ins::InvokeDirect>()?;
m.add_class::<ins::InvokeStatic>()?;
m.add_class::<ins::InvokeInterface>()?;
m.add_class::<ins::NegInt>()?;
m.add_class::<ins::NotInt>()?;
m.add_class::<ins::NegLong>()?;
m.add_class::<ins::NotLong>()?;
m.add_class::<ins::NegFloat>()?;
m.add_class::<ins::NegDouble>()?;
m.add_class::<ins::IntToLong>()?;
m.add_class::<ins::IntToFloat>()?;
m.add_class::<ins::IntToDouble>()?;
m.add_class::<ins::LongToInt>()?;
m.add_class::<ins::LongToFloat>()?;
m.add_class::<ins::LongToDouble>()?;
m.add_class::<ins::FloatToInt>()?;
m.add_class::<ins::FloatToLong>()?;
m.add_class::<ins::FloatToDouble>()?;
m.add_class::<ins::DoubleToInt>()?;
m.add_class::<ins::DoubleToLong>()?;
m.add_class::<ins::DoubleToFloat>()?;
m.add_class::<ins::IntToByte>()?;
m.add_class::<ins::IntToChar>()?;
m.add_class::<ins::IntToShort>()?;
m.add_class::<ins::AddInt>()?;
m.add_class::<ins::SubInt>()?;
m.add_class::<ins::MulInt>()?;
m.add_class::<ins::DivInt>()?;
m.add_class::<ins::RemInt>()?;
m.add_class::<ins::AndInt>()?;
m.add_class::<ins::OrInt>()?;
m.add_class::<ins::XorInt>()?;
m.add_class::<ins::ShlInt>()?;
m.add_class::<ins::ShrInt>()?;
m.add_class::<ins::UshrInt>()?;
m.add_class::<ins::AddLong>()?;
m.add_class::<ins::SubLong>()?;
m.add_class::<ins::MulLong>()?;
m.add_class::<ins::DivLong>()?;
m.add_class::<ins::RemLong>()?;
m.add_class::<ins::AndLong>()?;
m.add_class::<ins::OrLong>()?;
m.add_class::<ins::XorLong>()?;
m.add_class::<ins::ShlLong>()?;
m.add_class::<ins::ShrLong>()?;
m.add_class::<ins::UshrLong>()?;
m.add_class::<ins::AddFloat>()?;
m.add_class::<ins::SubFloat>()?;
m.add_class::<ins::MulFloat>()?;
m.add_class::<ins::DivFloat>()?;
m.add_class::<ins::RemFloat>()?;
m.add_class::<ins::AddDouble>()?;
m.add_class::<ins::SubDouble>()?;
m.add_class::<ins::MulDouble>()?;
m.add_class::<ins::DivDouble>()?;
m.add_class::<ins::RemDouble>()?;
m.add_class::<ins::AddInt2Addr>()?;
m.add_class::<ins::SubInt2Addr>()?;
m.add_class::<ins::MulInt2Addr>()?;
m.add_class::<ins::DivInt2Addr>()?;
m.add_class::<ins::RemInt2Addr>()?;
m.add_class::<ins::AndInt2Addr>()?;
m.add_class::<ins::OrInt2Addr>()?;
m.add_class::<ins::XorInt2Addr>()?;
m.add_class::<ins::ShlInt2Addr>()?;
m.add_class::<ins::ShrInt2Addr>()?;
m.add_class::<ins::UshrInt2Addr>()?;
m.add_class::<ins::AddLong2Addr>()?;
m.add_class::<ins::SubLong2Addr>()?;
m.add_class::<ins::MulLong2Addr>()?;
m.add_class::<ins::DivLong2Addr>()?;
m.add_class::<ins::RemLong2Addr>()?;
m.add_class::<ins::AndLong2Addr>()?;
m.add_class::<ins::OrLong2Addr>()?;
m.add_class::<ins::XorLong2Addr>()?;
m.add_class::<ins::ShlLong2Addr>()?;
m.add_class::<ins::ShrLong2Addr>()?;
m.add_class::<ins::UshrLong2Addr>()?;
m.add_class::<ins::AddFloat2Addr>()?;
m.add_class::<ins::SubFloat2Addr>()?;
m.add_class::<ins::MulFloat2Addr>()?;
m.add_class::<ins::DivFloat2Addr>()?;
m.add_class::<ins::RemFloat2Addr>()?;
m.add_class::<ins::AddDouble2Addr>()?;
m.add_class::<ins::SubDouble2Addr>()?;
m.add_class::<ins::MulDouble2Addr>()?;
m.add_class::<ins::DivDouble2Addr>()?;
m.add_class::<ins::RemDouble2Addr>()?;
m.add_class::<ins::AddIntLit>()?;
m.add_class::<ins::RsubIntLit>()?;
m.add_class::<ins::MulIntLit>()?;
m.add_class::<ins::DivIntLit>()?;
m.add_class::<ins::RemIntLit>()?;
m.add_class::<ins::AndIntLit>()?;
m.add_class::<ins::OrIntLit>()?;
m.add_class::<ins::XorIntLit>()?;
m.add_class::<ins::ShlIntLit>()?;
m.add_class::<ins::ShrIntLit>()?;
m.add_class::<ins::UshrIntLit>()?;
m.add_class::<ins::InvokePolymorphic>()?;
m.add_class::<ins::InvokeCustom>()?;
m.add_class::<ins::ConstMethodHandle>()?;
m.add_class::<ins::ConstMethodType>()?;
m.add_class::<ins::Try>()?;
m.add_class::<ins::Label>()?;
Ok(())
}

View file

@ -3,77 +3,73 @@
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
#[cfg(feature = "python")]
use pyo3::prelude::*;
use crate::{
Code, DexAnnotationItem, DexString, HiddenApiData, IdField, IdMethod, IdMethodType, IdType,
MethodHandle, Result, Visitable, VisitableMut, Visitor, VisitorMut,
Code, DexAnnotationItem, DexString, IdField, IdMethod, IdMethodType, IdType, MethodHandle,
Result,
};
use androscalpel_serializer::consts::*;
/// Represent a method.
#[cfg_attr(feature = "python", pyclass(eq))]
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
#[pyclass]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct Method {
/// The structure used to reference this method.
#[cfg_attr(feature = "python", pyo3(get))]
#[pyo3(get)]
pub descriptor: IdMethod,
/// The field visibility.
#[cfg_attr(feature = "python", pyo3(get))]
#[pyo3(get)]
pub visibility: MethodVisibility,
/// Static methods do not take this in argument.
#[cfg_attr(feature = "python", pyo3(get))]
#[pyo3(get)]
pub is_static: bool,
/// Final methods are not averridable.
#[cfg_attr(feature = "python", pyo3(get))]
#[pyo3(get)]
pub is_final: bool,
/// Synchronized method automatically acquire their associated lock around call.
/// Can only be set in native method, (`[Self::is_native] = true`).
#[cfg_attr(feature = "python", pyo3(get))]
#[pyo3(get)]
pub is_synchronized: bool,
/// Bridge are automatically added by the compiler as a type-safe bridge.
#[cfg_attr(feature = "python", pyo3(get))]
#[pyo3(get)]
pub is_bridge: bool,
/// If the last argument should be treated as a "rest" argument by compiler
/// (for method of variable number of argument).
#[cfg_attr(feature = "python", pyo3(get))]
#[pyo3(get)]
pub is_varargs: bool,
/// If the method is a native method.
#[cfg_attr(feature = "python", pyo3(get))]
#[pyo3(get)]
pub is_native: bool,
/// Abstract methods are not implemented by the class.
#[cfg_attr(feature = "python", pyo3(get))]
#[pyo3(get)]
pub is_abstract: bool,
/// If the method must use strict rules for floating point arithmetic.
#[cfg_attr(feature = "python", pyo3(get))]
#[pyo3(get)]
pub is_strictfp: bool,
/// Synthetic method are not directly defined in the source code.
#[cfg_attr(feature = "python", pyo3(get))]
#[pyo3(get)]
pub is_synthetic: bool,
/// If the method is a constructor.
#[cfg_attr(feature = "python", pyo3(get))]
#[pyo3(get)]
pub is_constructor: bool,
/// If the method is declared as synchronize (just indicatif)
#[cfg_attr(feature = "python", pyo3(get))]
#[pyo3(get)]
pub is_declared_syncrhonized: bool,
/// The annotations for this method
#[cfg_attr(feature = "python", pyo3(get))]
#[pyo3(get)]
pub annotations: Vec<DexAnnotationItem>,
/// The annotations for the parameters of this method method
#[cfg_attr(feature = "python", pyo3(get))]
#[pyo3(get)]
pub parameters_annotations: Vec<Vec<DexAnnotationItem>>,
/// Hidden Api data.
#[cfg_attr(feature = "python", pyo3(get))]
pub hiddenapi: Option<HiddenApiData>,
/// The code of the method
#[cfg_attr(feature = "python", pyo3(get))]
#[pyo3(get)]
pub code: Option<Code>,
}
/// Represent the visibility of a field
#[cfg_attr(feature = "python", pyclass(eq, eq_int))]
#[pyclass]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
pub enum MethodVisibility {
Public,
@ -82,18 +78,18 @@ pub enum MethodVisibility {
None_, // Actually quite common
}
#[cfg_attr(feature = "python", pymethods)]
#[pymethods]
impl Method {
pub fn to_json(&self) -> Result<String> {
Ok(serde_json::to_string(self)?)
}
#[cfg_attr(feature = "python", staticmethod)]
#[staticmethod]
pub fn from_json(json: &str) -> Result<Self> {
Ok(serde_json::from_str(json)?)
}
#[cfg_attr(feature = "python", new)]
#[new]
pub fn new(descriptor: IdMethod) -> Self {
// TODO: take code option as arg and set the default flags accordingly
Self {
@ -113,7 +109,6 @@ impl Method {
annotations: vec![],
parameters_annotations: vec![],
code: None,
hiddenapi: None,
}
}
@ -295,79 +290,7 @@ impl Method {
.any(|list| !list.is_empty())
}
/// Compute the `ins_size` field. This is the number of register parameters, including the
/// `this` parameter for non-static methods.
/// This information is stored in the code item in dex files, but is not computable from the code
/// (as opposed to `outs_size`). The [`Method`] struct is needed to compute it.
pub fn ins_size(&self) -> u16 {
let mut ins = 0;
if !self.is_static {
ins += 1; // this
}
for param in &self.descriptor.proto.parameters {
if param.is_long() || param.is_double() {
ins += 2;
} else {
ins += 1;
}
}
ins
}
/// Check if the method is a platform method (ie in the android SDK or a hidden API).
#[cfg(feature = "platform-list")]
pub fn is_platform_method(&self) -> bool {
self.descriptor.is_platform_method()
}
}
impl<V: Visitor> Visitable<V> for Method {
fn default_visit(&self, v: &mut V) -> Result<()> {
v.visit_method_id(&self.descriptor)?;
v.visit_method_visibility(&self.visibility)?;
for annot in &self.annotations {
v.visit_annotation_item(annot)?;
}
for parameter_annotations in &self.parameters_annotations {
for annot in parameter_annotations {
v.visit_annotation_item(annot)?;
}
}
if let Some(hiddenapi) = &self.hiddenapi {
v.visit_hidden_api_data(hiddenapi)?;
}
if let Some(code) = &self.code {
v.visit_code(code)?;
}
Ok(())
}
}
impl<V: VisitorMut> VisitableMut<V> for Method {
fn default_visit_mut(self, v: &mut V) -> Result<Self> {
Ok(Self {
descriptor: v.visit_method_id(self.descriptor)?,
visibility: v.visit_method_visibility(self.visibility)?,
annotations: self
.annotations
.into_iter()
.map(|annot| v.visit_annotation_item(annot))
.collect::<Result<_>>()?,
parameters_annotations: self
.parameters_annotations
.into_iter()
.map(|parameter_annotations| {
parameter_annotations
.into_iter()
.map(|annot| v.visit_annotation_item(annot))
.collect::<Result<_>>()
})
.collect::<Result<_>>()?,
hiddenapi: self
.hiddenapi
.map(|hiddenapi| v.visit_hidden_api_data(hiddenapi))
.transpose()?,
code: self.code.map(|code| v.visit_code(code)).transpose()?,
..self
})
pub fn __eq__(&self, other: &Self) -> bool {
self == other
}
}

View file

@ -4,151 +4,790 @@ use serde::{Deserialize, Serialize};
use std::collections::HashSet;
use std::hash::Hash;
#[cfg(feature = "python")]
use pyo3::exceptions::PyTypeError;
use pyo3::prelude::*;
use crate::dex_id::*;
use crate::{
DexString, FieldIdCollector, MethodHandleCollector, MethodIdCollector, MethodTypeCollector,
Result, StringCollector, TypeCollector, Visitable, VisitableMut, Visitor, VisitorMut,
};
use crate::DexString;
use crate::Result;
/// The structure use to reference a method invocation.
#[cfg_attr(feature = "python", pyclass(eq))]
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
pub enum MethodHandle {
StaticPut { field: IdField },
StaticGet { field: IdField },
InstancePut { field: IdField },
InstanceGet { field: IdField },
InvokeStatic { method: IdMethod },
InvokeInstance { method: IdMethod },
InvokeConstructor { method: IdMethod },
InvokeDirect { method: IdMethod },
InvokeInterface { method: IdMethod },
StaticPut(StaticPut),
StaticGet(StaticGet),
InstancePut(InstancePut),
InstanceGet(InstanceGet),
InvokeStatic(InvokeStatic),
InvokeInstance(InvokeInstance),
InvokeConstructor(InvokeConstructor),
InvokeDirect(InvokeDirect),
InvokeInterface(InvokeInterface),
}
impl<V: Visitor> Visitable<V> for MethodHandle {
fn default_visit(&self, v: &mut V) -> Result<()> {
match self {
Self::StaticPut { field } => v.visit_field_id(field)?,
Self::StaticGet { field } => v.visit_field_id(field)?,
Self::InstancePut { field } => v.visit_field_id(field)?,
Self::InstanceGet { field } => v.visit_field_id(field)?,
Self::InvokeStatic { method } => v.visit_method_id(method)?,
Self::InvokeInstance { method } => v.visit_method_id(method)?,
Self::InvokeConstructor { method } => v.visit_method_id(method)?,
Self::InvokeDirect { method } => v.visit_method_id(method)?,
Self::InvokeInterface { method } => v.visit_method_id(method)?,
}
Ok(())
}
}
#[pyclass]
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
pub struct StaticPut(pub IdField);
impl<V: VisitorMut> VisitableMut<V> for MethodHandle {
fn default_visit_mut(self, v: &mut V) -> Result<Self> {
match self {
Self::StaticPut { field } => Ok(Self::StaticPut {
field: v.visit_field_id(field)?,
}),
Self::StaticGet { field } => Ok(Self::StaticGet {
field: v.visit_field_id(field)?,
}),
Self::InstancePut { field } => Ok(Self::InstancePut {
field: v.visit_field_id(field)?,
}),
Self::InstanceGet { field } => Ok(Self::InstanceGet {
field: v.visit_field_id(field)?,
}),
Self::InvokeStatic { method } => Ok(Self::InvokeStatic {
method: v.visit_method_id(method)?,
}),
Self::InvokeInstance { method } => Ok(Self::InvokeInstance {
method: v.visit_method_id(method)?,
}),
Self::InvokeConstructor { method } => Ok(Self::InvokeConstructor {
method: v.visit_method_id(method)?,
}),
Self::InvokeDirect { method } => Ok(Self::InvokeDirect {
method: v.visit_method_id(method)?,
}),
Self::InvokeInterface { method } => Ok(Self::InvokeInterface {
method: v.visit_method_id(method)?,
}),
}
}
}
#[cfg_attr(feature = "python", pymethods)]
impl MethodHandle {
#[pymethods]
impl StaticPut {
pub fn to_json(&self) -> Result<String> {
Ok(serde_json::to_string(self)?)
}
#[cfg_attr(feature = "python", staticmethod)]
#[staticmethod]
pub fn from_json(json: &str) -> Result<Self> {
Ok(serde_json::from_str(json)?)
}
#[new]
pub fn new(val: IdField) -> Self {
Self(val)
}
pub fn get_field(&self) -> IdField {
self.0.clone()
}
pub fn __str__(&self) -> String {
self.__repr__()
}
pub fn __repr__(&self) -> String {
format!("StaticPut({})", self.0.__str__())
}
/// Return all strings referenced in the handle.
pub fn get_all_strings(&self) -> HashSet<DexString> {
self.0.get_all_strings()
}
/// Return all types referenced in the handle.
pub fn get_all_types(&self) -> HashSet<IdType> {
self.0.get_all_types()
}
/// Return all prototypes referenced in the handle.
pub fn get_all_protos(&self) -> HashSet<IdMethodType> {
HashSet::new()
}
/// Return all field ids referenced in the handle.
pub fn get_all_field_ids(&self) -> HashSet<IdField> {
let mut fields = HashSet::new();
fields.insert(self.0.clone());
fields
}
/// Return all method ids referenced in the handle.
pub fn get_all_method_ids(&self) -> HashSet<IdMethod> {
HashSet::new()
}
/// Return all method handles referenced in the handle.
pub fn get_all_method_handles(&self) -> HashSet<MethodHandle> {
let mut methods = HashSet::new();
methods.insert(MethodHandle::StaticPut(self.clone()));
methods
}
}
#[pyclass]
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
pub struct StaticGet(pub IdField);
#[pymethods]
impl StaticGet {
pub fn to_json(&self) -> Result<String> {
Ok(serde_json::to_string(self)?)
}
#[staticmethod]
pub fn from_json(json: &str) -> Result<Self> {
Ok(serde_json::from_str(json)?)
}
#[new]
pub fn new(val: IdField) -> Self {
Self(val)
}
pub fn get_field(&self) -> IdField {
self.0.clone()
}
pub fn __str__(&self) -> String {
self.__repr__()
}
pub fn __repr__(&self) -> String {
format!("StaticGet({})", self.0.__str__())
}
/// Return all strings referenced in the handle.
pub fn get_all_strings(&self) -> HashSet<DexString> {
self.0.get_all_strings()
}
/// Return all types referenced in the handle.
pub fn get_all_types(&self) -> HashSet<IdType> {
self.0.get_all_types()
}
/// Return all prototypes referenced in the handle.
pub fn get_all_protos(&self) -> HashSet<IdMethodType> {
HashSet::new()
}
/// Return all field ids referenced in the handle.
pub fn get_all_field_ids(&self) -> HashSet<IdField> {
let mut fields = HashSet::new();
fields.insert(self.0.clone());
fields
}
/// Return all method ids referenced in the handle.
pub fn get_all_method_ids(&self) -> HashSet<IdMethod> {
HashSet::new()
}
/// Return all method handles referenced in the handle.
pub fn get_all_method_handles(&self) -> HashSet<MethodHandle> {
let mut methods = HashSet::new();
methods.insert(MethodHandle::StaticGet(self.clone()));
methods
}
}
#[pyclass]
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
pub struct InstancePut(pub IdField);
#[pymethods]
impl InstancePut {
pub fn to_json(&self) -> Result<String> {
Ok(serde_json::to_string(self)?)
}
#[staticmethod]
pub fn from_json(json: &str) -> Result<Self> {
Ok(serde_json::from_str(json)?)
}
#[new]
pub fn new(val: IdField) -> Self {
Self(val)
}
pub fn get_field(&self) -> IdField {
self.0.clone()
}
pub fn __str__(&self) -> String {
self.__repr__()
}
pub fn __repr__(&self) -> String {
format!("InstancePut({})", self.0.__str__())
}
/// Return all strings referenced in the handle.
pub fn get_all_strings(&self) -> HashSet<DexString> {
self.0.get_all_strings()
}
/// Return all types referenced in the handle.
pub fn get_all_types(&self) -> HashSet<IdType> {
self.0.get_all_types()
}
/// Return all prototypes referenced in the handle.
pub fn get_all_protos(&self) -> HashSet<IdMethodType> {
HashSet::new()
}
/// Return all field ids referenced in the handle.
pub fn get_all_field_ids(&self) -> HashSet<IdField> {
let mut fields = HashSet::new();
fields.insert(self.0.clone());
fields
}
/// Return all method ids referenced in the handle.
pub fn get_all_method_ids(&self) -> HashSet<IdMethod> {
HashSet::new()
}
/// Return all method handles referenced in the handle.
pub fn get_all_method_handles(&self) -> HashSet<MethodHandle> {
let mut methods = HashSet::new();
methods.insert(MethodHandle::InstancePut(self.clone()));
methods
}
}
#[pyclass]
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
pub struct InstanceGet(pub IdField);
#[pymethods]
impl InstanceGet {
pub fn to_json(&self) -> Result<String> {
Ok(serde_json::to_string(self)?)
}
#[staticmethod]
pub fn from_json(json: &str) -> Result<Self> {
Ok(serde_json::from_str(json)?)
}
#[new]
pub fn new(val: IdField) -> Self {
Self(val)
}
pub fn get_field(&self) -> IdField {
self.0.clone()
}
pub fn __str__(&self) -> String {
self.__repr__()
}
pub fn __repr__(&self) -> String {
format!("InstanceGet({})", self.0.__str__())
}
/// Return all strings referenced in the handle.
pub fn get_all_strings(&self) -> HashSet<DexString> {
self.0.get_all_strings()
}
/// Return all types referenced in the handle.
pub fn get_all_types(&self) -> HashSet<IdType> {
self.0.get_all_types()
}
/// Return all prototypes referenced in the handle.
pub fn get_all_protos(&self) -> HashSet<IdMethodType> {
HashSet::new()
}
/// Return all field ids referenced in the handle.
pub fn get_all_field_ids(&self) -> HashSet<IdField> {
let mut fields = HashSet::new();
fields.insert(self.0.clone());
fields
}
/// Return all method ids referenced in the handle.
pub fn get_all_method_ids(&self) -> HashSet<IdMethod> {
HashSet::new()
}
/// Return all method handles referenced in the handle.
pub fn get_all_method_handles(&self) -> HashSet<MethodHandle> {
let mut methods = HashSet::new();
methods.insert(MethodHandle::InstanceGet(self.clone()));
methods
}
}
#[pyclass]
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
pub struct InvokeStatic(pub IdMethod);
#[pymethods]
impl InvokeStatic {
pub fn to_json(&self) -> Result<String> {
Ok(serde_json::to_string(self)?)
}
#[staticmethod]
pub fn from_json(json: &str) -> Result<Self> {
Ok(serde_json::from_str(json)?)
}
#[new]
pub fn new(val: IdMethod) -> Self {
Self(val)
}
pub fn get_method(&self) -> IdMethod {
self.0.clone()
}
pub fn __str__(&self) -> String {
self.__repr__()
}
pub fn __repr__(&self) -> String {
format!("InvokeStatic({})", self.0.__str__())
}
/// Return all strings referenced in the handle.
pub fn get_all_strings(&self) -> HashSet<DexString> {
self.0.get_all_strings()
}
/// Return all types referenced in the handle.
pub fn get_all_types(&self) -> HashSet<IdType> {
self.0.get_all_types()
}
/// Return all prototypes referenced in the handle.
pub fn get_all_protos(&self) -> HashSet<IdMethodType> {
self.0.get_all_protos()
}
/// Return all field ids referenced in the handle.
pub fn get_all_field_ids(&self) -> HashSet<IdField> {
HashSet::new()
}
/// Return all method ids referenced in the handle.
pub fn get_all_method_ids(&self) -> HashSet<IdMethod> {
let mut methods = HashSet::new();
methods.insert(self.0.clone());
methods
}
/// Return all method handles referenced in the handle.
pub fn get_all_method_handles(&self) -> HashSet<MethodHandle> {
let mut methods = HashSet::new();
methods.insert(MethodHandle::InvokeStatic(self.clone()));
methods
}
}
#[pyclass]
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
pub struct InvokeInstance(pub IdMethod);
#[pymethods]
impl InvokeInstance {
pub fn to_json(&self) -> Result<String> {
Ok(serde_json::to_string(self)?)
}
#[staticmethod]
pub fn from_json(json: &str) -> Result<Self> {
Ok(serde_json::from_str(json)?)
}
#[new]
pub fn new(val: IdMethod) -> Self {
Self(val)
}
pub fn get_method(&self) -> IdMethod {
self.0.clone()
}
pub fn __str__(&self) -> String {
self.__repr__()
}
pub fn __repr__(&self) -> String {
format!("InvokeInstance({})", self.0.__str__())
}
/// Return all strings referenced in the handle.
pub fn get_all_strings(&self) -> HashSet<DexString> {
self.0.get_all_strings()
}
/// Return all types referenced in the handle.
pub fn get_all_types(&self) -> HashSet<IdType> {
self.0.get_all_types()
}
/// Return all prototypes referenced in the handle.
pub fn get_all_protos(&self) -> HashSet<IdMethodType> {
self.0.get_all_protos()
}
/// Return all field ids referenced in the handle.
pub fn get_all_field_ids(&self) -> HashSet<IdField> {
HashSet::new()
}
/// Return all method ids referenced in the handle.
pub fn get_all_method_ids(&self) -> HashSet<IdMethod> {
let mut methods = HashSet::new();
methods.insert(self.0.clone());
methods
}
/// Return all method handles referenced in the handle.
pub fn get_all_method_handles(&self) -> HashSet<MethodHandle> {
let mut methods = HashSet::new();
methods.insert(MethodHandle::InvokeInstance(self.clone()));
methods
}
}
#[pyclass]
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
pub struct InvokeConstructor(pub IdMethod);
#[pymethods]
impl InvokeConstructor {
pub fn to_json(&self) -> Result<String> {
Ok(serde_json::to_string(self)?)
}
#[staticmethod]
pub fn from_json(json: &str) -> Result<Self> {
Ok(serde_json::from_str(json)?)
}
#[new]
pub fn new(val: IdMethod) -> Self {
Self(val)
}
pub fn get_method(&self) -> IdMethod {
self.0.clone()
}
pub fn __str__(&self) -> String {
self.__repr__()
}
pub fn __repr__(&self) -> String {
format!("InvokeConstructor({})", self.0.__str__())
}
/// Return all strings referenced in the handle.
pub fn get_all_strings(&self) -> HashSet<DexString> {
self.0.get_all_strings()
}
/// Return all types referenced in the handle.
pub fn get_all_types(&self) -> HashSet<IdType> {
self.0.get_all_types()
}
/// Return all prototypes referenced in the handle.
pub fn get_all_protos(&self) -> HashSet<IdMethodType> {
self.0.get_all_protos()
}
/// Return all field ids referenced in the handle.
pub fn get_all_field_ids(&self) -> HashSet<IdField> {
HashSet::new()
}
/// Return all method ids referenced in the handle.
pub fn get_all_method_ids(&self) -> HashSet<IdMethod> {
let mut methods = HashSet::new();
methods.insert(self.0.clone());
methods
}
/// Return all method handles referenced in the handle.
pub fn get_all_method_handles(&self) -> HashSet<MethodHandle> {
let mut methods = HashSet::new();
methods.insert(MethodHandle::InvokeConstructor(self.clone()));
methods
}
}
#[pyclass]
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
pub struct InvokeDirect(pub IdMethod);
#[pymethods]
impl InvokeDirect {
pub fn to_json(&self) -> Result<String> {
Ok(serde_json::to_string(self)?)
}
#[staticmethod]
pub fn from_json(json: &str) -> Result<Self> {
Ok(serde_json::from_str(json)?)
}
#[new]
pub fn new(val: IdMethod) -> Self {
Self(val)
}
pub fn get_method(&self) -> IdMethod {
self.0.clone()
}
pub fn __str__(&self) -> String {
self.__repr__()
}
pub fn __repr__(&self) -> String {
format!("InvokeDirect({})", self.0.__str__())
}
/// Return all strings referenced in the handle.
pub fn get_all_strings(&self) -> HashSet<DexString> {
self.0.get_all_strings()
}
/// Return all types referenced in the handle.
pub fn get_all_types(&self) -> HashSet<IdType> {
self.0.get_all_types()
}
/// Return all prototypes referenced in the handle.
pub fn get_all_protos(&self) -> HashSet<IdMethodType> {
self.0.get_all_protos()
}
/// Return all field ids referenced in the handle.
pub fn get_all_field_ids(&self) -> HashSet<IdField> {
HashSet::new()
}
/// Return all method ids referenced in the handle.
pub fn get_all_method_ids(&self) -> HashSet<IdMethod> {
let mut methods = HashSet::new();
methods.insert(self.0.clone());
methods
}
/// Return all method handles referenced in the handle.
pub fn get_all_method_handles(&self) -> HashSet<MethodHandle> {
let mut methods = HashSet::new();
methods.insert(MethodHandle::InvokeDirect(self.clone()));
methods
}
}
#[pyclass]
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
pub struct InvokeInterface(pub IdMethod);
#[pymethods]
impl InvokeInterface {
pub fn to_json(&self) -> Result<String> {
Ok(serde_json::to_string(self)?)
}
#[staticmethod]
pub fn from_json(json: &str) -> Result<Self> {
Ok(serde_json::from_str(json)?)
}
#[new]
pub fn new(val: IdMethod) -> Self {
Self(val)
}
pub fn get_method(&self) -> IdMethod {
self.0.clone()
}
pub fn __str__(&self) -> String {
self.__repr__()
}
pub fn __repr__(&self) -> String {
format!("InvokeInterface({})", self.0.__str__())
}
/// Return all strings referenced in the handle.
pub fn get_all_strings(&self) -> HashSet<DexString> {
self.0.get_all_strings()
}
/// Return all types referenced in the handle.
pub fn get_all_types(&self) -> HashSet<IdType> {
self.0.get_all_types()
}
/// Return all prototypes referenced in the handle.
pub fn get_all_protos(&self) -> HashSet<IdMethodType> {
self.0.get_all_protos()
}
/// Return all field ids referenced in the handle.
pub fn get_all_field_ids(&self) -> HashSet<IdField> {
HashSet::new()
}
/// Return all method ids referenced in the handle.
pub fn get_all_method_ids(&self) -> HashSet<IdMethod> {
let mut methods = HashSet::new();
methods.insert(self.0.clone());
methods
}
/// Return all method handles referenced in the handle.
pub fn get_all_method_handles(&self) -> HashSet<MethodHandle> {
let mut methods = HashSet::new();
methods.insert(MethodHandle::InvokeInterface(self.clone()));
methods
}
}
impl<'source> FromPyObject<'source> for MethodHandle {
fn extract(ob: &'source PyAny) -> PyResult<Self> {
if let Ok(val) = StaticPut::extract(ob) {
Ok(Self::StaticPut(val))
} else if let Ok(val) = StaticGet::extract(ob) {
Ok(Self::StaticGet(val))
} else if let Ok(val) = InstancePut::extract(ob) {
Ok(Self::InstancePut(val))
} else if let Ok(val) = InstanceGet::extract(ob) {
Ok(Self::InstanceGet(val))
} else if let Ok(val) = InvokeStatic::extract(ob) {
Ok(Self::InvokeStatic(val))
} else if let Ok(val) = InvokeInstance::extract(ob) {
Ok(Self::InvokeInstance(val))
} else if let Ok(val) = InvokeConstructor::extract(ob) {
Ok(Self::InvokeConstructor(val))
} else if let Ok(val) = InvokeDirect::extract(ob) {
Ok(Self::InvokeDirect(val))
} else if let Ok(val) = InvokeInterface::extract(ob) {
Ok(Self::InvokeInterface(val))
} else {
Err(PyErr::new::<PyTypeError, _>(format!(
"{} is not a castable as a MethodHandle",
ob.repr()?
)))
}
}
}
impl IntoPy<PyObject> for MethodHandle {
fn into_py(self, py: Python<'_>) -> PyObject {
match self {
Self::StaticPut { field } => format!("StaticPut({})", field.__str__()),
Self::StaticGet { field } => format!("StaticGet({})", field.__str__()),
Self::InstancePut { field } => format!("InstancePut({})", field.__str__()),
Self::InstanceGet { field } => format!("InstanceGet({})", field.__str__()),
Self::InvokeStatic { method } => format!("InvokeStatic({})", method.__str__()),
Self::InvokeInstance { method } => format!("InvokeInstance({})", method.__str__()),
Self::InvokeConstructor { method } => {
format!("InvokeConstructor({})", method.__str__())
}
Self::InvokeDirect { method } => format!("InvokeDirect({})", method.__str__()),
Self::InvokeInterface { method } => format!("InvokeInterface({})", method.__str__()),
Self::StaticPut(val) => val.into_py(py),
Self::StaticGet(val) => val.into_py(py),
Self::InstancePut(val) => val.into_py(py),
Self::InstanceGet(val) => val.into_py(py),
Self::InvokeStatic(val) => val.into_py(py),
Self::InvokeInstance(val) => val.into_py(py),
Self::InvokeConstructor(val) => val.into_py(py),
Self::InvokeDirect(val) => val.into_py(py),
Self::InvokeInterface(val) => val.into_py(py),
}
}
}
impl MethodHandle {
// Not exposed to python, but meh, let's keep it coherent
pub fn __str__(&self) -> String {
match self {
Self::StaticPut(val) => val.__str__(),
Self::StaticGet(val) => val.__str__(),
Self::InstancePut(val) => val.__str__(),
Self::InstanceGet(val) => val.__str__(),
Self::InvokeStatic(val) => val.__str__(),
Self::InvokeInstance(val) => val.__str__(),
Self::InvokeConstructor(val) => val.__str__(),
Self::InvokeDirect(val) => val.__str__(),
Self::InvokeInterface(val) => val.__str__(),
}
}
// Not exposed to python, but meh, let's keep it coherent
pub fn __repr__(&self) -> String {
match self {
Self::StaticPut(val) => val.__repr__(),
Self::StaticGet(val) => val.__repr__(),
Self::InstancePut(val) => val.__repr__(),
Self::InstanceGet(val) => val.__repr__(),
Self::InvokeStatic(val) => val.__repr__(),
Self::InvokeInstance(val) => val.__repr__(),
Self::InvokeConstructor(val) => val.__str__(),
Self::InvokeDirect(val) => val.__repr__(),
Self::InvokeInterface(val) => val.__repr__(),
}
}
/// Return all strings referenced in the Handle.
pub fn get_all_strings(&self) -> HashSet<DexString> {
let mut visitor = StringCollector::default();
visitor.visit_method_handle(self).unwrap();
visitor.result()
match self {
Self::StaticPut(val) => val.get_all_strings(),
Self::StaticGet(val) => val.get_all_strings(),
Self::InstancePut(val) => val.get_all_strings(),
Self::InstanceGet(val) => val.get_all_strings(),
Self::InvokeStatic(val) => val.get_all_strings(),
Self::InvokeInstance(val) => val.get_all_strings(),
Self::InvokeConstructor(val) => val.get_all_strings(),
Self::InvokeDirect(val) => val.get_all_strings(),
Self::InvokeInterface(val) => val.get_all_strings(),
}
}
/// Return all types referenced in the Handle.
pub fn get_all_types(&self) -> HashSet<IdType> {
let mut visitor = TypeCollector::default();
visitor.visit_method_handle(self).unwrap();
visitor.result()
match self {
Self::StaticPut(val) => val.get_all_types(),
Self::StaticGet(val) => val.get_all_types(),
Self::InstancePut(val) => val.get_all_types(),
Self::InstanceGet(val) => val.get_all_types(),
Self::InvokeStatic(val) => val.get_all_types(),
Self::InvokeInstance(val) => val.get_all_types(),
Self::InvokeConstructor(val) => val.get_all_types(),
Self::InvokeDirect(val) => val.get_all_types(),
Self::InvokeInterface(val) => val.get_all_types(),
}
}
/// Return all prototypes referenced in the handle.
pub fn get_all_protos(&self) -> HashSet<IdMethodType> {
let mut visitor = MethodTypeCollector::default();
visitor.visit_method_handle(self).unwrap();
visitor.result()
match self {
Self::StaticPut(val) => val.get_all_protos(),
Self::StaticGet(val) => val.get_all_protos(),
Self::InstancePut(val) => val.get_all_protos(),
Self::InstanceGet(val) => val.get_all_protos(),
Self::InvokeStatic(val) => val.get_all_protos(),
Self::InvokeInstance(val) => val.get_all_protos(),
Self::InvokeConstructor(val) => val.get_all_protos(),
Self::InvokeDirect(val) => val.get_all_protos(),
Self::InvokeInterface(val) => val.get_all_protos(),
}
}
/// Return all field ids referenced in the handle.
pub fn get_all_field_ids(&self) -> HashSet<IdField> {
let mut visitor = FieldIdCollector::default();
visitor.visit_method_handle(self).unwrap();
visitor.result()
match self {
Self::StaticPut(val) => val.get_all_field_ids(),
Self::StaticGet(val) => val.get_all_field_ids(),
Self::InstancePut(val) => val.get_all_field_ids(),
Self::InstanceGet(val) => val.get_all_field_ids(),
Self::InvokeStatic(val) => val.get_all_field_ids(),
Self::InvokeInstance(val) => val.get_all_field_ids(),
Self::InvokeConstructor(val) => val.get_all_field_ids(),
Self::InvokeDirect(val) => val.get_all_field_ids(),
Self::InvokeInterface(val) => val.get_all_field_ids(),
}
}
/// Return all method ids referenced in the handle.
pub fn get_all_method_ids(&self) -> HashSet<IdMethod> {
let mut visitor = MethodIdCollector::default();
visitor.visit_method_handle(self).unwrap();
visitor.result()
match self {
Self::StaticPut(val) => val.get_all_method_ids(),
Self::StaticGet(val) => val.get_all_method_ids(),
Self::InstancePut(val) => val.get_all_method_ids(),
Self::InstanceGet(val) => val.get_all_method_ids(),
Self::InvokeStatic(val) => val.get_all_method_ids(),
Self::InvokeInstance(val) => val.get_all_method_ids(),
Self::InvokeConstructor(val) => val.get_all_method_ids(),
Self::InvokeDirect(val) => val.get_all_method_ids(),
Self::InvokeInterface(val) => val.get_all_method_ids(),
}
}
/// Return all method handles referenced in the handle.
pub fn get_all_method_handles(&self) -> HashSet<MethodHandle> {
let mut visitor = MethodHandleCollector::default();
visitor.visit_method_handle(self).unwrap();
visitor.result()
match self {
Self::StaticPut(val) => val.get_all_method_handles(),
Self::StaticGet(val) => val.get_all_method_handles(),
Self::InstancePut(val) => val.get_all_method_handles(),
Self::InstanceGet(val) => val.get_all_method_handles(),
Self::InvokeStatic(val) => val.get_all_method_handles(),
Self::InvokeInstance(val) => val.get_all_method_handles(),
Self::InvokeConstructor(val) => val.get_all_method_handles(),
Self::InvokeDirect(val) => val.get_all_method_handles(),
Self::InvokeInterface(val) => val.get_all_method_handles(),
}
}
}

View file

@ -1,18 +1,13 @@
//! Function that are can be usefull on the python side.
use pyo3::prelude::*;
use pyo3::types::{PyBytes, PyBytesMethods};
use pyo3::types::PyBytes;
use std::collections::{HashMap, HashSet};
use std::fs::File;
use std::io::{Cursor, Seek, SeekFrom};
use std::io::Cursor;
use std::path::PathBuf;
use crate::{Apk, IdType, Result};
use androscalpel_serializer::{
DexFileReader, HeaderItem, Serializable, Sleb128, Uleb128, Uleb128p1,
};
use apk_frauder::{ZipFileReader, end_of_central_directory::EndCentralDirectory};
use crate::Result;
use androscalpel_serializer::{Serializable, Sleb128, Uleb128, Uleb128p1};
/// Convert an integer to the uleb128 byte encoding
#[pyfunction]
@ -49,32 +44,6 @@ pub fn sleb128_to_int(b: &[u8]) -> Result<i32> {
Ok(Sleb128::deserialize_from_slice(b)?.0)
}
// TODO: list_defined_classes, is_dex, is_zip take only &[u8] or file, but should allow to also read from both
/// List all classes defined in a dex file.
#[pyfunction]
pub fn list_defined_classes(dex: &[u8]) -> Result<HashSet<IdType>> {
let dex = DexFileReader::new(dex)?;
dex.get_class_defs()
.iter()
.map(|cdef| Apk::get_id_type_from_idx(cdef.class_idx as usize, &dex))
.collect()
}
/// Test if a file is as .dex file an return the dex version if it is, else return None.
#[pyfunction]
pub fn is_dex(file: PathBuf) -> Result<Option<usize>> {
let mut file = File::open(file)?;
crate::utils::is_dex(file)
}
/// Test if a file is a zip file.
#[pyfunction]
pub fn is_zip(file: PathBuf) -> Result<bool> {
let mut file = File::open(file)?;
crate::utils::is_zip(file)
}
/// Replace the dex files a an apk an resigned the apk.
///
/// # Warning
@ -82,69 +51,28 @@ pub fn is_zip(file: PathBuf) -> Result<bool> {
/// For now, only jks keystore are allowed.
///
/// The `zipalign` and `apksigner` args allow to use a specific version of the
/// toimpl Read + Seekols instead of the one in the PATH (if it even exist)
///
/// `additionnal_files` is a dict of file to add, modify or remove in the apk.
/// The keys are the file names and the values are `None` to remove the file, or
/// `bytes` for the content of the file.
/// tools instead of the one in the PATH (if it even exist)
#[pyfunction]
#[pyo3(signature = (
apk,
dst,
dexfiles,
keystore,
zipalign=None,
apksigner=None,
additionnal_files=None
))]
pub fn replace_dex(
apk: PathBuf,
dst: PathBuf,
dexfiles: Vec<Bound<'_, PyBytes>>,
dexfiles: Vec<&[u8]>,
keystore: PathBuf,
zipalign: Option<PathBuf>,
apksigner: Option<PathBuf>,
additionnal_files: Option<HashMap<String, Option<Bound<'_, PyBytes>>>>,
) {
let mut dexfiles: Vec<_> = dexfiles
.iter()
.map(PyBytesMethods::as_bytes)
.map(Cursor::new)
.collect();
let additionnal_files: Option<HashMap<_, _>> =
additionnal_files.as_ref().map(|additionnal_files| {
additionnal_files
.iter()
.map(|(k, v)| {
(
k.clone(),
v.as_ref().map(|bytes| bytes.as_bytes()).map(Cursor::new),
)
})
.collect()
});
apk_frauder::replace_dex(
apk,
dst,
&mut dexfiles,
keystore,
zipalign,
apksigner,
additionnal_files,
)
let mut dexfiles: Vec<_> = dexfiles.iter().map(Cursor::new).collect();
apk_frauder::replace_dex(apk, dst, &mut dexfiles, keystore, zipalign, apksigner)
}
/// export the function in a python module
pub(crate) fn export_module(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> {
pub(crate) fn export_module(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(int_to_uleb128, m)?)?;
m.add_function(wrap_pyfunction!(int_to_uleb128p1, m)?)?;
m.add_function(wrap_pyfunction!(int_to_sleb128, m)?)?;
m.add_function(wrap_pyfunction!(uleb128_to_int, m)?)?;
m.add_function(wrap_pyfunction!(uleb128p1_to_int, m)?)?;
m.add_function(wrap_pyfunction!(sleb128_to_int, m)?)?;
m.add_function(wrap_pyfunction!(list_defined_classes, m)?)?;
m.add_function(wrap_pyfunction!(is_dex, m)?)?;
m.add_function(wrap_pyfunction!(is_zip, m)?)?;
m.add_function(wrap_pyfunction!(replace_dex, m)?)?;
Ok(())
}

View file

@ -4,29 +4,24 @@ use crate::Result;
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
use crate::{
DexString, DexValue, IdField, IdMethod, IdMethodType, IdType, MethodHandle, Visitable,
VisitableMut, Visitor, VisitorMut,
};
#[cfg(feature = "python")]
use crate::{DexString, DexValue, IdField, IdMethod, IdMethodType, IdType, MethodHandle};
use pyo3::prelude::*;
#[cfg_attr(feature = "python", pyclass(eq))]
#[pyclass]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
pub struct DexByte(pub i8);
#[cfg_attr(feature = "python", pymethods)]
#[pymethods]
impl DexByte {
pub fn to_json(&self) -> Result<String> {
Ok(serde_json::to_string(self)?)
}
#[cfg_attr(feature = "python", staticmethod)]
#[staticmethod]
pub fn from_json(json: &str) -> Result<Self> {
Ok(serde_json::from_str(json)?)
}
#[cfg_attr(feature = "python", new)]
#[new]
pub fn new(val: i8) -> Self {
Self(val)
}
@ -44,21 +39,21 @@ impl DexByte {
}
}
#[cfg_attr(feature = "python", pyclass(eq))]
#[pyclass]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
pub struct DexShort(pub i16);
#[cfg_attr(feature = "python", pymethods)]
#[pymethods]
impl DexShort {
pub fn to_json(&self) -> Result<String> {
Ok(serde_json::to_string(self)?)
}
#[cfg_attr(feature = "python", staticmethod)]
#[staticmethod]
pub fn from_json(json: &str) -> Result<Self> {
Ok(serde_json::from_str(json)?)
}
#[cfg_attr(feature = "python", new)]
#[new]
pub fn new(val: i16) -> Self {
Self(val)
}
@ -76,21 +71,21 @@ impl DexShort {
}
}
#[cfg_attr(feature = "python", pyclass(eq))]
#[pyclass]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
pub struct DexChar(pub u16);
#[cfg_attr(feature = "python", pymethods)]
#[pymethods]
impl DexChar {
pub fn to_json(&self) -> Result<String> {
Ok(serde_json::to_string(self)?)
}
#[cfg_attr(feature = "python", staticmethod)]
#[staticmethod]
pub fn from_json(json: &str) -> Result<Self> {
Ok(serde_json::from_str(json)?)
}
#[cfg_attr(feature = "python", new)]
#[new]
pub fn new(val: u16) -> Self {
Self(val)
}
@ -108,21 +103,21 @@ impl DexChar {
}
}
#[cfg_attr(feature = "python", pyclass(eq))]
#[pyclass]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
pub struct DexInt(pub i32);
#[cfg_attr(feature = "python", pymethods)]
#[pymethods]
impl DexInt {
pub fn to_json(&self) -> Result<String> {
Ok(serde_json::to_string(self)?)
}
#[cfg_attr(feature = "python", staticmethod)]
#[staticmethod]
pub fn from_json(json: &str) -> Result<Self> {
Ok(serde_json::from_str(json)?)
}
#[cfg_attr(feature = "python", new)]
#[new]
pub fn new(val: i32) -> Self {
Self(val)
}
@ -140,21 +135,21 @@ impl DexInt {
}
}
#[cfg_attr(feature = "python", pyclass(eq))]
#[pyclass]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
pub struct DexLong(pub i64);
#[cfg_attr(feature = "python", pymethods)]
#[pymethods]
impl DexLong {
pub fn to_json(&self) -> Result<String> {
Ok(serde_json::to_string(self)?)
}
#[cfg_attr(feature = "python", staticmethod)]
#[staticmethod]
pub fn from_json(json: &str) -> Result<Self> {
Ok(serde_json::from_str(json)?)
}
#[cfg_attr(feature = "python", new)]
#[new]
pub fn new(val: i64) -> Self {
Self(val)
}
@ -172,21 +167,21 @@ impl DexLong {
}
}
#[cfg_attr(feature = "python", pyclass(eq))]
#[pyclass]
#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize)]
pub struct DexFloat(pub f32);
#[cfg_attr(feature = "python", pymethods)]
#[pymethods]
impl DexFloat {
pub fn to_json(&self) -> Result<String> {
Ok(serde_json::to_string(self)?)
}
#[cfg_attr(feature = "python", staticmethod)]
#[staticmethod]
pub fn from_json(json: &str) -> Result<Self> {
Ok(serde_json::from_str(json)?)
}
#[cfg_attr(feature = "python", new)]
#[new]
pub fn new(val: f32) -> Self {
Self(val)
}
@ -204,21 +199,21 @@ impl DexFloat {
}
}
#[cfg_attr(feature = "python", pyclass(eq))]
#[pyclass]
#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize)]
pub struct DexDouble(pub f64);
#[cfg_attr(feature = "python", pymethods)]
#[pymethods]
impl DexDouble {
pub fn to_json(&self) -> Result<String> {
Ok(serde_json::to_string(self)?)
}
#[cfg_attr(feature = "python", staticmethod)]
#[staticmethod]
pub fn from_json(json: &str) -> Result<Self> {
Ok(serde_json::from_str(json)?)
}
#[cfg_attr(feature = "python", new)]
#[new]
pub fn new(val: f64) -> Self {
Self(val)
}
@ -237,21 +232,21 @@ impl DexDouble {
}
/* DexString is already define in lib.rs, TODO: move the version in lib.rs here
#[cfg_attr(feature = "python", pyclass(eq))]
#[pyclass]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DexString(pub u32);
#[cfg_attr(feature = "python", pymethods)]
#[pymethods]
impl DexString {
pub fn to_json(&self) -> Result<String> {
Ok(serde_json::to_string(self)?)
}
#[cfg_attr(feature = "python", staticmethod)]
#[staticmethod]
pub fn from_json(json: &str) -> Result<Self> {
Ok(serde_json::from_str(json)?)
}
#[cfg_attr(feature = "python", new)]
#[new]
pub fn new(val: u32) -> Self {
Self(val)
}
@ -270,21 +265,21 @@ impl DexString {
}
*/
#[cfg_attr(feature = "python", pyclass(eq))]
#[pyclass]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
pub struct DexNull;
#[cfg_attr(feature = "python", pymethods)]
#[pymethods]
impl DexNull {
pub fn to_json(&self) -> Result<String> {
Ok(serde_json::to_string(self)?)
}
#[cfg_attr(feature = "python", staticmethod)]
#[staticmethod]
pub fn from_json(json: &str) -> Result<Self> {
Ok(serde_json::from_str(json)?)
}
#[cfg_attr(feature = "python", new)]
#[new]
pub fn _new() -> Self {
Self
}
@ -298,21 +293,21 @@ impl DexNull {
}
}
#[cfg_attr(feature = "python", pyclass(eq))]
#[pyclass]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
pub struct DexBoolean(pub bool);
#[cfg_attr(feature = "python", pymethods)]
#[pymethods]
impl DexBoolean {
pub fn to_json(&self) -> Result<String> {
Ok(serde_json::to_string(self)?)
}
#[cfg_attr(feature = "python", staticmethod)]
#[staticmethod]
pub fn from_json(json: &str) -> Result<Self> {
Ok(serde_json::from_str(json)?)
}
#[cfg_attr(feature = "python", new)]
#[new]
pub fn new(val: bool) -> Self {
Self(val)
}
@ -330,21 +325,21 @@ impl DexBoolean {
}
}
#[cfg_attr(feature = "python", pyclass(eq))]
#[pyclass]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct DexArray(pub Vec<DexValue>);
#[cfg_attr(feature = "python", pymethods)]
#[pymethods]
impl DexArray {
pub fn to_json(&self) -> Result<String> {
Ok(serde_json::to_string(self)?)
}
#[cfg_attr(feature = "python", staticmethod)]
#[staticmethod]
pub fn from_json(json: &str) -> Result<Self> {
Ok(serde_json::from_str(json)?)
}
#[cfg_attr(feature = "python", new)]
#[new]
pub fn _new(arr: Vec<DexValue>) -> Self {
Self(arr)
}
@ -422,23 +417,3 @@ impl DexArray {
methods
}
}
impl<V: Visitor> Visitable<V> for DexArray {
fn default_visit(&self, v: &mut V) -> Result<()> {
for val in &self.0 {
v.visit_value(val)?;
}
Ok(())
}
}
impl<V: VisitorMut> VisitableMut<V> for DexArray {
fn default_visit_mut(self, v: &mut V) -> Result<Self> {
Ok(Self(
self.0
.into_iter()
.map(|val| v.visit_value(val))
.collect::<Result<_>>()?,
))
}
}

View file

@ -1,103 +0,0 @@
#!/usr/bin/python3
# require zip and apktool installed
import sys
import os
import tempfile
from pathlib import Path
import json
def parse_smali(path: Path) -> tuple[str, dict, dict]:
class_name: str | None = None
fields = {}
methods = {}
with path.open() as file:
for line in file.readlines():
if line.startswith(".class"):
if class_name is None:
class_name = line.strip().split(" ")[-1]
else:
raise RuntimeError(f"Two classes found in {path}")
if line.startswith(".field"):
k, v = parse_field(line)
fields[k] = v
elif line.startswith(".method"):
k, v = parse_method(line)
methods[k] = v
if class_name is None:
raise RuntimeError(f"No classe found in {path}")
return (class_name, fields, methods)
def parse_field(line: str) -> tuple[str, dict]:
data: dict[str, None | str | list[str]] = {
"value": None,
"other": [],
"hiddenapi": None,
"hiddenapi_domain": None,
}
if "=" in line:
line, val = line.split("=")[0], "=".join(line.split("=")[1:])
data["value"] = val.strip()
line, ty = line.split(":")
vals = list(map(str.strip, line.split(" ")))
name = f"{vals[-1]}:{ty.strip()}"
vals = vals[:-1]
if vals:
if "api" in vals[-1]:
data["hiddenapi_domain"] = vals[-1]
vals = vals[:-1]
if vals:
if "list" in vals[-1]:
data["hiddenapi"] = vals[-1]
vals = vals[:-1]
data["other"] = vals
return name, data
def parse_method(line: str) -> tuple[str, dict]:
data: dict[str, None | str | list[str]] = {
"other": [],
"hiddenapi": None,
"hiddenapi_domain": None,
}
vals = list(map(str.strip, line.split(" ")))
name = vals[-1]
vals = vals[:-1]
if vals:
if "api" in vals[-1]:
data["hiddenapi_domain"] = vals[-1]
vals = vals[:-1]
if vals:
if "list" in vals[-1]:
data["hiddenapi"] = vals[-1]
vals = vals[:-1]
data["other"] = vals
return name, data
def main():
if len(sys.argv) != 3:
print("rought smali parser")
print("usage:")
print(f" {sys.argv[0]} some_classes.dex output.json")
exit()
file = sys.argv[1]
dst = sys.argv[2]
data = {}
with tempfile.TemporaryDirectory() as tmp:
os.system(f"zip {tmp}/apk.apk {file}")
os.system(f"apktool d -o {tmp}/apktool.out {tmp}/apk.apk")
smalidir = Path(f"{tmp}/apktool.out/smali_{file.removesuffix('.dex')}")
for root, dirs, files in smalidir.walk():
for file in files:
class_name, fields, methods = parse_smali(root / file)
data[class_name] = {"fields": fields, "methods": methods}
with open(dst, "w") as file:
json.dump(data, file)
if __name__ == "__main__":
main()

View file

@ -1,676 +0,0 @@
{
"dex_files": {
"classes.dex": {
"classes": {
"Lcom/example/testclassloader/TestA;": {
"descriptor": "Lcom/example/testclassloader/TestA;",
"is_public": true,
"is_final": false,
"is_interface": false,
"is_abstract": false,
"is_synthetic": false,
"is_annotation": false,
"is_enum": false,
"superclass": "Ljava/lang/Object;",
"interfaces": [],
"source_file": {
"String": "TestA.java"
},
"static_fields": {
"Lcom/example/testclassloader/TestA;->value:Ljava/lang/String;": {
"descriptor": "Lcom/example/testclassloader/TestA;->value:Ljava/lang/String;",
"visibility": "Public",
"is_static": true,
"is_final": false,
"is_volatile": false,
"is_transient": false,
"is_synthetic": false,
"is_enum": false,
"value": null,
"annotations": []
}
},
"instance_fields": {},
"direct_methods": {
"Lcom/example/testclassloader/TestA;-><init>()V": {
"descriptor": "Lcom/example/testclassloader/TestA;-><init>()V",
"visibility": "Public",
"is_static": false,
"is_final": false,
"is_synchronized": false,
"is_bridge": false,
"is_varargs": false,
"is_native": false,
"is_abstract": false,
"is_strictfp": false,
"is_synthetic": false,
"is_constructor": true,
"is_declared_syncrhonized": false,
"annotations": [],
"parameters_annotations": [],
"code": {
"registers_size": 1,
"ins_size": 1,
"outs_size": 1,
"debug_info": [
3,
[
14,
0
]
],
"parameter_names": [],
"insns": [
{
"Label": {
"name": "label_00000000"
}
},
{
"InvokeDirect": {
"method": "Ljava/lang/Object;-><init>()V",
"args": [
0
]
}
},
{
"Label": {
"name": "label_00000003"
}
},
{
"ReturnVoid": {}
}
]
}
},
"Lcom/example/testclassloader/TestA;-><clinit>()V": {
"descriptor": "Lcom/example/testclassloader/TestA;-><clinit>()V",
"visibility": "None_",
"is_static": true,
"is_final": false,
"is_synchronized": false,
"is_bridge": false,
"is_varargs": false,
"is_native": false,
"is_abstract": false,
"is_strictfp": false,
"is_synthetic": false,
"is_constructor": true,
"is_declared_syncrhonized": false,
"annotations": [],
"parameters_annotations": [],
"code": {
"registers_size": 1,
"ins_size": 0,
"outs_size": 0,
"debug_info": [
5,
[
14,
0
]
],
"parameter_names": [],
"insns": [
{
"Label": {
"name": "label_00000000"
}
},
{
"ConstString": {
"reg": 0,
"lit": {
"String": "Pirat\\')"
}
}
},
{
"Label": {
"name": "label_00000002"
}
},
{
"SPutObject": {
"from": 0,
"field": "Lcom/example/testclassloader/TestA;->value:Ljava/lang/String;"
}
},
{
"Label": {
"name": "label_00000004"
}
},
{
"ReturnVoid": {}
},
{
"Label": {
"name": "label_00000005"
}
},
{
"Nop": {}
}
]
}
}
},
"virtual_methods": {
"Lcom/example/testclassloader/TestA;->get_value()Ljava/lang/String;": {
"descriptor": "Lcom/example/testclassloader/TestA;->get_value()Ljava/lang/String;",
"visibility": "Public",
"is_static": false,
"is_final": false,
"is_synchronized": false,
"is_bridge": false,
"is_varargs": false,
"is_native": false,
"is_abstract": false,
"is_strictfp": false,
"is_synthetic": false,
"is_constructor": false,
"is_declared_syncrhonized": false,
"annotations": [],
"parameters_annotations": [],
"code": {
"registers_size": 2,
"ins_size": 1,
"outs_size": 0,
"debug_info": [
11,
[
14,
0
]
],
"parameter_names": [],
"insns": [
{
"Label": {
"name": "label_00000000"
}
},
{
"SGetObject": {
"to": 0,
"field": "Lcom/example/testclassloader/TestA;->value:Ljava/lang/String;"
}
},
{
"Label": {
"name": "label_00000002"
}
},
{
"ReturnObject": {
"reg": 0
}
},
{
"Label": {
"name": "label_00000003"
}
},
{
"Nop": {}
}
]
}
},
"Lcom/example/testclassloader/TestA;->get_cl()Ljava/lang/String;": {
"descriptor": "Lcom/example/testclassloader/TestA;->get_cl()Ljava/lang/String;",
"visibility": "Public",
"is_static": false,
"is_final": false,
"is_synchronized": false,
"is_bridge": false,
"is_varargs": false,
"is_native": false,
"is_abstract": false,
"is_strictfp": false,
"is_synthetic": false,
"is_constructor": false,
"is_declared_syncrhonized": false,
"annotations": [],
"parameters_annotations": [],
"code": {
"registers_size": 2,
"ins_size": 1,
"outs_size": 1,
"debug_info": [
7,
[
14,
0
]
],
"parameter_names": [],
"insns": [
{
"Label": {
"name": "label_00000000"
}
},
{
"InvokeVirtual": {
"method": "Ljava/lang/Object;->getClass()Ljava/lang/Class;",
"args": [
1
]
}
},
{
"Label": {
"name": "label_00000003"
}
},
{
"MoveResultObject": {
"to": 0
}
},
{
"Label": {
"name": "label_00000004"
}
},
{
"InvokeVirtual": {
"method": "Ljava/lang/Class;->getClassLoader()Ljava/lang/ClassLoader;",
"args": [
0
]
}
},
{
"Label": {
"name": "label_00000007"
}
},
{
"MoveResultObject": {
"to": 0
}
},
{
"Label": {
"name": "label_00000008"
}
},
{
"InvokeVirtual": {
"method": "Ljava/lang/Object;->toString()Ljava/lang/String;",
"args": [
0
]
}
},
{
"Label": {
"name": "label_0000000B"
}
},
{
"MoveResultObject": {
"to": 0
}
},
{
"Label": {
"name": "label_0000000C"
}
},
{
"ReturnObject": {
"reg": 0
}
},
{
"Label": {
"name": "label_0000000D"
}
},
{
"Nop": {}
}
]
}
}
},
"annotations": []
},
"Lcom/example/testclassloader/TestB;": {
"descriptor": "Lcom/example/testclassloader/TestB;",
"is_public": true,
"is_final": false,
"is_interface": false,
"is_abstract": false,
"is_synthetic": false,
"is_annotation": false,
"is_enum": false,
"superclass": "Ljava/lang/Object;",
"interfaces": [],
"source_file": {
"String": "TestB.java"
},
"static_fields": {},
"instance_fields": {},
"direct_methods": {
"Lcom/example/testclassloader/TestB;-><init>()V": {
"descriptor": "Lcom/example/testclassloader/TestB;-><init>()V",
"visibility": "Public",
"is_static": false,
"is_final": false,
"is_synchronized": false,
"is_bridge": false,
"is_varargs": false,
"is_native": false,
"is_abstract": false,
"is_strictfp": false,
"is_synthetic": false,
"is_constructor": true,
"is_declared_syncrhonized": false,
"annotations": [],
"parameters_annotations": [],
"code": {
"registers_size": 1,
"ins_size": 1,
"outs_size": 1,
"debug_info": [
3,
[
14,
0
]
],
"parameter_names": [],
"insns": [
{
"Label": {
"name": "label_00000000"
}
},
{
"InvokeDirect": {
"method": "Ljava/lang/Object;-><init>()V",
"args": [
0
]
}
},
{
"Label": {
"name": "label_00000003"
}
},
{
"ReturnVoid": {}
}
]
}
},
"Lcom/example/testclassloader/TestB;-><clinit>()V": {
"descriptor": "Lcom/example/testclassloader/TestB;-><clinit>()V",
"visibility": "None_",
"is_static": true,
"is_final": false,
"is_synchronized": false,
"is_bridge": false,
"is_varargs": false,
"is_native": false,
"is_abstract": false,
"is_strictfp": false,
"is_synthetic": false,
"is_constructor": true,
"is_declared_syncrhonized": false,
"annotations": [],
"parameters_annotations": [],
"code": {
"registers_size": 1,
"ins_size": 0,
"outs_size": 0,
"debug_info": [
5,
[
14,
0
]
],
"parameter_names": [],
"insns": [
{
"Label": {
"name": "label_00000000"
}
},
{
"ConstString": {
"reg": 0,
"lit": {
"String": "Plopliplop"
}
}
},
{
"Label": {
"name": "label_00000002"
}
},
{
"SPutObject": {
"from": 0,
"field": "Lcom/example/testclassloader/TestB;->value:Ljava/lang/String;"
}
},
{
"Label": {
"name": "label_00000004"
}
},
{
"ReturnVoid": {}
},
{
"Label": {
"name": "label_00000005"
}
},
{
"Nop": {}
}
]
}
}
},
"virtual_methods": {
"Lcom/example/testclassloader/TestB;->get_cl()Ljava/lang/String;": {
"descriptor": "Lcom/example/testclassloader/TestB;->get_cl()Ljava/lang/String;",
"visibility": "Public",
"is_static": false,
"is_final": false,
"is_synchronized": false,
"is_bridge": false,
"is_varargs": false,
"is_native": false,
"is_abstract": false,
"is_strictfp": false,
"is_synthetic": false,
"is_constructor": false,
"is_declared_syncrhonized": false,
"annotations": [],
"parameters_annotations": [],
"code": {
"registers_size": 2,
"ins_size": 1,
"outs_size": 1,
"debug_info": [
7,
[
14,
0
]
],
"parameter_names": [],
"insns": [
{
"Label": {
"name": "label_00000000"
}
},
{
"InvokeVirtual": {
"method": "Ljava/lang/Object;->getClass()Ljava/lang/Class;",
"args": [
1
]
}
},
{
"Label": {
"name": "label_00000003"
}
},
{
"MoveResultObject": {
"to": 0
}
},
{
"Label": {
"name": "label_00000004"
}
},
{
"InvokeVirtual": {
"method": "Ljava/lang/Class;->getClassLoader()Ljava/lang/ClassLoader;",
"args": [
0
]
}
},
{
"Label": {
"name": "label_00000007"
}
},
{
"MoveResultObject": {
"to": 0
}
},
{
"Label": {
"name": "label_00000008"
}
},
{
"InvokeVirtual": {
"method": "Ljava/lang/Object;->toString()Ljava/lang/String;",
"args": [
0
]
}
},
{
"Label": {
"name": "label_0000000B"
}
},
{
"MoveResultObject": {
"to": 0
}
},
{
"Label": {
"name": "label_0000000C"
}
},
{
"ReturnObject": {
"reg": 0
}
},
{
"Label": {
"name": "label_0000000D"
}
},
{
"Nop": {}
}
]
}
},
"Lcom/example/testclassloader/TestB;->get_value()Ljava/lang/String;": {
"descriptor": "Lcom/example/testclassloader/TestB;->get_value()Ljava/lang/String;",
"visibility": "Public",
"is_static": false,
"is_final": false,
"is_synchronized": false,
"is_bridge": false,
"is_varargs": false,
"is_native": false,
"is_abstract": false,
"is_strictfp": false,
"is_synthetic": false,
"is_constructor": false,
"is_declared_syncrhonized": false,
"annotations": [],
"parameters_annotations": [],
"code": {
"registers_size": 2,
"ins_size": 1,
"outs_size": 0,
"debug_info": [
11,
[
14,
0
]
],
"parameter_names": [],
"insns": [
{
"Label": {
"name": "label_00000000"
}
},
{
"SGetObject": {
"to": 0,
"field": "Lcom/example/testclassloader/TestA;->value:Ljava/lang/String;"
}
},
{
"Label": {
"name": "label_00000002"
}
},
{
"ReturnObject": {
"reg": 0
}
},
{
"Label": {
"name": "label_00000003"
}
},
{
"Nop": {}
}
]
}
}
},
"annotations": []
}
},
"not_referenced_strings": []
}
}
}

File diff suppressed because one or more lines are too long

View file

@ -1,11 +1,9 @@
use super::*;
use androscalpel_serializer::Instruction as InsFormat;
use androscalpel_serializer::*;
use serde_json as sj;
use std::collections::HashSet;
use std::fs::File;
use std::io;
use std::io::{Read, Write};
use std::io::Write;
use std::ops::Deref;
use std::sync::{Mutex, OnceLock};
use std::time::Instant;
@ -13,14 +11,13 @@ use std::time::Instant;
fn write_to_report(data: &str) {
static REPORT_FILE: Mutex<Option<File>> = Mutex::new(None);
let mut report_file = REPORT_FILE.lock().unwrap();
let mut report_file = match report_file.deref() {
Some(report_file) => report_file,
_ => {
*report_file = Some(
File::create(&format!("{}/test_repport.txt", env!("CARGO_MANIFEST_DIR"),)).unwrap(),
);
report_file.deref().as_ref().unwrap()
}
let mut report_file = if let Some(report_file) = report_file.deref() {
report_file
} else {
*report_file = Some(
File::create(&format!("{}/test_repport.txt", env!("CARGO_MANIFEST_DIR"),)).unwrap(),
);
report_file.deref().as_ref().unwrap()
};
writeln!(report_file, "{data}").unwrap();
}
@ -43,8 +40,7 @@ fn get_hello_world_apk() -> &'static Apk {
HELLO_WORLD_APK.get_or_init(|| {
let mut apk = Apk::new();
let start = Instant::now();
apk.add_dex_file("classes.dex", get_hello_world_dex(), |_, _, _| None, false)
.unwrap();
apk.add_dex_file(get_hello_world_dex()).unwrap();
let duration = start.elapsed();
write_to_report(&format!("Parsing classes_hello_world.dex: {duration:?}"));
apk
@ -55,11 +51,7 @@ fn get_hello_world_recompilled() -> &'static [u8] {
static HELLO_WORLD_RECOMP: OnceLock<Vec<u8>> = OnceLock::new();
HELLO_WORLD_RECOMP.get_or_init(|| {
let start = Instant::now();
let dex = get_hello_world_apk()
.gen_raw_dex()
.unwrap()
.remove("classes.dex")
.unwrap();
let dex = get_hello_world_apk().gen_raw_dex().unwrap().pop().unwrap();
let duration = start.elapsed();
write_to_report(&format!("Recompile classes_hello_world.dex: {duration:?}"));
dex
@ -85,7 +77,7 @@ fn get_class_dex<'a, 'b>(name: &'a str, dex: &'b DexFileReader) -> Option<&'b Cl
fn get_method_code_dex(name: &str, dex: &DexFileReader) -> Option<CodeItem> {
let method = IdMethod::from_smali(name).unwrap();
let class_name: String = (&method.class_.0).try_into().unwrap();
let class_name: String = (&method.class_.0).into();
let class = get_class_dex(&class_name, dex);
let class = if let Some(class) = class {
class
@ -142,30 +134,7 @@ fn test_generated_data_size() {
fn test_generated_apk_equivalence() {
let new_dex = get_hello_world_recompilled();
let mut new_apk = Apk::new();
new_apk
.add_dex_file("classes.dex", &new_dex, |_, _, _| None, false)
.unwrap();
/*
use pretty_assertions::assert_eq;
let method = IdMethod::from_smali(
"Lcom/google/android/material/datepicker/DateFormatTextWatcher;->lambda$new$0$com-google-android-material-datepicker-DateFormatTextWatcher(Ljava/lang/String;)V"
)
.unwrap();
assert_eq!(
get_hello_world_apk()
.get_class(&method.class_)
.unwrap()
.virtual_methods
.get(&method)
.unwrap(),
new_apk
.get_class(&method.class_)
.unwrap()
.virtual_methods
.get(&method)
.unwrap()
);*/
new_apk.add_dex_file(&new_dex).unwrap();
assert_eq!(get_hello_world_apk(), &new_apk);
}
@ -383,344 +352,3 @@ fn test_sort_strings() {
assert_eq!(list1, list2);
assert_eq!(list1, vec![s1.clone(), s2.clone()]);
}
/// Emulate test https://cs.android.com/android/platform/superproject/main/+/main:art/libdexfile/dex/dex_file_verifier.cc;drc=e8da7cd1d0e7d3535c82f8d05adcef3edd43cd40;l=581
fn check_valid_offset_and_size(
dex: &DexFileReader,
offset: u32,
size: u32,
alignment: u32,
label: &str,
) {
if size == 0 {
if offset != 0 {
panic!("Offset 0x{offset:x} should be zero when size is zero for {label}");
}
return;
}
// offset < hdr_offset is not relevent (we index from hdr_offset=0)
let file_size = dex.get_header().file_size;
if file_size <= offset {
panic!("Offset 0x{offset:x} sould be within file size 0x{file_size:x} for {label}");
}
if file_size - offset < size {
// size + offset could overflow
panic!(
"Section end 0x{:x} should be within file size 0x{file_size:x} for {label}",
size + offset
);
}
if alignment != 0 && !(offset & (alignment - 1) == 0) {
panic!("Offset 0x{offset:x} sould be aligned by {alignment} for {label}");
}
}
#[test]
fn test_1_from_json() {
let filename = "test_class1.json";
let hello_world_dex = format!("{}/src/tests/{}", env!("CARGO_MANIFEST_DIR"), filename);
let mut file = File::open(&hello_world_dex).expect(&format!("{} not found", filename));
let mut json = String::new();
file.read_to_string(&mut json).unwrap();
let test_a: Class = serde_json::from_str(&json).unwrap();
let mut apk = Apk::new();
apk.add_class("classes.dex", test_a).unwrap();
let dex = apk.gen_raw_dex().unwrap().remove("classes.dex").unwrap();
let dex = DexFileReader::new(&dex).unwrap();
check_valid_offset_and_size(
&dex,
dex.get_header().link_off,
dex.get_header().link_size,
0,
"link",
);
check_valid_offset_and_size(&dex, dex.get_header().map_off, 4, 4, "map");
check_valid_offset_and_size(
&dex,
dex.get_header().string_ids_off,
dex.get_header().string_ids_size,
4,
"string-ids",
);
check_valid_offset_and_size(
&dex,
dex.get_header().type_ids_off,
dex.get_header().type_ids_size,
4,
"type-ids",
);
check_valid_offset_and_size(
&dex,
dex.get_header().proto_ids_off,
dex.get_header().proto_ids_size,
4,
"proto-ids",
);
check_valid_offset_and_size(
&dex,
dex.get_header().field_ids_off,
dex.get_header().field_ids_size,
4,
"field-ids",
);
check_valid_offset_and_size(
&dex,
dex.get_header().method_ids_off,
dex.get_header().method_ids_size,
4,
"method-ids",
);
check_valid_offset_and_size(
&dex,
dex.get_header().class_defs_off,
dex.get_header().class_defs_size,
4,
"class-defs",
);
check_valid_offset_and_size(
&dex,
dex.get_header().data_off,
dex.get_header().data_size,
0,
"data",
);
if dex.get_header().type_ids_size > (u16::MAX as u32) {
panic!(
"Size 0x{:x} should not exceed limit 0x{:x} for type-ids",
dex.get_header().type_ids_size,
u16::MAX
)
}
if dex.get_header().proto_ids_size > (u16::MAX as u32) {
panic!(
"Size 0x{:x} should not exceed limit 0x{:x} for proto-ids",
dex.get_header().proto_ids_size,
u16::MAX
)
}
let map = dex.get_map_list();
let mut last_offset = 0;
let mut last_type = MapItemType::UnkownType(0);
let mut data_item_count = 0;
let mut data_items_left = dex.get_header().data_size;
let file_size = dex.get_header().file_size;
let mut used_types = HashSet::new();
for (i, item) in map.list.iter().enumerate() {
if last_offset >= item.offset && i != 0 {
panic!(
"Out of order map item: 0x{:x} then 0x{:x} for type {:?} (last type was {:?})",
last_offset, item.offset, item.type_, last_type
)
}
if item.offset >= file_size {
panic!(
"Map item for type {:?} ends after file: 0x{:x} >= 0x{:x}",
item.type_, item.offset, file_size
);
}
if item.type_.is_data_section_type() {
if item.size > data_items_left {
panic!(
"Too many items in data section: {} > {} (item type: {:?}",
item.size + data_item_count,
dex.get_header().data_size,
item.type_
);
}
data_items_left -= item.size;
data_item_count += item.size;
}
if used_types.contains(&item.type_) {
panic!("Duplicate map section of type {:?}", item.type_);
}
used_types.insert(item.type_);
last_offset = item.offset;
last_type = item.type_;
}
if !used_types.contains(&MapItemType::HeaderItem) {
panic!("Map is missing Header entry");
}
if !used_types.contains(&MapItemType::MapList) {
panic!("Map is missing Map List entry");
}
if !used_types.contains(&MapItemType::StringIdItem) && (dex.get_header().string_ids_off != 0) {
panic!("Map is missing String Id entry");
}
if !used_types.contains(&MapItemType::TypeIdItem) && (dex.get_header().type_ids_off != 0) {
panic!("Map is missing Type Id entry");
}
if !used_types.contains(&MapItemType::ProtoIdItem) && (dex.get_header().proto_ids_off != 0) {
panic!("Map is missing Proto Id entry");
}
if !used_types.contains(&MapItemType::FieldIdItem) && (dex.get_header().field_ids_off != 0) {
panic!("Map is missing Field Id entry");
}
if !used_types.contains(&MapItemType::MethodIdItem) && (dex.get_header().method_ids_off != 0) {
panic!("Map is missing Method Id entry");
}
if !used_types.contains(&MapItemType::ClassDefItem) && (dex.get_header().class_defs_off != 0) {
panic!("Map is missing Class Def entry");
}
}
#[test]
fn test_2_from_json() {
let filename = "app1.json";
let json_path = format!("{}/src/tests/{}", env!("CARGO_MANIFEST_DIR"), filename);
let mut file = File::open(&json_path).expect(&format!("{} not found", filename));
let mut json = String::new();
file.read_to_string(&mut json).unwrap();
let apk: Apk = serde_json::from_str(&json).unwrap();
let dex = apk.gen_raw_dex().unwrap().remove("classes.dex").unwrap();
let mut new_apk = Apk::new();
new_apk
.add_dex_file("classes.dex", &dex, |_, _, _| None, false)
.unwrap();
assert_eq!(apk, new_apk);
}
#[test]
fn test_hidden_api() {
let dex_raw = get_dex("core-oj-33_classes.dex");
// The data are generated using src/tests/apktool_parse_hiddenapi.py src/tests/core-oj-33_classes.dex src/tests/core-oj-33_hiddenapi.json
let apktool_result = format!(
"{}/src/tests/{}",
env!("CARGO_MANIFEST_DIR"),
"core-oj-33_hiddenapi.json"
);
fn f_desc_to_pointer(f_dsc: &IdField) -> String {
let name: String = String::try_from(&f_dsc.name.0).unwrap().replace("/", "~1");
let ty: String = f_dsc.type_.try_to_smali().unwrap().replace("/", "~1");
format!(
"/{}/fields/{name}:{ty}",
f_dsc.class_.try_to_smali().unwrap().replace("/", "~1")
)
}
fn m_desc_to_pointer(m_dsc: &IdMethod) -> String {
let name: String = String::try_from(&m_dsc.name.0).unwrap().replace("/", "~1");
let ty: String = m_dsc.proto.try_to_smali().unwrap().replace("/", "~1");
format!(
"/{}/methods/{name}{ty}",
m_dsc.class_.try_to_smali().unwrap().replace("/", "~1")
)
}
fn compare_hidden_api(
apktool_hiddenapi: &sj::Value,
apktool_hiddenapi_domain: &sj::Value,
parsed_api: &Option<HiddenApiData>,
name: &str,
) {
match (apktool_hiddenapi, apktool_hiddenapi_domain, parsed_api) {
(sj::Value::Null, sj::Value::Null, None) => (),
(
sj::Value::String(apktool_hiddenapi),
sj::Value::String(apktool_domain),
Some(HiddenApiData { permission, domain }),
) if (permission.to_smali_name() == apktool_hiddenapi)
&& (&domain.to_smali_name() == apktool_domain) =>
{
()
}
(
sj::Value::String(apktool_hiddenapi),
sj::Value::Null,
Some(HiddenApiData { permission, domain }),
) if (permission.to_smali_name() == apktool_hiddenapi)
&& &domain.to_smali_name() == "" =>
{
()
}
_ => panic!(
"Expected {apktool_hiddenapi:?} and {apktool_hiddenapi_domain:?}, found {parsed_api:?} in {name}"
),
}
}
let apktool_result = File::open(&apktool_result).expect("core-oj-33_hiddenapi.json not found");
let apktool_result = std::io::BufReader::new(apktool_result);
let apktool_result: sj::Value = sj::from_reader(apktool_result).unwrap();
let mut apk = Apk::new();
apk.add_dex_file("classes.dex", &dex_raw, |_, _, _| None, false)
.unwrap();
for cls in apktool_result.as_object().unwrap().keys() {
assert!(
apk.dex_files
.get("classes.dex")
.unwrap()
.classes
.get(&dex_id::IdType(cls.as_str().into()))
.is_some(),
"{cls} not found in core-oj-33_classes.dex"
);
}
for cls in apk.dex_files.get("classes.dex").unwrap().classes.keys() {
assert!(
apktool_result
.get::<String>((&cls.0).try_into().unwrap())
.is_some(),
"{} not found in core-oj-33_hiddenapi.json",
cls.__str__()
);
}
for (_, cls) in &apk.dex_files.get("classes.dex").unwrap().classes {
for (f_dsc, field) in &cls.static_fields {
let pointer = f_desc_to_pointer(f_dsc);
let apktool_field = apktool_result.pointer(&pointer).unwrap();
let apktool_hiddenapi = apktool_field.get("hiddenapi").unwrap();
let apktool_hiddenapi_domain = apktool_field.get("hiddenapi_domain").unwrap();
compare_hidden_api(
apktool_hiddenapi,
&apktool_hiddenapi_domain,
&field.hiddenapi,
&f_dsc.try_to_smali().unwrap(),
);
}
for (f_dsc, field) in &cls.instance_fields {
let pointer = f_desc_to_pointer(f_dsc);
let apktool_field = apktool_result.pointer(&pointer).unwrap();
let apktool_hiddenapi = apktool_field.get("hiddenapi").unwrap();
let apktool_hiddenapi_domain = apktool_field.get("hiddenapi_domain").unwrap();
compare_hidden_api(
apktool_hiddenapi,
&apktool_hiddenapi_domain,
&field.hiddenapi,
&f_dsc.try_to_smali().unwrap(),
);
}
for (m_dsc, method) in &cls.direct_methods {
let pointer = m_desc_to_pointer(m_dsc);
let apktool_method = apktool_result.pointer(&pointer).unwrap();
let apktool_hiddenapi = apktool_method.get("hiddenapi").unwrap();
let apktool_hiddenapi_domain = apktool_method.get("hiddenapi_domain").unwrap();
compare_hidden_api(
apktool_hiddenapi,
&apktool_hiddenapi_domain,
&method.hiddenapi,
&m_dsc.try_to_smali().unwrap(),
);
}
for (m_dsc, method) in &cls.virtual_methods {
let pointer = m_desc_to_pointer(m_dsc);
let apktool_method = apktool_result.pointer(&pointer).unwrap();
let apktool_hiddenapi = apktool_method.get("hiddenapi").unwrap();
let apktool_hiddenapi_domain = apktool_method.get("hiddenapi_domain").unwrap();
compare_hidden_api(
apktool_hiddenapi,
&apktool_hiddenapi_domain,
&method.hiddenapi,
&m_dsc.try_to_smali().unwrap(),
);
}
}
}
#[test]
fn test_is_platform_class() {
let activity = Class::new("Landroid/app/Activity;".into()).unwrap();
assert!(activity.is_platform_class());
let not_platform = Class::new("Landroid/app/NotAPlatformClass;".into()).unwrap();
assert!(!not_platform.is_platform_class());
}

View file

@ -1,194 +0,0 @@
{
"descriptor": "Lcom/example/testclassloader/TestB;",
"is_public": true,
"is_final": false,
"is_interface": false,
"is_abstract": false,
"is_synthetic": false,
"is_annotation": false,
"is_enum": false,
"superclass": "Ljava/lang/Object;",
"interfaces": [],
"source_file": {
"String": "TestB.java"
},
"static_fields": {},
"instance_fields": {},
"direct_methods": {
"Lcom/example/testclassloader/TestB;-><init>()V": {
"descriptor": "Lcom/example/testclassloader/TestB;-><init>()V",
"visibility": "Public",
"is_static": false,
"is_final": false,
"is_synchronized": false,
"is_bridge": false,
"is_varargs": false,
"is_native": false,
"is_abstract": false,
"is_strictfp": false,
"is_synthetic": false,
"is_constructor": true,
"is_declared_syncrhonized": false,
"annotations": [],
"parameters_annotations": [],
"code": {
"registers_size": 1,
"ins_size": 1,
"outs_size": 1,
"debug_info": [
3,
[
14,
0
]
],
"parameter_names": [],
"insns": [
{
"Label": {
"name": "label_00000000"
}
},
{
"InvokeDirect": {
"method": "Ljava/lang/Object;-><init>()V",
"args": [
0
]
}
},
{
"Label": {
"name": "label_00000003"
}
},
{
"ReturnVoid": {}
}
]
}
}
},
"virtual_methods": {
"Lcom/example/testclassloader/TestB;->val()Ljava/lang/String;": {
"descriptor": "Lcom/example/testclassloader/TestB;->val()Ljava/lang/String;",
"visibility": "Public",
"is_static": false,
"is_final": false,
"is_synchronized": false,
"is_bridge": false,
"is_varargs": false,
"is_native": false,
"is_abstract": false,
"is_strictfp": false,
"is_synthetic": false,
"is_constructor": false,
"is_declared_syncrhonized": false,
"annotations": [],
"parameters_annotations": [],
"code": {
"registers_size": 2,
"ins_size": 1,
"outs_size": 1,
"debug_info": [
6,
[
14,
0
]
],
"parameter_names": [],
"insns": [
{
"Label": {
"name": "label_00000000"
}
},
{
"InvokeVirtual": {
"method": "Ljava/lang/Object;->getClass()Ljava/lang/Class;",
"args": [
1
]
}
},
{
"Label": {
"name": "label_00000003"
}
},
{
"MoveResultObject": {
"to": 0
}
},
{
"Label": {
"name": "label_00000004"
}
},
{
"InvokeVirtual": {
"method": "Ljava/lang/Class;->getClassLoader()Ljava/lang/ClassLoader;",
"args": [
0
]
}
},
{
"Label": {
"name": "label_00000007"
}
},
{
"MoveResultObject": {
"to": 0
}
},
{
"Label": {
"name": "label_00000008"
}
},
{
"InvokeVirtual": {
"method": "Ljava/lang/Object;->toString()Ljava/lang/String;",
"args": [
0
]
}
},
{
"Label": {
"name": "label_0000000B"
}
},
{
"MoveResultObject": {
"to": 0
}
},
{
"Label": {
"name": "label_0000000C"
}
},
{
"ReturnObject": {
"reg": 0
}
},
{
"Label": {
"name": "label_0000000D"
}
},
{
"Nop": {}
}
]
}
}
},
"annotations": []
}

View file

@ -1,34 +0,0 @@
use std::io::{Read, Seek, SeekFrom};
use crate::Result;
use androscalpel_serializer::{HeaderItem, Serializable};
use apk_frauder::{ZipFileReader, end_of_central_directory::EndCentralDirectory};
/// Test if a file is as .dex file an return the dex version if it is, else return None.
pub fn is_dex(file: &mut (impl Read + Seek)) -> Result<Option<usize>> {
let pos = file.stream_position()?;
let r = HeaderItem::deserialize(file)
.ok()
.and_then(|header| String::from_utf8(header.magic.version.to_vec()).ok())
.and_then(|version| version.parse::<usize>().ok());
file.seek(SeekFrom::Start(pos))?;
Ok(r)
}
/// Test if a file is a zip file.
pub fn is_zip(mut file: impl Read + Seek) -> Result<bool> {
let pos = file.stream_position()?;
let ecd_off = match ZipFileReader::get_end_of_central_directory_offset(&mut file) {
Some(off) => off,
_ => {
return Ok(false);
}
};
file.seek(SeekFrom::Start(ecd_off))?;
let r = match apk_frauder::Signature::deserialize(&mut file) {
Ok(sig) => EndCentralDirectory::SIGNATURE == sig,
_ => false,
};
file.seek(SeekFrom::Start(pos))?;
Ok(r)
}

View file

@ -3,13 +3,10 @@
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
#[cfg(feature = "python")]
use pyo3::{exceptions::PyTypeError, prelude::*};
use pyo3::exceptions::PyTypeError;
use pyo3::prelude::*;
use crate::{
DexAnnotation, DexString, MethodHandle, Result, Visitable, VisitableMut, Visitor, VisitorMut,
dex_id::*, scalar::*,
};
use crate::{dex_id::*, scalar::*, DexAnnotation, DexString, MethodHandle};
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub enum DexValue {
@ -33,94 +30,43 @@ pub enum DexValue {
Boolean(DexBoolean),
}
impl<V: Visitor> Visitable<V> for DexValue {
fn default_visit(&self, v: &mut V) -> Result<()> {
match self {
Self::Byte(val) => v.visit_byte(val),
Self::Short(val) => v.visit_short(val),
Self::Char(val) => v.visit_char(val),
Self::Int(val) => v.visit_int(val),
Self::Long(val) => v.visit_long(val),
Self::Float(val) => v.visit_float(val),
Self::Double(val) => v.visit_double(val),
Self::MethodType(val) => v.visit_method_type(val),
Self::MethodHandle(val) => v.visit_method_handle(val),
Self::String(val) => v.visit_string(val),
Self::Type(val) => v.visit_type(val),
Self::Field(val) => v.visit_field_id(val),
Self::Method(val) => v.visit_method_id(val),
Self::Enum(val) => v.visit_enum_id(val),
Self::Array(val) => v.visit_array(val),
Self::Annotation(val) => v.visit_annotation(val),
Self::Null(val) => v.visit_null(val),
Self::Boolean(val) => v.visit_bool(val),
}
}
}
impl<V: VisitorMut> VisitableMut<V> for DexValue {
fn default_visit_mut(self, v: &mut V) -> Result<Self> {
match self {
Self::Byte(val) => Ok(Self::Byte(v.visit_byte(val)?)),
Self::Short(val) => Ok(Self::Short(v.visit_short(val)?)),
Self::Char(val) => Ok(Self::Char(v.visit_char(val)?)),
Self::Int(val) => Ok(Self::Int(v.visit_int(val)?)),
Self::Long(val) => Ok(Self::Long(v.visit_long(val)?)),
Self::Float(val) => Ok(Self::Float(v.visit_float(val)?)),
Self::Double(val) => Ok(Self::Double(v.visit_double(val)?)),
Self::MethodType(val) => Ok(Self::MethodType(v.visit_method_type(val)?)),
Self::MethodHandle(val) => Ok(Self::MethodHandle(v.visit_method_handle(val)?)),
Self::String(val) => Ok(Self::String(v.visit_string(val)?)),
Self::Type(val) => Ok(Self::Type(v.visit_type(val)?)),
Self::Field(val) => Ok(Self::Field(v.visit_field_id(val)?)),
Self::Method(val) => Ok(Self::Method(v.visit_method_id(val)?)),
Self::Enum(val) => Ok(Self::Enum(v.visit_enum_id(val)?)),
Self::Array(val) => Ok(Self::Array(v.visit_array(val)?)),
Self::Annotation(val) => Ok(Self::Annotation(v.visit_annotation(val)?)),
Self::Null(val) => Ok(Self::Null(v.visit_null(val)?)),
Self::Boolean(val) => Ok(Self::Boolean(v.visit_bool(val)?)),
}
}
}
#[cfg(feature = "python")]
impl<'source> FromPyObject<'source> for DexValue {
fn extract_bound(ob: &Bound<'source, PyAny>) -> PyResult<Self> {
if let Ok(val) = DexByte::extract_bound(ob) {
fn extract(ob: &'source PyAny) -> PyResult<Self> {
if let Ok(val) = DexByte::extract(ob) {
Ok(Self::Byte(val))
} else if let Ok(val) = DexShort::extract_bound(ob) {
} else if let Ok(val) = DexShort::extract(ob) {
Ok(Self::Short(val))
} else if let Ok(val) = DexChar::extract_bound(ob) {
} else if let Ok(val) = DexChar::extract(ob) {
Ok(Self::Char(val))
} else if let Ok(val) = DexInt::extract_bound(ob) {
} else if let Ok(val) = DexInt::extract(ob) {
Ok(Self::Int(val))
} else if let Ok(val) = DexLong::extract_bound(ob) {
} else if let Ok(val) = DexLong::extract(ob) {
Ok(Self::Long(val))
} else if let Ok(val) = DexFloat::extract_bound(ob) {
} else if let Ok(val) = DexFloat::extract(ob) {
Ok(Self::Float(val))
} else if let Ok(val) = DexDouble::extract_bound(ob) {
} else if let Ok(val) = DexDouble::extract(ob) {
Ok(Self::Double(val))
} else if let Ok(val) = DexString::extract_bound(ob) {
} else if let Ok(val) = DexString::extract(ob) {
Ok(Self::String(val))
} else if let Ok(val) = IdType::extract_bound(ob) {
} else if let Ok(val) = IdType::extract(ob) {
Ok(Self::Type(val))
} else if let Ok(val) = IdField::extract_bound(ob) {
} else if let Ok(val) = IdField::extract(ob) {
Ok(Self::Field(val))
} else if let Ok(val) = IdMethod::extract_bound(ob) {
} else if let Ok(val) = IdMethod::extract(ob) {
Ok(Self::Method(val))
} else if let Ok(val) = IdEnum::extract_bound(ob) {
} else if let Ok(val) = IdEnum::extract(ob) {
Ok(Self::Enum(val))
} else if let Ok(val) = DexArray::extract_bound(ob) {
} else if let Ok(val) = DexArray::extract(ob) {
Ok(Self::Array(val))
} else if let Ok(val) = DexAnnotation::extract_bound(ob) {
} else if let Ok(val) = DexAnnotation::extract(ob) {
Ok(Self::Annotation(val))
} else if let Ok(val) = DexNull::extract_bound(ob) {
} else if let Ok(val) = DexNull::extract(ob) {
Ok(Self::Null(val))
} else if let Ok(val) = DexBoolean::extract_bound(ob) {
} else if let Ok(val) = DexBoolean::extract(ob) {
Ok(Self::Boolean(val))
} else if let Ok(val) = IdMethodType::extract_bound(ob) {
} else if let Ok(val) = IdMethodType::extract(ob) {
Ok(Self::MethodType(val))
} else if let Ok(val) = MethodHandle::extract_bound(ob) {
} else if let Ok(val) = MethodHandle::extract(ob) {
Ok(Self::MethodHandle(val))
} else {
Err(PyErr::new::<PyTypeError, _>(format!(
@ -306,32 +252,28 @@ impl DexValue {
}
}
}
#[cfg(feature = "python")]
impl<'py> IntoPyObject<'py> for DexValue {
type Target = PyAny;
type Output = Bound<'py, Self::Target>;
type Error = PyErr;
fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
impl IntoPy<PyObject> for DexValue {
fn into_py(self, py: Python<'_>) -> PyObject {
match self {
DexValue::Byte(val) => Bound::new(py, val).map(Bound::<_>::into_any),
DexValue::Short(val) => Bound::new(py, val).map(Bound::<_>::into_any),
DexValue::Char(val) => Bound::new(py, val).map(Bound::<_>::into_any),
DexValue::Int(val) => Bound::new(py, val).map(Bound::<_>::into_any),
DexValue::Long(val) => Bound::new(py, val).map(Bound::<_>::into_any),
DexValue::Float(val) => Bound::new(py, val).map(Bound::<_>::into_any),
DexValue::Double(val) => Bound::new(py, val).map(Bound::<_>::into_any),
DexValue::MethodType(val) => Bound::new(py, val).map(Bound::<_>::into_any),
DexValue::MethodHandle(val) => Bound::new(py, val).map(Bound::<_>::into_any),
DexValue::String(val) => Bound::new(py, val).map(Bound::<_>::into_any),
DexValue::Type(val) => Bound::new(py, val).map(Bound::<_>::into_any),
DexValue::Field(val) => Bound::new(py, val).map(Bound::<_>::into_any),
DexValue::Method(val) => Bound::new(py, val).map(Bound::<_>::into_any),
DexValue::Enum(val) => Bound::new(py, val).map(Bound::<_>::into_any),
DexValue::Array(val) => Bound::new(py, val).map(Bound::<_>::into_any),
DexValue::Annotation(val) => Bound::new(py, val).map(Bound::<_>::into_any),
DexValue::Null(val) => Bound::new(py, val).map(Bound::<_>::into_any),
DexValue::Boolean(val) => Bound::new(py, val).map(Bound::<_>::into_any),
DexValue::Byte(val) => val.into_py(py),
DexValue::Short(val) => val.into_py(py),
DexValue::Char(val) => val.into_py(py),
DexValue::Int(val) => val.into_py(py),
DexValue::Long(val) => val.into_py(py),
DexValue::Float(val) => val.into_py(py),
DexValue::Double(val) => val.into_py(py),
DexValue::MethodType(val) => val.into_py(py),
DexValue::MethodHandle(val) => val.into_py(py),
DexValue::String(val) => val.into_py(py),
DexValue::Type(val) => val.into_py(py),
DexValue::Field(val) => val.into_py(py),
DexValue::Method(val) => val.into_py(py),
DexValue::Enum(val) => val.into_py(py),
DexValue::Array(val) => val.into_py(py),
DexValue::Annotation(val) => val.into_py(py),
DexValue::Null(val) => val.into_py(py),
DexValue::Boolean(val) => val.into_py(py),
}
}
}

View file

@ -1,419 +0,0 @@
//! The visitor trait and common implementations.
use crate::{
ins::Instruction, scalar::*, Apk, CallSite, Class, Code, DexAnnotation, DexAnnotationItem,
DexFile, DexString, DexValue, Field, FieldVisibility, HiddenApiData, HiddenApiDomain,
HiddenApiPermission, IdEnum, IdField, IdMethod, IdMethodType, IdType, Method, MethodHandle,
MethodVisibility, Result,
};
use rayon::prelude::*;
use std::collections::HashSet;
pub trait Visitor: Sized {
fn visit_instruction(&mut self, ins: &Instruction) -> Result<()> {
ins.default_visit(self)
}
fn visit_string(&mut self, string: &DexString) -> Result<()> {
string.default_visit(self)
}
fn visit_type(&mut self, ty: &IdType) -> Result<()> {
ty.default_visit(self)
}
fn visit_method_type(&mut self, mty: &IdMethodType) -> Result<()> {
mty.default_visit(self)
}
fn visit_field_id(&mut self, id: &IdField) -> Result<()> {
id.default_visit(self)
}
fn visit_method_id(&mut self, id: &IdMethod) -> Result<()> {
id.default_visit(self)
}
fn visit_enum_id(&mut self, id: &IdEnum) -> Result<()> {
id.default_visit(self)
}
fn visit_method_handle(&mut self, handle: &MethodHandle) -> Result<()> {
handle.default_visit(self)
}
fn visit_call_site(&mut self, site: &CallSite) -> Result<()> {
site.default_visit(self)
}
fn visit_value(&mut self, val: &DexValue) -> Result<()> {
val.default_visit(self)
}
fn visit_byte(&mut self, _: &DexByte) -> Result<()> {
Ok(())
}
fn visit_short(&mut self, _: &DexShort) -> Result<()> {
Ok(())
}
fn visit_char(&mut self, _: &DexChar) -> Result<()> {
Ok(())
}
fn visit_int(&mut self, _: &DexInt) -> Result<()> {
Ok(())
}
fn visit_long(&mut self, _: &DexLong) -> Result<()> {
Ok(())
}
fn visit_float(&mut self, _: &DexFloat) -> Result<()> {
Ok(())
}
fn visit_double(&mut self, _: &DexDouble) -> Result<()> {
Ok(())
}
fn visit_array(&mut self, val: &DexArray) -> Result<()> {
val.default_visit(self)
}
fn visit_annotation(&mut self, val: &DexAnnotation) -> Result<()> {
val.default_visit(self)
}
fn visit_null(&mut self, _: &DexNull) -> Result<()> {
Ok(())
}
fn visit_bool(&mut self, _: &DexBoolean) -> Result<()> {
Ok(())
}
fn visit_class(&mut self, class: &Class) -> Result<()> {
class.default_visit(self)
}
fn visit_field(&mut self, field: &Field) -> Result<()> {
field.default_visit(self)
}
fn visit_method(&mut self, method: &Method) -> Result<()> {
method.default_visit(self)
}
fn visit_annotation_item(&mut self, annotation: &DexAnnotationItem) -> Result<()> {
annotation.default_visit(self)
}
fn visit_field_visibility(&mut self, _: &FieldVisibility) -> Result<()> {
Ok(())
}
fn visit_method_visibility(&mut self, _: &MethodVisibility) -> Result<()> {
Ok(())
}
fn visit_hidden_api_data(&mut self, hidden_api: &HiddenApiData) -> Result<()> {
hidden_api.default_visit(self)
}
fn visit_hidden_api_permission(&mut self, _: &HiddenApiPermission) -> Result<()> {
Ok(())
}
fn visit_hidden_api_domain(&mut self, _: &HiddenApiDomain) -> Result<()> {
Ok(())
}
fn visit_code(&mut self, code: &Code) -> Result<()> {
code.default_visit(self)
}
fn visit_dex_file(&mut self, dex_file: &DexFile) -> Result<()> {
dex_file.default_visit(self)
}
fn visit_apk(&mut self, apk: &Apk) -> Result<()> {
apk.default_visit(self)
}
}
pub trait VisitorMut: Sized {
fn visit_instruction(&mut self, ins: Instruction) -> Result<Instruction> {
ins.default_visit_mut(self)
}
fn visit_string(&mut self, string: DexString) -> Result<DexString> {
string.default_visit_mut(self)
}
fn visit_type(&mut self, ty: IdType) -> Result<IdType> {
ty.default_visit_mut(self)
}
fn visit_method_type(&mut self, mty: IdMethodType) -> Result<IdMethodType> {
mty.default_visit_mut(self)
}
fn visit_field_id(&mut self, id: IdField) -> Result<IdField> {
id.default_visit_mut(self)
}
fn visit_method_id(&mut self, id: IdMethod) -> Result<IdMethod> {
id.default_visit_mut(self)
}
fn visit_enum_id(&mut self, id: IdEnum) -> Result<IdEnum> {
id.default_visit_mut(self)
}
fn visit_method_handle(&mut self, handle: MethodHandle) -> Result<MethodHandle> {
handle.default_visit_mut(self)
}
fn visit_call_site(&mut self, site: CallSite) -> Result<CallSite> {
site.default_visit_mut(self)
}
fn visit_value(&mut self, val: DexValue) -> Result<DexValue> {
val.default_visit_mut(self)
}
fn visit_byte(&mut self, val: DexByte) -> Result<DexByte> {
Ok(val)
}
fn visit_short(&mut self, val: DexShort) -> Result<DexShort> {
Ok(val)
}
fn visit_char(&mut self, val: DexChar) -> Result<DexChar> {
Ok(val)
}
fn visit_int(&mut self, val: DexInt) -> Result<DexInt> {
Ok(val)
}
fn visit_long(&mut self, val: DexLong) -> Result<DexLong> {
Ok(val)
}
fn visit_float(&mut self, val: DexFloat) -> Result<DexFloat> {
Ok(val)
}
fn visit_double(&mut self, val: DexDouble) -> Result<DexDouble> {
Ok(val)
}
fn visit_array(&mut self, val: DexArray) -> Result<DexArray> {
val.default_visit_mut(self)
}
fn visit_annotation(&mut self, val: DexAnnotation) -> Result<DexAnnotation> {
val.default_visit_mut(self)
}
fn visit_null(&mut self, val: DexNull) -> Result<DexNull> {
Ok(val)
}
fn visit_bool(&mut self, val: DexBoolean) -> Result<DexBoolean> {
Ok(val)
}
fn visit_class(&mut self, class: Class) -> Result<Class> {
class.default_visit_mut(self)
}
fn visit_field(&mut self, field: Field) -> Result<Field> {
field.default_visit_mut(self)
}
fn visit_method(&mut self, method: Method) -> Result<Method> {
method.default_visit_mut(self)
}
fn visit_annotation_item(
&mut self,
annotation: DexAnnotationItem,
) -> Result<DexAnnotationItem> {
annotation.default_visit_mut(self)
}
fn visit_field_visibility(&mut self, visibility: FieldVisibility) -> Result<FieldVisibility> {
Ok(visibility)
}
fn visit_method_visibility(
&mut self,
visibility: MethodVisibility,
) -> Result<MethodVisibility> {
Ok(visibility)
}
fn visit_hidden_api_data(&mut self, hidden_api: HiddenApiData) -> Result<HiddenApiData> {
hidden_api.default_visit_mut(self)
}
fn visit_hidden_api_permission(
&mut self,
perm: HiddenApiPermission,
) -> Result<HiddenApiPermission> {
Ok(perm)
}
fn visit_hidden_api_domain(&mut self, domain: HiddenApiDomain) -> Result<HiddenApiDomain> {
Ok(domain)
}
fn visit_code(&mut self, code: Code) -> Result<Code> {
code.default_visit_mut(self)
}
fn visit_dex_file(&mut self, dex_file: DexFile) -> Result<DexFile> {
dex_file.default_visit_mut(self)
}
fn visit_apk(&mut self, apk: Apk) -> Result<Apk> {
apk.default_visit_mut(self)
}
}
/// Trait for structures that can be visited.
/// In theorie, this is not required, as the [`Visitor`] trait implement
/// all tree traversal, however if the visitor do not do a complex
/// operation it can be usefull to have a default tree traversal accessible
/// to re-implemented trait methods.
pub trait Visitable<V: Visitor> {
fn default_visit(&self, visitor: &mut V) -> Result<()>;
}
/// Trait for structures that can be modified by a visitor.
/// In theorie, this is not required, as the [`VisitorMut`] trait implement
/// all tree traversal, however if the visitor do not do a complex
/// operation it can be usefull to have a default tree traversal accessible
/// to re-implemented trait methods.
pub trait VisitableMut<V: VisitorMut> {
fn default_visit_mut(self, visitor: &mut V) -> Result<Self>
where
Self: Sized;
}
#[derive(Debug, Default)]
pub struct StringCollector {
pub strings: HashSet<DexString>,
}
impl StringCollector {
pub fn result(self) -> HashSet<DexString> {
self.strings
}
}
impl Visitor for StringCollector {
fn visit_string(&mut self, string: &DexString) -> Result<()> {
self.strings.insert(string.clone());
Ok(())
}
}
#[derive(Debug, Default)]
pub struct TypeCollector {
pub types: HashSet<IdType>,
}
impl TypeCollector {
pub fn result(self) -> HashSet<IdType> {
self.types
}
}
impl Visitor for TypeCollector {
fn visit_type(&mut self, ty: &IdType) -> Result<()> {
self.types.insert(ty.clone());
Ok(())
}
fn visit_dex_file(&mut self, dex: &DexFile) -> Result<()> {
let results: Vec<_> = dex
.classes
.par_iter()
.map(|(ty, cls)| {
let mut v = Self::default();
v.visit_type(ty)
.map(|_| v.visit_class(cls))
.map(|_| v.result())
})
.collect::<Result<_, _>>()?;
for r in results.into_iter() {
self.types.extend(r);
}
Ok(())
}
}
#[derive(Debug, Default)]
pub struct MethodTypeCollector {
pub method_types: HashSet<IdMethodType>,
}
impl MethodTypeCollector {
pub fn result(self) -> HashSet<IdMethodType> {
self.method_types
}
}
impl Visitor for MethodTypeCollector {
fn visit_method_type(&mut self, mty: &IdMethodType) -> Result<()> {
self.method_types.insert(mty.clone());
Ok(())
}
fn visit_dex_file(&mut self, dex: &DexFile) -> Result<()> {
let results: Vec<_> = dex
.classes
.par_iter()
.map(|(ty, cls)| {
let mut v = Self::default();
v.visit_type(ty)
.map(|_| v.visit_class(cls))
.map(|_| v.result())
})
.collect::<Result<_, _>>()?;
for r in results.into_iter() {
self.method_types.extend(r);
}
Ok(())
}
}
#[derive(Debug, Default)]
pub struct FieldIdCollector {
pub field_ids: HashSet<IdField>,
}
impl FieldIdCollector {
pub fn result(self) -> HashSet<IdField> {
self.field_ids
}
}
impl Visitor for FieldIdCollector {
fn visit_field_id(&mut self, id: &IdField) -> Result<()> {
self.field_ids.insert(id.clone());
Ok(())
}
fn visit_dex_file(&mut self, dex: &DexFile) -> Result<()> {
let results: Vec<_> = dex
.classes
.par_iter()
.map(|(ty, cls)| {
let mut v = Self::default();
v.visit_type(ty)
.map(|_| v.visit_class(cls))
.map(|_| v.result())
})
.collect::<Result<_, _>>()?;
for r in results.into_iter() {
self.field_ids.extend(r);
}
Ok(())
}
}
#[derive(Debug, Default)]
pub struct MethodIdCollector {
pub method_ids: HashSet<IdMethod>,
}
impl MethodIdCollector {
pub fn result(self) -> HashSet<IdMethod> {
self.method_ids
}
}
impl Visitor for MethodIdCollector {
fn visit_method_id(&mut self, id: &IdMethod) -> Result<()> {
self.method_ids.insert(id.clone());
Ok(())
}
fn visit_dex_file(&mut self, dex: &DexFile) -> Result<()> {
let results: Vec<_> = dex
.classes
.par_iter()
.map(|(ty, cls)| {
let mut v = Self::default();
v.visit_type(ty)
.map(|_| v.visit_class(cls))
.map(|_| v.result())
})
.collect::<Result<_, _>>()?;
for r in results.into_iter() {
self.method_ids.extend(r);
}
Ok(())
}
}
#[derive(Debug, Default)]
pub struct MethodHandleCollector {
pub handles: HashSet<MethodHandle>,
}
impl MethodHandleCollector {
pub fn result(self) -> HashSet<MethodHandle> {
self.handles
}
}
impl Visitor for MethodHandleCollector {
fn visit_method_handle(&mut self, handle: &MethodHandle) -> Result<()> {
self.handles.insert(handle.clone());
Ok(())
}
}

View file

@ -1,10 +0,0 @@
[package]
name = "androscalpel_platform_api_list"
version = "0.1.0"
edition = "2024"
license = "GPL-3.0-or-later"
[dependencies]
[features]
sdk34 = []

View file

@ -1,5 +0,0 @@
# Android Platform API
List of android platform API (API that are either in the android SDK or hidden API).
The list is extracted from the default android emulator the respective SDKs.

View file

@ -1,20 +0,0 @@
pub mod sdk34;
pub use sdk34::*;
// TODO: automate addign a new SDK
// TODO: different version with different features
/// Test if the class is part of the platform classes.
pub fn is_platform_class(smali: &str) -> bool {
is_sdk34_platform_class(smali)
}
/// Test if the field is part of the platform classes.
pub fn is_platform_field(smali: &str) -> bool {
is_sdk34_platform_field(smali)
}
/// Test if the method is part of the platform classes.
pub fn is_platform_method(smali: &str) -> bool {
is_sdk34_platform_method(smali)
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,60 +0,0 @@
use std::collections::HashSet;
use std::sync::LazyLock;
pub static PLATFORM_CLASSES_SDK34: LazyLock<HashSet<&'static str>> =
LazyLock::new(|| include_str!("classes.txt").lines().collect());
pub static PLATFORM_FIELDS_SDK34: LazyLock<HashSet<&'static str>> =
LazyLock::new(|| include_str!("fields.txt").lines().collect());
pub static PLATFORM_METHODS_SDK34: LazyLock<HashSet<&'static str>> =
LazyLock::new(|| include_str!("methods.txt").lines().collect());
/// Test if the class is part of the platform classes of the SDK 34.
pub fn is_sdk34_platform_class(smali: &str) -> bool {
PLATFORM_CLASSES_SDK34.contains(smali)
}
/// Test if the field is part of the platform classes of the SDK 34.
pub fn is_sdk34_platform_field(smali: &str) -> bool {
PLATFORM_FIELDS_SDK34.contains(smali)
}
/// Test if the method is part of the platform classes of the SDK 34.
pub fn is_sdk34_platform_method(smali: &str) -> bool {
PLATFORM_METHODS_SDK34.contains(smali)
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_classes_sdk34_set() {
assert!(PLATFORM_CLASSES_SDK34
.contains("Landroid/accessibilityservice/AccessibilityGestureEvent;"));
let s: String = "Landroid/accessibilityservice/AccessibilityInputMethodSession;".into();
assert!(PLATFORM_CLASSES_SDK34.contains(s.as_str()));
}
#[test]
fn test_fields_sdk34_set() {
assert!(PLATFORM_FIELDS_SDK34.contains(
"Landroid/accessibilityservice/AccessibilityButtonController$$\
ExternalSyntheticLambda1;->f$0:Landroid/accessibilityservice/\
AccessibilityButtonController;"
));
let s: String = "Landroid/accessibilityservice/AccessibilityButtonController;->mLock:Ljava/lang/Object;".into();
assert!(PLATFORM_FIELDS_SDK34.contains(s.as_str()));
}
#[test]
fn test_methods_sdk34_set() {
assert!(PLATFORM_METHODS_SDK34
.contains("Landroid/accessibilityservice/AccessibilityButtonController$$ExternalSyntheticLambda1;->run()V"));
let s: String = "Landroid/accessibilityservice/AccessibilityButtonController;->\
$r8$lambda$h5Na0_pkg6ffhlKQPqxrTXannSI(\
Landroid/accessibilityservice/AccessibilityButtonController;\
Landroid/accessibilityservice/AccessibilityButtonController$AccessibilityButtonCallback;\
)V".into();
assert!(PLATFORM_METHODS_SDK34.contains(s.as_str()));
}
}

View file

@ -1,8 +1,7 @@
[package]
name = "androscalpel_serializer"
version = "0.1.0"
edition = "2024"
license = "GPL-3.0-or-later"
edition = "2021"
[dependencies]
androscalpel_serializer_derive = { path = "../androscalpel_serializer_derive" }

View file

@ -19,7 +19,7 @@ pub struct Uleb128p1(pub u32);
impl Sleb128 {
fn get_serialized_bytes(&self) -> Vec<u8> {
let &Self(mut val) = self;
let Self(mut val) = self;
let mut bytes = vec![];
loop {
let byte = val as u8 & 0b0111_1111;
@ -81,7 +81,7 @@ impl Serializable for Sleb128 {
impl Uleb128 {
fn get_serialized_bytes(&self) -> Vec<u8> {
let &Self(mut val) = self;
let Self(mut val) = self;
let mut bytes = vec![];
loop {
let byte = val as u8 & 0b0111_1111;

View file

@ -17,7 +17,6 @@ pub enum Error {
DeserializationError(String),
InvalidStringEncoding(String),
InconsistantStruct(String),
OutOfBound(String),
}
pub type Result<T> = core::result::Result<T, Error>;
@ -33,7 +32,6 @@ impl std::fmt::Display for Error {
Self::DeserializationError(msg) => write!(f, "{}", msg),
Self::InvalidStringEncoding(msg) => write!(f, "{}", msg),
Self::InconsistantStruct(msg) => write!(f, "{}", msg),
Self::OutOfBound(msg) => write!(f, "{}", msg),
}
}
}
@ -390,9 +388,7 @@ mod test {
fn serialize_u64() {
assert_eq!(
0x123456789ABCDEF0u64.serialize_to_vec().unwrap(),
vec![
0xF0u8, 0xDEu8, 0xBCu8, 0x9Au8, 0x78u8, 0x56u8, 0x34u8, 0x12u8
]
vec![0xF0u8, 0xDEu8, 0xBCu8, 0x9Au8, 0x78u8, 0x56u8, 0x34u8, 0x12u8]
);
}

View file

@ -143,7 +143,7 @@ impl StringDataItem {
let two = self.data[i] as u32;
i += 1;
if one & 0x20 == 0 {
utf16_string.push(((one & 0x1f) << 6) | (two & 0x3f));
utf16_string.push((one & 0x1f) << 6 | (two & 0x3f));
continue;
}
@ -155,7 +155,7 @@ impl StringDataItem {
let three = self.data[i] as u32;
i += 1;
if one & 0x10 == 0 {
utf16_string.push(((one & 0x0f) << 12) | ((two & 0x3f) << 6) | (three & 0x3f));
utf16_string.push((one & 0x0f) << 12 | (two & 0x3f) << 6 | (three & 0x3f));
continue;
}
@ -167,7 +167,7 @@ impl StringDataItem {
let four = self.data[i] as u32;
i += 1;
let code_point =
((one & 0x0f) << 18) | ((two & 0x3f) << 12) | ((three & 0x3f) << 6) | (four & 0x3f);
(one & 0x0f) << 18 | (two & 0x3f) << 12 | (three & 0x3f) << 6 | (four & 0x3f);
let mut pair = ((code_point >> 10) + 0xd7c0) & 0xffff;
pair |= ((code_point & 0x03ff) + 0xdc00) << 16;
utf16_string.push(pair);
@ -357,34 +357,26 @@ mod test {
/// Test for bug found in <https://github.com/TkTech/mutf8/tree/master>:
#[test]
fn test_tktech_bad_mutf8() {
assert!(
TryInto::<String>::try_into(StringDataItem {
utf16_size: Uleb128(0),
data: vec![0x00]
})
.is_err()
);
assert!(
TryInto::<String>::try_into(StringDataItem {
utf16_size: Uleb128(0),
data: vec![0xC2]
})
.is_err()
);
assert!(
TryInto::<String>::try_into(StringDataItem {
utf16_size: Uleb128(0),
data: vec![0xED]
})
.is_err()
);
assert!(
TryInto::<String>::try_into(StringDataItem {
utf16_size: Uleb128(0),
data: vec![0xE2]
})
.is_err()
);
assert!(TryInto::<String>::try_into(StringDataItem {
utf16_size: Uleb128(0),
data: vec![0x00]
})
.is_err());
assert!(TryInto::<String>::try_into(StringDataItem {
utf16_size: Uleb128(0),
data: vec![0xC2]
})
.is_err());
assert!(TryInto::<String>::try_into(StringDataItem {
utf16_size: Uleb128(0),
data: vec![0xED]
})
.is_err());
assert!(TryInto::<String>::try_into(StringDataItem {
utf16_size: Uleb128(0),
data: vec![0xE2]
})
.is_err());
}
/// Test from <https://github.com/TkTech/mutf8/tree/master>, test 2 bytes

View file

@ -1,9 +1,7 @@
//! Debug structs
use crate as androscalpel_serializer;
use crate::{
Error, NO_INDEX, ReadSeek, Result, Serializable, SerializableUntil, Sleb128, Uleb128, Uleb128p1,
};
use crate::{ReadSeek, Result, Serializable, SerializableUntil, Sleb128, Uleb128, Uleb128p1};
use std::io::Write;
/// <https://source.android.com/docs/core/runtime/dex-format#debug-info-item>
@ -95,556 +93,6 @@ impl Serializable for DebugInfoItem {
}
}
/// The name and type of the variable in a register.
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct DebugRegState {
pub type_idx: Option<u32>,
pub name_idx: Option<u32>,
pub sig_idx: Option<u32>,
pub in_scope: bool,
}
/// A simplified debug information
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum DebugInfo {
DefLocal {
addr: u32,
reg: u32,
val: DebugRegState,
},
EndLocal {
addr: u32,
reg: u32,
},
PrologueEnd {
addr: u32,
},
EpilogueBegin {
addr: u32,
},
SetSourceFile {
addr: u32,
source_file_idx: Option<u32>,
},
SetLineNumber {
addr: u32,
line_num: u32,
},
EndOfData,
}
impl DebugInfo {
pub fn get_addr(&self) -> u32 {
match self {
Self::DefLocal { addr, .. } => *addr,
Self::EndLocal { addr, .. } => *addr,
Self::PrologueEnd { addr } => *addr,
Self::EpilogueBegin { addr } => *addr,
Self::SetSourceFile { addr, .. } => *addr,
Self::SetLineNumber { addr, .. } => *addr,
Self::EndOfData => u32::MAX, // TODO should be an Option::None?
}
}
}
/// A state machine that interpret a [`DebugInfoItem`].
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct DebugInfoReader {
debug_info: DebugInfoItem,
pub pc: usize,
pub address: u32,
pub line: u32,
// Those are registers described in the doc but not necessary in the end
//pub source_file_idx: Option<u32>,
//pub prologue_end: bool,
//pub epilogue_begin: bool,
pub register_states: Vec<DebugRegState>,
}
impl DebugInfoReader {
pub fn new(
debug_info: DebugInfoItem,
//source_file_idx: Option<u32>,
//nb_reg: usize
) -> Self {
let line = debug_info.line_start.0;
Self {
debug_info,
pc: 0,
address: 0,
line,
//source_file_idx,
//prologue_end: false,
//epilogue_begin: false,
//register_states: vec![
// DebugRegState {
// name_idx: None,
// type_idx: None,
// sig_idx: None,
// in_scope: false,
// };
// nb_reg
//],
register_states: vec![], // In the end, it's easier to grow this on the fly
}
}
pub fn get_ins(&self) -> Result<DbgBytecode> {
if self.pc >= self.debug_info.bytecode.len() {
return Err(Error::OutOfBound(
"Try to read an instruction out of bound, maybe after the end of the debug sequence."
.into()
));
}
Ok(self.debug_info.bytecode[self.pc])
}
pub fn tick(&mut self) -> Option<DebugInfo> {
let ins = if let Ok(ins) = self.get_ins() {
ins
} else {
return Some(DebugInfo::EndOfData);
};
self.pc += 1;
match ins {
DbgBytecode::EndSequence => {
self.pc = self.debug_info.bytecode.len();
Some(DebugInfo::EndOfData)
}
DbgBytecode::AdvancePC {
addr_diff: Uleb128(addr_diff),
} => {
self.address += addr_diff;
None
}
DbgBytecode::AdvanceLine {
line_diff: Sleb128(line_diff),
} => {
self.line = (self.line as i32 + line_diff) as u32;
None
}
DbgBytecode::StartLocal {
register_num: Uleb128(register_num),
name_idx,
type_idx,
} => {
while self.register_states.len() < (register_num + 1) as usize {
self.register_states.push(DebugRegState {
name_idx: None,
type_idx: None,
sig_idx: None,
in_scope: false,
})
}
self.register_states[register_num as usize] = DebugRegState {
name_idx: if name_idx == NO_INDEX {
None
} else {
Some(name_idx.0)
},
type_idx: if type_idx == NO_INDEX {
None
} else {
Some(type_idx.0)
},
sig_idx: None,
in_scope: true,
};
Some(DebugInfo::DefLocal {
addr: self.address,
reg: register_num,
val: self.register_states[register_num as usize],
})
}
DbgBytecode::StartLocalExtended {
register_num: Uleb128(register_num),
name_idx,
type_idx,
sig_idx,
} => {
while self.register_states.len() < (register_num + 1) as usize {
self.register_states.push(DebugRegState {
name_idx: None,
type_idx: None,
sig_idx: None,
in_scope: false,
})
}
self.register_states[register_num as usize] = DebugRegState {
name_idx: if name_idx == NO_INDEX {
None
} else {
Some(name_idx.0)
},
type_idx: if type_idx == NO_INDEX {
None
} else {
Some(type_idx.0)
},
sig_idx: if sig_idx == NO_INDEX {
None
} else {
Some(sig_idx.0)
},
in_scope: true,
};
Some(DebugInfo::DefLocal {
addr: self.address,
reg: register_num,
val: self.register_states[register_num as usize],
})
}
DbgBytecode::EndLocal {
register_num: Uleb128(register_num),
} => {
// Yes this can happen
while self.register_states.len() < (register_num + 1) as usize {
self.register_states.push(DebugRegState {
name_idx: None,
type_idx: None,
sig_idx: None,
in_scope: false,
})
}
self.register_states[register_num as usize].in_scope = false;
Some(DebugInfo::EndLocal {
addr: self.address,
reg: register_num,
})
}
DbgBytecode::RestartLocal {
register_num: Uleb128(register_num),
} => {
while self.register_states.len() < (register_num + 1) as usize {
self.register_states.push(DebugRegState {
name_idx: None,
type_idx: None,
sig_idx: None,
in_scope: false,
})
}
self.register_states[register_num as usize].in_scope = true;
Some(DebugInfo::DefLocal {
addr: self.address,
reg: register_num,
val: self.register_states[register_num as usize],
})
}
DbgBytecode::SetPrologueEnd => {
//self.prologue_end = true;
Some(DebugInfo::PrologueEnd { addr: self.address })
}
DbgBytecode::SetEpilogueBegin => {
//self.epilogue_begin = true;
Some(DebugInfo::EpilogueBegin { addr: self.address })
}
DbgBytecode::SetFile { name_idx: NO_INDEX } => {
//self.source_file_idx = None;
Some(DebugInfo::SetSourceFile {
addr: self.address,
source_file_idx: None,
})
}
DbgBytecode::SetFile {
name_idx: Uleb128p1(name_idx),
} => {
//self.source_file_idx = Some(name_idx);
Some(DebugInfo::SetSourceFile {
addr: self.address,
source_file_idx: Some(name_idx),
})
}
DbgBytecode::SpecialOpcode(op) => {
//if op >= 0x0a {
// self.prologue_end = false;
// self.epilogue_begin = true;
//}
// See <https://source.android.com/docs/core/runtime/dex-format#opcodes>
let adjusted_opcode = op as u32 - 0x0a;
self.line = (self.line as i32 + (adjusted_opcode as i32 % 15) - 4) as u32;
self.address += adjusted_opcode / 15;
Some(DebugInfo::SetLineNumber {
addr: self.address,
line_num: self.line,
})
}
}
}
pub fn next_info(&mut self) -> DebugInfo {
loop {
if let Some(info) = self.tick() {
return info;
}
}
}
}
/// A state machine that generate a [`DebugInfoItem`].
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct DebugInfoBuilder {
debug_infos: Vec<DbgBytecode>,
line_start: Option<u32>,
parameter_names: Vec<Uleb128p1>,
//pub pc: usize,
address: u32,
line: u32,
// Those are registers described in the doc but not necessary in the end
//pub source_file_idx: Option<u32>,
//pub prologue_end: bool,
//pub epilogue_begin: bool,
register_states: Vec<DebugRegState>,
finished: bool,
}
impl DebugInfoBuilder {
pub fn new(parameter_names: Vec<Uleb128p1>) -> Self {
Self {
debug_infos: vec![],
line_start: None,
parameter_names,
//pc: 0,
address: 0,
line: 0,
//source_file_idx,
//prologue_end: false,
//epilogue_begin: false,
//register_states: vec![
// DebugRegState {
// name_idx: None,
// type_idx: None,
// sig_idx: None,
// in_scope: false,
// };
// nb_reg
//],
register_states: vec![], // In the end, it's easier to grow this on the fly
finished: false,
}
}
pub fn add_info(&mut self, info: &DebugInfo) -> Result<()> {
if self.finished {
return Err(Error::SerializationError(
"Cannot add more information: EndSequence has already been send".into(),
));
}
match info {
DebugInfo::DefLocal { addr, reg, val } => {
if *addr < self.address {
return Err(Error::SerializationError(format!(
"The address register can only increase, \
found 0x{addr:02x} while register is already \
0x{:02x}",
self.address
)));
}
while self.register_states.len() < (reg + 1) as usize {
self.register_states.push(DebugRegState {
name_idx: None,
type_idx: None,
sig_idx: None,
in_scope: false,
});
}
if *addr != self.address {
let addr_diff = *addr - self.address;
self.debug_infos.push(DbgBytecode::AdvancePC {
addr_diff: Uleb128(addr_diff),
});
self.address += addr_diff;
}
let mut old_val = self.register_states[*reg as usize];
let old_val_in_scope = old_val.in_scope;
old_val.in_scope = true;
if old_val_in_scope && old_val == *val {
self.register_states[*reg as usize].in_scope = true;
self.debug_infos.push(DbgBytecode::RestartLocal {
register_num: Uleb128(*reg),
});
} else {
self.register_states[*reg as usize] = *val;
if val.sig_idx.is_some() {
self.debug_infos.push(DbgBytecode::StartLocalExtended {
register_num: Uleb128(*reg),
name_idx: if let Some(name_idx) = val.name_idx {
Uleb128p1(name_idx)
} else {
NO_INDEX
},
type_idx: if let Some(type_idx) = val.type_idx {
Uleb128p1(type_idx)
} else {
NO_INDEX
},
sig_idx: if let Some(sig_idx) = val.sig_idx {
Uleb128p1(sig_idx)
} else {
NO_INDEX
},
})
} else {
self.debug_infos.push(DbgBytecode::StartLocal {
register_num: Uleb128(*reg),
name_idx: if let Some(name_idx) = val.name_idx {
Uleb128p1(name_idx)
} else {
NO_INDEX
},
type_idx: if let Some(type_idx) = val.type_idx {
Uleb128p1(type_idx)
} else {
NO_INDEX
},
})
}
}
Ok(())
}
DebugInfo::EndLocal { addr, reg } => {
if *addr < self.address {
return Err(Error::SerializationError(format!(
"The address register can only increase, \
found 0x{addr:02x} while register is already \
0x{:02x}",
self.address
)));
}
while self.register_states.len() < (reg + 1) as usize {
self.register_states.push(DebugRegState {
name_idx: None,
type_idx: None,
sig_idx: None,
in_scope: false,
});
}
if *addr != self.address {
let addr_diff = *addr - self.address;
self.debug_infos.push(DbgBytecode::AdvancePC {
addr_diff: Uleb128(addr_diff),
});
self.address += addr_diff;
}
self.debug_infos.push(DbgBytecode::EndLocal {
register_num: Uleb128(*reg),
});
Ok(())
}
DebugInfo::PrologueEnd { addr } => {
if *addr < self.address {
return Err(Error::SerializationError(format!(
"The address register can only increase, \
found 0x{addr:02x} while register is already \
0x{:02x}",
self.address
)));
}
if *addr != self.address {
let addr_diff = *addr - self.address;
self.debug_infos.push(DbgBytecode::AdvancePC {
addr_diff: Uleb128(addr_diff),
});
self.address += addr_diff;
}
self.debug_infos.push(DbgBytecode::SetPrologueEnd);
Ok(())
}
DebugInfo::EpilogueBegin { addr } => {
if *addr < self.address {
return Err(Error::SerializationError(format!(
"The address register can only increase, \
found 0x{addr:02x} while register is already \
0x{:02x}",
self.address
)));
}
if *addr != self.address {
let addr_diff = *addr - self.address;
self.debug_infos.push(DbgBytecode::AdvancePC {
addr_diff: Uleb128(addr_diff),
});
self.address += addr_diff;
}
self.debug_infos.push(DbgBytecode::SetEpilogueBegin);
Ok(())
}
DebugInfo::SetLineNumber { addr, line_num } => {
if *addr < self.address {
return Err(Error::SerializationError(format!(
"The address register can only increase, \
found 0x{addr:02x} while register is already \
0x{:02x}",
self.address
)));
}
if self.line_start.is_none() {
self.line_start = Some(*line_num);
self.line = *line_num;
}
let mut line_diff = *line_num as i32 - self.line as i32;
let mut addr_diff = addr - self.address;
if !(-4..15 - 4).contains(&line_diff) {
self.debug_infos.push(DbgBytecode::AdvanceLine {
line_diff: Sleb128(line_diff),
});
self.line = *line_num;
line_diff = 0;
}
if addr_diff as i32 * 15 + 0x0a + line_diff + 4 > 0xff {
self.debug_infos.push(DbgBytecode::AdvancePC {
addr_diff: Uleb128(addr_diff),
});
self.address = *addr;
addr_diff = 0;
}
let op = 0x0a + addr_diff as u8 * 15 + (line_diff + 4) as u8;
self.debug_infos.push(DbgBytecode::SpecialOpcode(op));
self.address += addr_diff;
self.line = (self.line as i32 + line_diff) as u32;
Ok(())
}
DebugInfo::SetSourceFile {
addr,
source_file_idx,
} => {
if *addr != self.address {
let addr_diff = *addr - self.address;
self.debug_infos.push(DbgBytecode::AdvancePC {
addr_diff: Uleb128(addr_diff),
});
self.address += addr_diff;
}
self.debug_infos.push(DbgBytecode::SetFile {
name_idx: if let Some(source_file_idx) = source_file_idx {
Uleb128p1(*source_file_idx)
} else {
NO_INDEX
},
});
Ok(())
}
DebugInfo::EndOfData => {
self.finished = true;
Ok(())
}
}
}
/// If they are no debug information, return None, else compute and return the [`DebugInfoItem`].
pub fn build(self) -> Option<DebugInfoItem> {
if self.debug_infos.is_empty() && self.parameter_names.iter().all(|&idx| idx == NO_INDEX) {
None
} else {
Some(DebugInfoItem {
line_start: Uleb128(self.line_start.unwrap_or(0)),
parameter_names: self.parameter_names,
bytecode: self.debug_infos,
})
}
}
}
#[cfg(test)]
mod test {
use super::DbgBytecode::*;
@ -664,10 +112,6 @@ mod test {
addr_diff: Uleb128(51),
},
SpecialOpcode(14),
// End a local that do not already exist
EndLocal {
register_num: Uleb128(41),
},
],
};
assert_eq!(
@ -686,27 +130,4 @@ mod test {
DbgBytecode::deserialize_from_slice(&advance_line.serialize_to_vec().unwrap()).unwrap()
);
}
#[test]
fn test_get_expl_debug() {
const RAW_DEBUG: [u8; 10] = [23, 0, 14, 135, 3, 0, 16, 2, 150, 0];
let debug = DebugInfoItem::deserialize_from_slice(&RAW_DEBUG).unwrap();
let mut reader = DebugInfoReader::new(debug.clone());
let mut list_info = vec![];
loop {
list_info.push(reader.next_info());
if list_info.last() == Some(&DebugInfo::EndOfData) {
break;
}
}
let mut builder = DebugInfoBuilder::new(debug.parameter_names.clone());
for info in list_info {
builder.add_info(&info).unwrap();
}
let debug_computed = builder.build().unwrap();
assert_eq!(
&RAW_DEBUG,
&(debug_computed.serialize_to_vec().unwrap()).as_slice()
);
}
}

View file

@ -1,9 +1,9 @@
//! Parser for a .dex file.
use crate::{
CallSiteIdItem, ClassDataItem, ClassDefItem, EndianConstant, Error, FieldIdItem, HeaderItem,
HiddenApiFlags, HiddenapiClassDataItem, MapItemType, MapList, MethodHandleItem, MethodIdItem,
ProtoIdItem, Result, Serializable, StringDataItem, StringIdItem, TypeIdItem,
CallSiteIdItem, ClassDefItem, EndianConstant, Error, FieldIdItem, HeaderItem, MapItemType,
MapList, MethodHandleItem, MethodIdItem, ProtoIdItem, Result, Serializable, StringDataItem,
StringIdItem, TypeIdItem,
};
use log::{error, info, warn};
use std::io::{Cursor, Seek, SeekFrom};
@ -11,8 +11,6 @@ use std::sync::atomic::{AtomicBool, Ordering};
#[derive(Debug)]
pub struct DexFileReader<'a> {
// Ideally, this would be a Read+Seek, but Read+Seek is not thread safe, while we can
// internally instanciate multiple cursors on the same non mutable slice.
data: &'a [u8],
header: HeaderItem,
string_ids: Vec<StringIdItem>,
@ -30,7 +28,6 @@ pub struct DexFileReader<'a> {
class_defs: Vec<ClassDefItem>,
call_site_ids: Vec<CallSiteIdItem>,
method_handles: Vec<MethodHandleItem>,
hiddenapi_class_data: Option<HiddenapiClassDataItem>,
map_list: MapList,
}
@ -51,12 +48,9 @@ impl<'a> DexFileReader<'a> {
class_defs: vec![],
call_site_ids: vec![],
method_handles: vec![],
hiddenapi_class_data: None,
map_list: MapList { list: vec![] },
};
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)?;
tmp_file.string_ids = tmp_file.get_item_list::<StringIdItem>(
tmp_file.header.string_ids_off,
tmp_file.header.string_ids_size,
@ -103,15 +97,6 @@ impl<'a> DexFileReader<'a> {
tmp_file.method_handles =
tmp_file.get_item_list::<MethodHandleItem>(item.offset, item.size)?
}
if let Some(item) = tmp_file
.map_list
.list
.iter()
.find(|item| item.type_ == MapItemType::HiddenapiClassDataItem)
{
tmp_file.hiddenapi_class_data =
Some(tmp_file.get_struct_at_offset::<HiddenapiClassDataItem>(item.offset)?);
}
tmp_file.sanity_check()?;
Ok(tmp_file)
}
@ -120,39 +105,39 @@ impl<'a> DexFileReader<'a> {
pub fn get_header(&self) -> &HeaderItem {
&self.header
}
/// Return the file [`StringIdItem`] list.
/// Retunr the file [`StringIdItem`] list.
pub fn get_string_ids(&self) -> &[StringIdItem] {
&self.string_ids
}
/// Return the file [`TypeIdItem`] list.
/// Retunr the file [`TypeIdItem`] list.
pub fn get_type_ids(&self) -> &[TypeIdItem] {
&self.type_ids
}
/// Return the file [`ProtoIdItem`] list.
/// Retunr the file [`ProtoIdItem`] list.
pub fn get_proto_ids(&self) -> &[ProtoIdItem] {
&self.proto_ids
}
/// Return the file [`FieldIdItem`] list.
/// Retunr the file [`FieldIdItem`] list.
pub fn get_field_ids(&self) -> &[FieldIdItem] {
&self.field_ids
}
/// Return the file [`MethodIdItem`] list.
/// Retunr the file [`MethodIdItem`] list.
pub fn get_method_ids(&self) -> &[MethodIdItem] {
&self.method_ids
}
/// Return the file [`ClassDefItem`] list.
/// Retunr the file [`ClassDefItem`] list.
pub fn get_class_defs(&self) -> &[ClassDefItem] {
&self.class_defs
}
/// Return the file [`CallSiteIdItem`] list.
/// Retunr the file [`CallSiteIdItem`] list.
pub fn get_call_site_ids(&self) -> &[CallSiteIdItem] {
&self.call_site_ids
}
/// Return the file [`MethodHandleItem`] list.
/// Retunr the file [`MethodHandleItem`] list.
pub fn get_method_handles(&self) -> &[MethodHandleItem] {
&self.method_handles
}
/// Return the file [`MapList`].
/// Retunr the file [`MapList`].
pub fn get_map_list(&self) -> &MapList {
&self.map_list
}
@ -237,11 +222,9 @@ impl<'a> DexFileReader<'a> {
}
fn sanity_check(&self) -> Result<()> {
let version = self.header.magic.version;
let version = (version[0] - 0x30) * 100 + (version[1] - 0x30) * 10 + (version[2] - 0x30);
if version > 39 {
if self.header.magic.version != [0x30, 0x33, 0x39] {
warn!(
"Only version <= DEX 039 are currently supported, found {}",
"DEX 039 is the only version currently supported, found {}",
std::str::from_utf8(self.header.magic.version.as_slice())
.unwrap_or(&format!("{:x?}", self.header.magic.version))
);
@ -272,7 +255,7 @@ impl<'a> DexFileReader<'a> {
MapItemType::HeaderItem if item.offset != 0 || item.size != 1 => {
return Err(Error::InconsistantStruct(format!(
"Inconsistant Header Mapping info found in map_list: {item:x?}"
)));
)))
}
MapItemType::StringIdItem
if item.offset != self.header.string_ids_off
@ -282,7 +265,7 @@ impl<'a> DexFileReader<'a> {
"Inconsistant MapList Mapping info found in map_list: {item:x?}, \
header.string_ids_off: 0x{:x}, header.string_ids_size: {}",
self.header.string_ids_off, self.header.string_ids_size
)));
)))
}
MapItemType::TypeIdItem
if item.offset != self.header.type_ids_off
@ -292,7 +275,7 @@ impl<'a> DexFileReader<'a> {
"Inconsistant MapList Mapping info found in map_list: {item:x?}, \
header.type_ids_off: 0x{:x}, header.type_ids_size: {}",
self.header.type_ids_off, self.header.type_ids_size
)));
)))
}
MapItemType::ProtoIdItem
if item.offset != self.header.proto_ids_off
@ -302,7 +285,7 @@ impl<'a> DexFileReader<'a> {
"Inconsistant MapList Mapping info found in map_list: {item:x?}, \
header.proto_ids_off: 0x{:x}, header.proto_ids_size: {}",
self.header.proto_ids_off, self.header.proto_ids_size
)));
)))
}
MapItemType::FieldIdItem
if item.offset != self.header.field_ids_off
@ -312,7 +295,7 @@ impl<'a> DexFileReader<'a> {
"Inconsistant MapList Mapping info found in map_list: {item:x?}, \
header.field_ids_off: 0x{:x}, header.field_ids_size: {}",
self.header.field_ids_off, self.header.field_ids_size
)));
)))
}
MapItemType::MethodIdItem
if item.offset != self.header.method_ids_off
@ -322,7 +305,7 @@ impl<'a> DexFileReader<'a> {
"Inconsistant MapList Mapping info found in map_list: {item:x?}, \
header.method_ids_off: 0x{:x}, header.method_ids_size: {}",
self.header.method_ids_off, self.header.method_ids_size
)));
)))
}
MapItemType::ClassDefItem
if item.offset != self.header.class_defs_off
@ -332,14 +315,14 @@ impl<'a> DexFileReader<'a> {
"Inconsistant MapList Mapping info found in map_list: {item:x?}, \
header.class_defs_off: 0x{:x}, header.class_defs_size: {}",
self.header.class_defs_off, self.header.class_defs_size
)));
)))
}
MapItemType::MapList if item.offset != self.header.map_off || item.size != 1 => {
return Err(Error::InconsistantStruct(format!(
"Inconsistant MapList Mapping info found in map_list: {item:x?}, \
header.map_list_off: 0x{:x}",
self.header.map_off
)));
)))
}
/*
MapItemType::CallSiteIdItem => todo!(),
@ -382,9 +365,6 @@ impl<'a> DexFileReader<'a> {
}
fn get_item_list<T: Serializable>(&self, offset: u32, size: u32) -> Result<Vec<T>> {
if offset == 0 {
return Ok(vec![]);
}
let mut buffer = Cursor::new(self.data);
buffer.seek(SeekFrom::Start(offset as u64)).map_err(|err| {
Error::DeserializationError(format!("Failed to seek 0x{offset:x} position: {err}"))
@ -436,45 +416,6 @@ impl<'a> DexFileReader<'a> {
r
}
/// Return the hiddenapi flags list for the given class.
///
/// The list of flags is composed of one [`HiddenApiFlags`] for each static field, instance
/// field, direct method and virtual method of the class, in that order.
///
/// `class_def_item_idx` if the idx of the `class_def_item`, **not** the `class_idx` (contrary
/// to what <https://source.android.com/docs/core/runtime/dex-format#hiddenapi-class-data-item>
/// says)
pub fn get_class_hiddenapi_flags(
&self,
class_def_item_idx: usize,
) -> Result<Option<Vec<HiddenApiFlags>>> {
if class_def_item_idx >= self.class_defs.len() {
return Err(Error::InconsistantStruct(format!(
"idx 0x{class_def_item_idx:x} is out of bound of class_defs"
)));
}
let class_def = self.class_defs[class_def_item_idx];
if class_def.class_data_off == 0 {
if self.hiddenapi_class_data.is_some() {
return Ok(Some(vec![]));
} else {
return Ok(None);
}
}
let class_data = self.get_struct_at_offset::<ClassDataItem>(class_def.class_data_off)?;
let nb_flags = class_data.static_fields.len()
+ class_data.instance_fields.len()
+ class_data.direct_methods.len()
+ class_data.virtual_methods.len();
if let Some(hidden_api_data) = &self.hiddenapi_class_data {
hidden_api_data
.get_flags(nb_flags, class_def_item_idx)
.map(Some)
} else {
Ok(None)
}
}
/// Return the strings that where not referenced.
pub fn get_not_resolved_strings(&mut self) -> Result<Vec<StringDataItem>> {
// use `&mut self` because using this method at the same time as performing

View file

@ -100,8 +100,8 @@ impl CodeItem {
addresses.push(try_.start_addr);
addresses.push(try_.start_addr + try_.insn_count as u32);
}
if let Some(handlers) = &self.handlers {
for catch in &handlers.list {
for handler in &self.handlers {
for catch in &handler.list {
for EncodedTypeAddrPair {
addr: Uleb128(addr),
..
@ -977,9 +977,7 @@ mod test {
}
.serialize_to_vec()
.unwrap(),
vec![
0x03, 0xb4, 0x01, 0x80, 0x02, 0xc7, 0x08, 0x8e, 0x02, 0xd9, 0x09, 0x87, 0x02,
]
vec![0x03, 0xb4, 0x01, 0x80, 0x02, 0xc7, 0x08, 0x8e, 0x02, 0xd9, 0x09, 0x87, 0x02,]
);
assert_eq!(
EncodedCatchHandler {
@ -1001,9 +999,7 @@ mod test {
}
.serialize_to_vec()
.unwrap(),
vec![
0x03, 0xe9, 0x46, 0x56, 0xc7, 0x08, 0x8e, 0x02, 0xd9, 0x09, 0x87, 0x02,
]
vec![0x03, 0xe9, 0x46, 0x56, 0xc7, 0x08, 0x8e, 0x02, 0xd9, 0x09, 0x87, 0x02,]
);
assert_eq!(
EncodedCatchHandler {

View file

@ -1,12 +1,13 @@
//! Hidden api items.
use crate as androscalpel_serializer;
use crate::{Error, ReadSeek, Result, Serializable, Uleb128};
use std::io::{Cursor, Seek, SeekFrom, Write};
use std::io::{Cursor, Write};
/// <https://source.android.com/docs/core/runtime/dex-format#hiddenapi-class-data-item>
/// Hard to serialize/deserialize without additional data like the number of classes defs
/// Hard to serialize/deserialize without additional data like the number of classes
/// or the method/field of the classes.
#[derive(Clone, PartialEq, Eq, Debug)]
#[derive(Clone, PartialEq, Eq)]
pub struct HiddenapiClassDataItem {
//pub size: u32,
pub data: Vec<u8>,
@ -17,7 +18,7 @@ impl HiddenapiClassDataItem {
self.data.len() as u32
}
/// Return `hiddenapi_class_data_item.offsets[class_def_item_idx]`.
/// Return `hiddenapi_class_data_item.offsets[class_idx]`.
///
/// If `0`: Either no data for this class or all API flags are zero.
/// Else: offset from the begining of the [`HiddenapiClassDataItem`]
@ -25,17 +26,13 @@ impl HiddenapiClassDataItem {
///
/// # Warning
///
/// `class_def_item_idx` is **NOT** `class_idx` (Contrary to what
/// <https://source.android.com/docs/core/runtime/dex-format#hiddenapi-class-data-item> says).
/// `class_def_item_idx` is the index of the `class_def_item`.
///
/// They are no check weither the `class_def_item_idx` is valid one or not.
/// Giving an invalid idx (like an idx >= nb class def) is UB.
pub fn get_offset(&self, class_def_item_idx: usize) -> Result<u32> {
let index = class_def_item_idx * 4;
if self.data.len() <= index {
/// They are no check weither the `class_idx` is valid one or not.
/// Giving an invalid idx (like an idx >= nb class) is UB.
pub fn get_offset(&self, class_idx: u32) -> Result<u32> {
let index = (class_idx as usize) * 4;
if self.data.len() < index - 4 {
Err(Error::InconsistantStruct(format!(
"class index 0x{class_def_item_idx:x} out of bound of HiddenapiClassDataItem data"
"class index 0x{class_idx:x} out of bound of HiddenapiClassDataItem data"
)))
} else {
u32::deserialize_from_slice(&self.data[index..])
@ -47,7 +44,7 @@ impl HiddenapiClassDataItem {
/// # Warning
///
/// They are no check weither the `nb_class` is valid one or not.
/// Giving an invalid `nb_class` is UB.
/// Giving an invalid `nb_class`.
pub fn get_offsets(&self, nb_class: u32) -> Result<Vec<u32>> {
let mut offsets = vec![];
let mut buffer = Cursor::new(self.data.as_slice());
@ -63,37 +60,19 @@ impl HiddenapiClassDataItem {
///
/// # Warning
///
/// `class_def_item_idx` is **NOT** `class_idx` (Contrary to what
/// <https://source.android.com/docs/core/runtime/dex-format#hiddenapi-class-data-item> says).
/// `class_def_item_idx` is the index of the `class_def_item`.
///
/// They are no check weither the `nb_flags` or `class_def_item_idx`
/// They are no check weither the `nb_flags` or `offset`
/// are valid. Providing invalid values is UB.
pub fn get_flags(
&self,
nb_flags: usize,
class_def_item_idx: usize,
) -> Result<Vec<HiddenApiFlags>> {
let offset = self.get_offset(class_def_item_idx)?;
pub fn get_flags(&self, nb_flags: usize, offset: u32) -> Result<Vec<Uleb128>> {
if offset == 0 {
Ok(vec![HiddenApiFlags::DEFAULT; nb_flags])
Ok(vec![Uleb128(0); nb_flags])
} else if offset < 4 {
// < 8 is almost certainly false
panic!()
} else {
let mut buffer = Cursor::new(self.data.as_slice());
buffer
.seek(SeekFrom::Start(offset as u64 - 4))
.map_err(|_| {
Error::InconsistantStruct(format!(
"{offset} if out of data bound for HiddenApiClassData"
))
})?;
let mut flags = vec![];
for _ in 0..nb_flags {
flags.push(HiddenApiFlags::from_uleb128(Uleb128::deserialize(
&mut buffer,
)?));
flags.push(Uleb128::deserialize(&mut buffer)?);
}
Ok(flags)
}
@ -123,139 +102,40 @@ impl Serializable for HiddenapiClassDataItem {
}
}
#[derive(Clone, PartialEq, Eq)]
pub struct HiddenApiFlags {
pub value: HiddenApiValue,
pub domain_api: HiddenApiDomain,
}
impl HiddenApiFlags {
pub const DEFAULT: Self = Self {
value: HiddenApiValue::Whitelist,
domain_api: HiddenApiDomain {
is_test_api: false,
is_core_platform_api: false,
unknown_flags: 0,
},
};
pub fn from_uleb128(Uleb128(val): Uleb128) -> Self {
Self {
value: HiddenApiValue::from_u32(val),
domain_api: HiddenApiDomain::from_u32(val),
}
}
pub fn to_uleb128(&self) -> Uleb128 {
Uleb128(self.value.to_u32() | self.domain_api.to_u32())
}
}
/// Flags for hidden api flag value
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum HiddenApiValue {
/// Flags for hidden api
#[derive(Serializable, Debug, PartialEq, Eq, Copy, Clone)]
#[prefix_type(Uleb128)]
pub enum HiddenApiFlag {
/// Interfaces that can be freely used and are supported as
/// part of the officially documented Android framework Package Index
#[prefix(Uleb128(0x00))]
Whitelist,
/// Non-SDK interfaces that can be used regardless of the
/// application's target API level
#[prefix(Uleb128(0x01))]
Greylist,
/// Non-SDK interfaces that cannot be used regardless of the
/// application's target API level. Accessing one of these
/// interfaces causes a runtime error.
#[prefix(Uleb128(0x02))]
Blacklist,
/// Non-SDK interfaces that can be used for Android 8.x and
/// below unless they are restricted (targetSdkVersion <= 27 (O_MR1)).
/// below unless they are restricted.
#[prefix(Uleb128(0x03))]
GreylistMaxO,
/// Non-SDK interfaces that can be used for Android 9.x unless
/// they are restricted.
#[prefix(Uleb128(0x04))]
GreylistMaxP,
/// Non-SDK interfaces that can be used for Android 10.x unless
/// they are restricted.
#[prefix(Uleb128(0x05))]
GreylistMaxQ,
/// Non-SDK interfaces that can be used for Android 11.x unless
/// they are restricted.
#[prefix(Uleb128(0x06))]
GreylistMaxR,
GreylistMaxS,
/// Unknown flag, either an error or this crate is out of date.
Unknwon(u8),
}
impl HiddenApiValue {
pub const MASK: u32 = 0b111;
pub fn from_u32(flags: u32) -> Self {
match flags & Self::MASK {
0x00 => Self::Whitelist,
0x01 => Self::Greylist,
0x02 => Self::Blacklist,
0x03 => Self::GreylistMaxO,
0x04 => Self::GreylistMaxP,
0x05 => Self::GreylistMaxQ,
0x06 => Self::GreylistMaxR,
0x07 => Self::GreylistMaxS,
other => Self::Unknwon(other as u8),
}
}
pub fn to_u32(&self) -> u32 {
match self {
Self::Whitelist => 0x00,
Self::Greylist => 0x01,
Self::Blacklist => 0x02,
Self::GreylistMaxO => 0x03,
Self::GreylistMaxP => 0x04,
Self::GreylistMaxQ => 0x05,
Self::GreylistMaxR => 0x06,
Self::GreylistMaxS => 0x07,
Self::Unknwon(other) => {
if (0b1111_1000 & *other) != 0 {
panic!("HiddenApiValue is encoded on 3 bits but found value {other}");
}
*other as u32
}
}
}
}
/// Flags for hidden api flag domain
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub struct HiddenApiDomain {
pub is_core_platform_api: bool,
pub is_test_api: bool,
pub unknown_flags: u32,
}
impl HiddenApiDomain {
pub const CORE_PLATFORM_API_FLAG: u32 = 0x08;
pub const TEST_API_FLAG: u32 = 0x10;
pub fn from_u32(flags: u32) -> Self {
let flags = flags & !HiddenApiValue::MASK;
Self {
is_core_platform_api: (flags & Self::CORE_PLATFORM_API_FLAG) != 0,
is_test_api: (flags & Self::TEST_API_FLAG) != 0,
unknown_flags: flags & (!Self::CORE_PLATFORM_API_FLAG) & (!Self::TEST_API_FLAG),
}
}
pub fn to_u32(&self) -> u32 {
if ((0b111 | Self::CORE_PLATFORM_API_FLAG | Self::CORE_PLATFORM_API_FLAG)
& self.unknown_flags)
!= 0
{
panic!(
"The first 5 bits of HiddenApiDomain are reserved and \
shoud be set to 0, but value 0x{:x} was found",
self.unknown_flags
);
}
self.unknown_flags
| if self.is_core_platform_api {
Self::CORE_PLATFORM_API_FLAG
} else {
0
}
| if self.is_test_api {
Self::TEST_API_FLAG
} else {
0
}
}
#[default_variant]
Unknwon(Uleb128),
}

View file

@ -71,36 +71,6 @@ pub enum MapItemType {
UnkownType(u16),
}
impl MapItemType {
/// If data of this type is stored in the data section
pub fn is_data_section_type(&self) -> bool {
match self {
Self::HeaderItem => false,
Self::StringIdItem => false,
Self::TypeIdItem => false,
Self::ProtoIdItem => false,
Self::FieldIdItem => false,
Self::MethodIdItem => false,
Self::ClassDefItem => false,
Self::CallSiteIdItem => true,
Self::MethodHandleItem => true,
Self::MapList => true,
Self::TypeList => true,
Self::AnnotationSetRefList => true,
Self::AnnotationSetItem => true,
Self::ClassDataItem => true,
Self::CodeItem => true,
Self::StringDataItem => true,
Self::DebugInfoItem => true,
Self::AnnotationItem => true,
Self::EncodedArrayItem => true,
Self::AnnotationsDirectoryItem => true,
Self::HiddenapiClassDataItem => true,
Self::UnkownType(_) => true, // Most likely
}
}
}
impl MapList {
/// The size field of a MapList.
pub fn size_field(&self) -> u32 {

View file

@ -1,8 +1,7 @@
[package]
name = "androscalpel_serializer_derive"
version = "0.1.0"
edition = "2024"
license = "GPL-3.0-or-later"
edition = "2021"
[lib]
proc-macro = true

View file

@ -3,8 +3,8 @@ use quote::{format_ident, quote, quote_spanned};
use syn::parse::{Parse, ParseStream};
use syn::spanned::Spanned;
use syn::{
Attribute, Data, DeriveInput, Field, Fields, Ident, Index, Meta, MetaList, Token, Type,
Variant, parse_macro_input,
parse_macro_input, Attribute, Data, DeriveInput, Field, Fields, Ident, Index, Meta, MetaList,
Token, Type, Variant,
};
/// Derive the type Serializable.
@ -55,7 +55,7 @@ use syn::{
/// An enum can define ONE variant as the default variant. This variant is a catch all,
/// and MUST be have named field or unnamed field (no unit variant!) and:
/// - The first field of named fields variant must be named `prefix` and of the type
/// `prefix_type`.
/// `prefix_type`.
/// - The first field of unnamed fields variant must be of the type `prefix_type`.
///
/// The first field of the default variant store the prefix of the variant.
@ -318,25 +318,19 @@ fn get_enum_match(variant: &Variant) -> TokenStream {
/// for a specific field `f` accessible using `field_ref`.
fn get_implem_size_for_field(f: &Field, field_ref: TokenStream) -> TokenStream {
let params = ParamsField::parse(&f.attrs);
let prefix_stream = match params.prefix {
Some(PrefixParamsField(ref stream)) => {
quote_spanned! { f.span() =>
#stream.iter().collect::<Vec<_>>().len()
}
}
_ => {
quote_spanned! { f.span() => 0 }
let prefix_stream = if let Some(PrefixParamsField(ref stream)) = params.prefix {
quote_spanned! { f.span() =>
#stream.iter().collect::<Vec<_>>().len()
}
} else {
quote_spanned! { f.span() => 0 }
};
let suffix_stream = match params.suffix {
Some(SuffixParamsField(ref stream)) => {
quote_spanned! { f.span() =>
#stream.iter().collect::<Vec<_>>().len()
}
}
_ => {
quote_spanned! { f.span() => 0 }
let suffix_stream = if let Some(SuffixParamsField(ref stream)) = params.suffix {
quote_spanned! { f.span() =>
#stream.iter().collect::<Vec<_>>().len()
}
} else {
quote_spanned! { f.span() => 0 }
};
let main_stream = match (&f.ty, params) {
(
@ -442,29 +436,23 @@ fn get_implem_size(data: &Data, params: &ParamsStruct) -> TokenStream {
fn get_implem_serialize_for_field(f: &Field, field_ref: TokenStream) -> TokenStream {
let params = ParamsField::parse(&f.attrs);
// TODO: Improve error handling
let prefix_stream = match params.prefix {
Some(PrefixParamsField(ref stream)) => {
quote_spanned! { f.span() =>
for byte in #stream {
<u8 as androscalpel_serializer::Serializable>::serialize(&byte, output)?;
}
let prefix_stream = if let Some(PrefixParamsField(ref stream)) = params.prefix {
quote_spanned! { f.span() =>
for byte in #stream {
<u8 as androscalpel_serializer::Serializable>::serialize(&byte, output)?;
}
}
_ => {
quote_spanned! { f.span() => }
}
} else {
quote_spanned! { f.span() => }
};
let suffix_stream = match params.suffix {
Some(SuffixParamsField(ref stream)) => {
quote_spanned! { f.span() =>
for byte in #stream {
<u8 as androscalpel_serializer::Serializable>::serialize(&byte, output)?;
}
let suffix_stream = if let Some(SuffixParamsField(ref stream)) = params.suffix {
quote_spanned! { f.span() =>
for byte in #stream {
<u8 as androscalpel_serializer::Serializable>::serialize(&byte, output)?;
}
}
_ => {
quote_spanned! { f.span() => }
}
} else {
quote_spanned! { f.span() => }
};
let main_stream = match (&f.ty, params) {
(
@ -550,6 +538,7 @@ fn get_implem_serialize(data: &Data, params: &ParamsStruct) -> TokenStream {
"The first field of a named field variant must be named `prefix` and of the type of the prefix of the enum"
);
}
let recurse = fields.named.iter().map(|f| {
let name = &f.ident;
get_implem_serialize_for_field(f, quote! { *#name })
@ -598,37 +587,31 @@ fn get_implem_deserialize_for_field(f: &Field, field_ref: TokenStream) -> TokenS
let params = ParamsField::parse(&f.attrs);
let ty = &f.ty;
// TODO: Improve error handling
let prefix_stream = match params.prefix {
Some(PrefixParamsField(ref stream)) => {
quote_spanned! { f.span() =>
for byte in #stream {
if <u8 as androscalpel_serializer::Serializable>::deserialize(input)? != byte {
return Err(androscalpel_serializer::Error::DeserializationError(
"Prefix do not match #stream".into()
));
}
let prefix_stream = if let Some(PrefixParamsField(ref stream)) = params.prefix {
quote_spanned! { f.span() =>
for byte in #stream {
if <u8 as androscalpel_serializer::Serializable>::deserialize(input)? != byte {
return Err(androscalpel_serializer::Error::DeserializationError(
"Prefix do not match #stream".into()
));
}
}
}
_ => {
quote_spanned! { f.span() => }
}
} else {
quote_spanned! { f.span() => }
};
let suffix_stream = match params.suffix {
Some(SuffixParamsField(ref stream)) => {
quote_spanned! { f.span() =>
for byte in #stream {
if <u8 as androscalpel_serializer::Serializable>::deserialize(input)? != byte {
return Err(androscalpel_serializer::Error::DeserializationError(
"Suffix do not match #stream".into()
));
}
let suffix_stream = if let Some(SuffixParamsField(ref stream)) = params.suffix {
quote_spanned! { f.span() =>
for byte in #stream {
if <u8 as androscalpel_serializer::Serializable>::deserialize(input)? != byte {
return Err(androscalpel_serializer::Error::DeserializationError(
"Suffix do not match #stream".into()
));
}
}
}
_ => {
quote_spanned! { f.span() => }
}
} else {
quote_spanned! { f.span() => }
};
match (ty, params) {
(

View file

@ -1,14 +1,11 @@
[package]
name = "apk_frauder"
version = "0.1.0"
edition = "2024"
license = "GPL-3.0-or-later"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
androscalpel_serializer = { version = "0.1.0", path = "../androscalpel_serializer" }
anyhow = "1.0.98"
flate2 = { version = "1.0.28", features = ["rust_backend"] }
log = "0.4.25"
rand = "0.8.5"

View file

@ -1,3 +0,0 @@
- replace panic/unwrap/expect with results
- tests
- write zip with data descriptor

View file

@ -1,117 +0,0 @@
use crate::Signature;
use androscalpel_serializer::{ReadSeek, Result, Serializable};
use std::io::{SeekFrom, Write};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DataDescriptor {
Zip32(DataDescriptor32),
Zip64(DataDescriptor64),
}
/// DataDescriptor for 32bit zip format.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DataDescriptor32 {
// signature: Option<Signature(0x08074b50)>
pub crc_32: u32,
pub compressed_size: u32,
pub uncompressed_size: u32,
/// For reproducibility. This should be true when writting a zip, but can be false when
/// reading one. If we want to stick to the original, we need to keep this information.
pub use_signature: bool,
}
/// DataDescriptor for 64bit zip format.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DataDescriptor64 {
// signature: Option<Signature(0x08074b50)>
pub crc_32: u32,
pub compressed_size: u64,
pub uncompressed_size: u64,
/// For reproducibility. This should be true when writting a zip, but can be false when
/// reading one. If we want to stick to the original, we need to keep this information.
pub use_signature: bool,
}
impl DataDescriptor32 {
const SIGNATURE: Signature = Signature(0x08074b50);
}
impl Serializable for DataDescriptor32 {
fn serialize(&self, output: &mut dyn Write) -> Result<()> {
if self.use_signature {
Self::SIGNATURE.serialize(output)?;
}
self.crc_32.serialize(output)?;
self.compressed_size.serialize(output)?;
self.uncompressed_size.serialize(output)
}
fn deserialize(input: &mut dyn ReadSeek) -> Result<Self> {
let pos = input.stream_position().unwrap(); //TODO
let signature = Signature::deserialize(input)?;
let use_signature = if signature != Self::SIGNATURE {
input.seek(SeekFrom::Start(pos)).unwrap(); //TODO
false
} else {
true
};
let crc_32 = u32::deserialize(input)?;
let compressed_size = u32::deserialize(input)?;
let uncompressed_size = u32::deserialize(input)?;
Ok(Self {
crc_32,
compressed_size,
uncompressed_size,
use_signature,
})
}
fn size(&self) -> usize {
12 + if self.use_signature {
Self::SIGNATURE.size()
} else {
0
}
}
}
impl DataDescriptor64 {
const SIGNATURE: Signature = Signature(0x08074b50);
}
impl Serializable for DataDescriptor64 {
fn serialize(&self, output: &mut dyn Write) -> Result<()> {
if self.use_signature {
Self::SIGNATURE.serialize(output)?;
}
self.crc_32.serialize(output)?;
self.compressed_size.serialize(output)?;
self.uncompressed_size.serialize(output)
}
fn deserialize(input: &mut dyn ReadSeek) -> Result<Self> {
let pos = input.stream_position().unwrap(); //TODO
let signature = Signature::deserialize(input)?;
let use_signature = if signature != Self::SIGNATURE {
input.seek(SeekFrom::Start(pos)).unwrap(); //TODO
false
} else {
true
};
let crc_32 = u32::deserialize(input)?;
let compressed_size = u64::deserialize(input)?;
let uncompressed_size = u64::deserialize(input)?;
Ok(Self {
crc_32,
compressed_size,
uncompressed_size,
use_signature,
})
}
fn size(&self) -> usize {
20 + if self.use_signature {
Self::SIGNATURE.size()
} else {
0
}
}
}

View file

@ -1,4 +1,3 @@
use log::warn;
use std::io::{SeekFrom, Write};
use crate::compression::CompressionMethod;
@ -123,7 +122,7 @@ impl Serializable for FileHeader {
let field = ExtraField::deserialize(input);
if let Err(err) = field {
warn!(
println!(
"Failed to parsed extra field in {}: {err:?}",
header.get_name()
);
@ -136,15 +135,8 @@ impl Serializable for FileHeader {
}
}
if extra_size_read > extra_field_length as usize {
let last_extra = header.extra_field.pop().unwrap();
let size = last_extra.size();
warn!(
"Failed to parse last extra field in {}, last field ({} bytes long) is {} bytes too long",
header.get_name(),
size,
extra_size_read - extra_field_length as usize
);
//warn!("Forgetting last extra field: {:?}", last_extra);
println!("Failed to parse last extra field in {}", header.get_name());
let size = header.extra_field.pop().unwrap().size();
input.seek(SeekFrom::Current(-(size as i64))).unwrap();
}
let mut extra_size_read = input.stream_position().unwrap() - extra_field_off;

View file

@ -1,6 +1,4 @@
use androscalpel_serializer::Serializable;
use anyhow::{Context, Result};
use std::collections::HashMap;
use std::env;
use std::fs;
use std::fs::File;
@ -11,7 +9,6 @@ use std::process::Command;
pub mod apk_signing_block;
pub mod compression;
mod cp437;
pub mod data_descriptor;
pub mod end_of_central_directory;
pub mod error;
pub mod extra_fields;
@ -20,7 +17,6 @@ pub mod local_file_header;
pub mod zip_reader;
pub mod zip_writer;
use data_descriptor::DataDescriptor;
use extra_fields::{ExtraField, Zip64ExtraField};
use file_header::FileHeader;
use local_file_header::LocalFileHeader;
@ -72,8 +68,8 @@ pub mod external_file_attributes {
pub struct FileInfo {
pub local_header: LocalFileHeader,
pub header: FileHeader,
pub data_descriptor: Option<DataDescriptor>,
}
// TODO: support data descriptor (MASK_USE_DATA_DESCRIPTOR)
impl FileInfo {
pub fn get_name(&self) -> String {
@ -143,42 +139,55 @@ impl FileInfo {
}
}
/// Replace the dex files in an apk and strip the old signature.
#[allow(clippy::too_many_arguments)]
pub fn replace_dex_unsigned(
/// Replace the dex files a an apk an resigned the apk.
///
/// # Warning
///
/// For now, only jks keystore are allowed.
///
/// The `zipalign` and `apksigner` args allow to use a specific version of the
/// tools instead of the one in the PATH (if it even exist)
pub fn replace_dex(
apk: impl AsRef<Path>,
dst: impl AsRef<Path>,
dexfiles: &mut [impl Read + Seek],
additionnal_files: Option<HashMap<String, Option<impl Read + Seek>>>,
) -> Result<()> {
let file = File::open(&apk).with_context(|| {
format!(
"failed to open file {}",
apk.as_ref().to_str().unwrap_or("")
)
})?;
let mut apk = ZipFileReader::new(file)?;
let file = File::create(&dst).context("failed to create file")?;
keystore: impl AsRef<Path>, // TODO enum for handling p11 and generating a random cert
// `keytool -genkey -v -keystore KEYSTORE.jks -keyalg RSA -keysize
// 2048 -validity 10000 -alias ALIAS`
zipalign: Option<impl AsRef<Path>>,
apksigner: Option<impl AsRef<Path>>,
) {
let zipalign = if let Some(path) = &zipalign {
path.as_ref().as_os_str()
} else {
"zipalign".as_ref()
};
let apksigner = if let Some(path) = &apksigner {
path.as_ref().as_os_str()
} else {
"apksigner".as_ref()
};
let tmp_dir = env::temp_dir().join(format!("apk_frauder_{:x}", rand::random::<u128>()));
let unaligned_path = tmp_dir.join("stripped.apk");
let aligned_path = tmp_dir.join("aligned.apk");
fs::create_dir_all(&tmp_dir).expect("Failed to create temporary directory");
let file = File::open(apk).expect("failed to open file");
let mut apk = ZipFileReader::new(file);
let file = File::create(&unaligned_path).expect("failed to create file");
let mut apk_out = ZipFileWriter::new(file, apk.zip64_end_of_central_directory.clone());
let mut file_info_ref = (*apk
.get_classes_file_info()
.first()
.context("No dex file found in apk")?)
.expect("No dex file found in apk"))
.clone();
apk.unlink_signature_files();
apk.unlink_bytecode_files();
if let Some(additionnal_files) = &additionnal_files {
apk.files
.retain(|file| additionnal_files.get(&file.get_name()).is_none());
}
for f in apk.files.clone() {
apk_out.insert_file_from_zip(f, &mut apk);
}
for (i, mut dex) in dexfiles.iter_mut().enumerate() {
if i == 0 {
file_info_ref.set_name("classes.dex");
@ -189,63 +198,9 @@ pub fn replace_dex_unsigned(
&mut dex,
file_info_ref.header.clone(),
Some(file_info_ref.local_header.clone()),
file_info_ref.data_descriptor.clone(),
);
}
if let Some(mut additionnal_files) = additionnal_files {
for (name, data) in &mut additionnal_files {
file_info_ref.set_name(name);
if let Some(data) = data.as_mut() {
apk_out.insert_file(
data,
file_info_ref.header.clone(),
Some(file_info_ref.local_header.clone()),
file_info_ref.data_descriptor.clone(),
);
}
}
}
apk_out.write_central_directory();
Ok(())
}
/// Replace the dex files in an apk an resigned the apk.
///
/// # Warning
///
/// For now, only jks keystore are allowed.
///
/// The `zipalign` and `apksigner` args allow to use a specific version of the
/// tools instead of the one in the PATH (if it even exist)
#[allow(clippy::too_many_arguments)]
pub fn replace_dex(
apk: impl AsRef<Path>,
dst: impl AsRef<Path>,
dexfiles: &mut [impl Read + Seek],
keystore: impl AsRef<Path>, // TODO enum for handling p11 and generating a random cert
// `keytool -genkey -v -keystore KEYSTORE.jks -keyalg RSA -keysize
// 2048 -validity 10000 -alias ALIAS`
zipalign: Option<impl AsRef<Path>>,
apksigner: Option<impl AsRef<Path>>,
keypassword: Option<&str>,
additionnal_files: Option<HashMap<String, Option<impl Read + Seek>>>,
) -> Result<()> {
let zipalign = match &zipalign {
Some(path) => path.as_ref().as_os_str(),
_ => "zipalign".as_ref(),
};
let apksigner = match &apksigner {
Some(path) => path.as_ref().as_os_str(),
_ => "apksigner".as_ref(),
};
let tmp_dir = env::temp_dir().join(format!("apk_frauder_{:x}", rand::random::<u128>()));
let unaligned_path = tmp_dir.join("stripped.apk");
let aligned_path = tmp_dir.join("aligned.apk");
fs::create_dir_all(&tmp_dir).context("Failed to create temporary directory")?;
replace_dex_unsigned(apk, &unaligned_path, dexfiles, additionnal_files)?;
// TODO: we can probably do that ourself an spare ourself the trouble of finding zipalign
Command::new(zipalign)
.arg("-v")
@ -254,23 +209,16 @@ pub fn replace_dex(
.arg(unaligned_path.as_os_str())
.arg(aligned_path.as_os_str())
.status()
.context("Failed to run zipalign")?;
.unwrap();
let mut cmd = Command::new(apksigner);
let cmd = cmd
Command::new(apksigner)
.arg("sign")
.arg("--ks")
.arg(keystore.as_ref().as_os_str())
.arg("--out")
.arg(dst.as_ref().as_os_str())
.arg("--in")
.arg(aligned_path.as_os_str());
let cmd = if let Some(pwd) = keypassword {
cmd.arg("--ks-pass").arg(format!("pass:{pwd}"))
} else {
cmd
};
cmd.status().context("Failled to run apksigne")?;
fs::remove_dir_all(tmp_dir).context("Failled to remove tmp dir")?;
Ok(())
.arg(aligned_path.as_os_str())
.status()
.unwrap();
fs::remove_dir_all(tmp_dir).expect("Failled to remove tmp dir");
}

View file

@ -1,7 +1,5 @@
use std::io::{SeekFrom, Write};
use log::warn;
use crate::compression::CompressionMethod;
use crate::error::Error;
use crate::extra_fields::{ExtraField, GenericExtraField, Zip64ExtraField};
@ -101,9 +99,9 @@ impl Serializable for LocalFileHeader {
let field = ExtraField::deserialize(input);
if let Err(err) = field {
warn!(
println!(
"Failed to parsed extra field in {}: {err:?}",
header.get_name(),
header.get_name()
);
input.seek(SeekFrom::Start(field_off)).unwrap();
break;
@ -113,32 +111,25 @@ impl Serializable for LocalFileHeader {
header.extra_field.push(field);
}
}
let mut failed_last_extra_field_data = None;
let mut failed_last_extra_field = false;
if extra_size_read > extra_field_length as usize {
//println!("Failed to parse last extra field in {}", header.get_name());
failed_last_extra_field = true;
let size = header.extra_field.pop().unwrap().size();
input.seek(SeekFrom::Current(-(size as i64))).unwrap();
failed_last_extra_field_data =
Some((size, extra_size_read - extra_field_length as usize));
}
let mut extra_size_read = input.stream_position().unwrap() - extra_field_off;
while extra_size_read < extra_field_length as u64 {
header.malformed_extra_field.push(u8::deserialize(input)?);
extra_size_read += 1;
}
if let Some((size, size_diff)) = failed_last_extra_field_data {
// If it is not padding from zipalign
if header.malformed_extra_field != vec![0]
&& header.malformed_extra_field != vec![0, 0]
&& header.malformed_extra_field != vec![0, 0, 0]
{
warn!(
"Failed to parse last extra field in {}, last field ({} bytes long) is {} bytes too long",
header.get_name(),
size,
size_diff
);
}
// If it is not padding from zipalign
if failed_last_extra_field
&& header.malformed_extra_field != vec![0]
&& header.malformed_extra_field != vec![0, 0]
&& header.malformed_extra_field != vec![0, 0, 0]
{
println!("Failed to parse last extra field in {}", header.get_name());
}
//input.seek(SeekFrom::Start(end_of_extra_field)).unwrap();
for field in &mut header.extra_field {

View file

@ -1,11 +1,8 @@
use apk_frauder::ZipFileReader;
//use std::collections::HashMap;
//use std::env;
use std::env;
use std::fs::File;
//use std::io::Cursor;
/*
fn main() {
/*
apk_frauder::replace_dex(
"app-release.apk",
"app-instrumented.apk",
@ -19,9 +16,78 @@ fn main() {
"{}/Android/Sdk/build-tools/34.0.0/apksigner",
env::var("HOME").expect("$HOME not set")
)),
None::<HashMap<String, Option<Cursor<&[u8]>>>>,
);*/
let file = File::open("zagruski.apk").unwrap();
let reader = ZipFileReader::new(file).unwrap();
println!("{:#?}", &reader.files[..2]);
);
}*/
fn main() {
let args: Vec<String> = env::args().collect();
if args.len() != 3 {
panic!("Usage: {} <folder to zip> <header file>", args[0]);
}
let folder = &args[1];
let header_file = &args[2];
let archive_name = format!("{folder}.zip");
let file = File::create(archive_name).expect("failed to create file");
let mut zip = apk_frauder::zip_writer::ZipFileWriter::new(file, None);
zip.insert_untracked_file(
&mut File::open(header_file)
.unwrap_or_else(|_| panic!("failed to open file {header_file}")),
);
insert_to_zip(std::path::Path::new(folder), &mut zip, None);
zip.write_central_directory();
}
fn insert_to_zip(
file: &std::path::Path,
zip: &mut apk_frauder::zip_writer::ZipFileWriter<File>,
root: Option<&std::path::Path>,
) {
let root = if let Some(root) = root { root } else { file };
let internal_path =
&std::path::Path::new(root.file_name().unwrap()).join(file.strip_prefix(root).unwrap());
if file.is_dir() {
zip.insert_file(
&mut std::io::Cursor::new(vec![]),
apk_frauder::file_header::FileHeader {
external_file_attributes: apk_frauder::external_file_attributes::DIR_FILE
| apk_frauder::external_file_attributes::PERM_UR
| apk_frauder::external_file_attributes::PERM_UW
| apk_frauder::external_file_attributes::PERM_GR
| apk_frauder::external_file_attributes::PERM_OR
| apk_frauder::external_file_attributes::PERM_UX
| apk_frauder::external_file_attributes::PERM_GX
| apk_frauder::external_file_attributes::PERM_OX,
..apk_frauder::file_header::FileHeader::new_default(internal_path.to_str().unwrap())
},
None,
);
for entry in std::fs::read_dir(file).unwrap() {
let entry = entry.unwrap();
let path = entry.path();
insert_to_zip(path.as_ref(), zip, Some(root));
}
} else {
//use std::os::unix::fs::PermissionsExt;
let mut f = File::open(file)
.unwrap_or_else(|_| panic!("failed to open file {}", file.to_str().unwrap()));
//let metadata = f.metadata().unwrap();
//let permissions = metadata.permissions();
//let external_file_attributes = permissions.mode(); does not work?
let external_file_attributes = apk_frauder::external_file_attributes::REGULAR_FILE
| apk_frauder::external_file_attributes::PERM_UR
| apk_frauder::external_file_attributes::PERM_UW
| apk_frauder::external_file_attributes::PERM_GR
| apk_frauder::external_file_attributes::PERM_OR
| apk_frauder::external_file_attributes::PERM_UX
| apk_frauder::external_file_attributes::PERM_GX
| apk_frauder::external_file_attributes::PERM_OX;
zip.insert_file(
&mut f,
apk_frauder::file_header::FileHeader {
external_file_attributes,
..apk_frauder::file_header::FileHeader::new_default(internal_path.to_str().unwrap())
},
None,
);
}
}

View file

@ -1,20 +1,13 @@
use std::io::{Read, Seek, SeekFrom};
use crate::{
FileHeader, FileInfo, LocalFileHeader, Signature,
apk_signing_block::ApkSigningBlock,
apk_signing_block::Magic,
compression::CompressionMethod,
data_descriptor::{DataDescriptor, DataDescriptor32, DataDescriptor64},
apk_signing_block::ApkSigningBlock, apk_signing_block::Magic,
end_of_central_directory::EndCentralDirectory,
end_of_central_directory::Zip64EndCentralDirectory,
end_of_central_directory::Zip64EndCentralDirectoryLocator,
general_purpose_flags,
end_of_central_directory::Zip64EndCentralDirectoryLocator, general_purpose_flags, FileHeader,
FileInfo, LocalFileHeader, Signature,
};
use androscalpel_serializer::Serializable;
use anyhow::{Context, Result, bail};
use flate2::read::DeflateDecoder;
use log::{info, warn};
use std::collections::HashMap;
use std::io::{Read, Seek, SeekFrom};
#[derive(Debug, PartialEq, Eq)]
pub struct ZipFileReader<T: Read + Seek> {
@ -26,37 +19,29 @@ pub struct ZipFileReader<T: Read + Seek> {
}
impl<T: Read + Seek> ZipFileReader<T> {
pub fn new(mut reader: T) -> Result<Self> {
pub fn new(mut reader: T) -> Self {
let end_of_central_directory_off =
Self::get_end_of_central_directory_offset(&mut reader)
.context("end of centrall directory not found, probably not a zip")?;
Self::get_end_of_central_directory_offset(&mut reader).unwrap();
reader
.seek(SeekFrom::Start(end_of_central_directory_off))
.context("Failed to seek to end of central directory")?;
let end_of_central_directory = EndCentralDirectory::deserialize(&mut reader)
.context("Failed to deserialize end of central directory")?;
let zip64_end_of_central_directory = if reader
.unwrap();
let end_of_central_directory = EndCentralDirectory::deserialize(&mut reader).unwrap();
reader
.seek(SeekFrom::Start(
end_of_central_directory_off - Zip64EndCentralDirectoryLocator::SIZE as u64,
))
.is_ok()
{
let zip64_ecd_locator = Zip64EndCentralDirectoryLocator::deserialize(&mut reader).ok();
if let Some(zip64_ecd_locator) = zip64_ecd_locator {
assert_eq!(
zip64_ecd_locator.disk_number_of_zip64_end_central_directory_start,
0
);
assert!(zip64_ecd_locator.total_number_of_disks <= 1);
let zip64_edc_record_off =
zip64_ecd_locator.offset_zip64_end_of_central_directory_record;
reader
.seek(SeekFrom::Start(zip64_edc_record_off))
.context("Failed to seek to end of zip64 central directory")?;
Zip64EndCentralDirectory::deserialize(&mut reader).ok()
} else {
None
}
.unwrap();
let zip64_ecd_locator = Zip64EndCentralDirectoryLocator::deserialize(&mut reader).ok();
let zip64_end_of_central_directory = if let Some(zip64_ecd_locator) = zip64_ecd_locator {
assert_eq!(
zip64_ecd_locator.disk_number_of_zip64_end_central_directory_start,
0
);
assert!(zip64_ecd_locator.total_number_of_disks <= 1);
let zip64_edc_record_off =
zip64_ecd_locator.offset_zip64_end_of_central_directory_record;
reader.seek(SeekFrom::Start(zip64_edc_record_off)).unwrap();
Zip64EndCentralDirectory::deserialize(&mut reader).ok()
} else {
None
};
@ -75,62 +60,36 @@ impl<T: Read + Seek> ZipFileReader<T> {
zip_file
.data
.seek(SeekFrom::Start(zip_file.get_cd_offset()))
.context("Failed to seek to central directory")?;
.unwrap();
let mut size_read = 0;
let cd_size = zip_file.get_cd_size();
while size_read < cd_size {
let header = FileHeader::deserialize(&mut zip_file.data)
.context("Failed to deserialize file header")?;
let header = FileHeader::deserialize(&mut zip_file.data).unwrap();
//println!("{:#?}", header);
size_read += header.size() as u64;
let pos_in_dir = zip_file
.data
.stream_position()
.context("Failed to get stream position")?;
let pos_in_dir = zip_file.data.stream_position().unwrap();
if header.general_purpose_flags & general_purpose_flags::MASK_ENCRYPTED_CENTRAL_DIR != 0
{
bail!("Central directory encryption not supported");
panic!("Central directory encryption not supported");
}
zip_file
.data
.seek(SeekFrom::Start(header.get_offset_local_header()))
.context("Failled to seek to local header")?;
let local_header = LocalFileHeader::deserialize(&mut zip_file.data)
.context("Failed to deserialize local file header")?;
let data_descriptor = if (local_header.general_purpose_flags
.unwrap();
let local_header = LocalFileHeader::deserialize(&mut zip_file.data).unwrap();
zip_file.data.seek(SeekFrom::Start(pos_in_dir)).unwrap();
if (local_header.general_purpose_flags
& general_purpose_flags::MASK_USE_DATA_DESCRIPTOR
!= 0)
|| (header.general_purpose_flags & general_purpose_flags::MASK_USE_DATA_DESCRIPTOR
!= 0)
{
warn!("Data Descriptor support is experimental");
zip_file
.data
.seek(SeekFrom::Current(header.compressed_size as i64))
.context("failed to seek to after the file data")?;
if zip_file.zip64_end_of_central_directory.is_some() {
Some(DataDescriptor::Zip64(
DataDescriptor64::deserialize(&mut zip_file.data)
.context("Failed to deserialize data descriptor 64")?,
))
} else {
Some(DataDescriptor::Zip32(
DataDescriptor32::deserialize(&mut zip_file.data)
.context("Failed to deserialize data descriptor")?,
))
}
} else {
None
};
zip_file
.data
.seek(SeekFrom::Start(pos_in_dir))
.context("Failed to seek to position in directory")?;
panic!("Data Descriptor not yet suported");
}
zip_file.files.push(FileInfo {
local_header,
header,
data_descriptor,
});
}
assert_eq!(size_read, cd_size);
@ -138,26 +97,24 @@ impl<T: Read + Seek> ZipFileReader<T> {
zip_file
.data
.seek(SeekFrom::Start(zip_file.get_cd_offset() - 16))
.context("Failed to seek to central directory")?;
let magic =
Magic::deserialize(&mut zip_file.data).context("Failed to deserialize Magic")?;
.unwrap();
let magic = Magic::deserialize(&mut zip_file.data).unwrap();
if magic == ApkSigningBlock::MAGIC {
zip_file
.data
.seek(SeekFrom::Start(zip_file.get_cd_offset() - 16 - 8))
.context("Failed to seek to central directory")?;
let block_size = u64::deserialize(&mut zip_file.data)
.context("Failed to deserialize block size")?;
.unwrap();
let block_size = u64::deserialize(&mut zip_file.data).unwrap();
zip_file
.data
.seek(SeekFrom::Start(zip_file.get_cd_offset() - block_size - 8))
.context("Failed to seek to central directory")?;
.unwrap();
zip_file.apk_sign_block = ApkSigningBlock::deserialize(&mut zip_file.data).ok();
}
}
Ok(zip_file)
zip_file
}
pub fn is_zip64(&self) -> bool {
@ -216,7 +173,7 @@ impl<T: Read + Seek> ZipFileReader<T> {
}
pub fn get_end_of_central_directory_offset(reader: &mut T) -> Option<u64> {
let file_size = reader.seek(SeekFrom::End(0)).ok()?;
let file_size = reader.seek(SeekFrom::End(0)).unwrap();
let mut sig = Signature::default();
let mut comment_size = 0;
while sig != EndCentralDirectory::SIGNATURE {
@ -224,8 +181,8 @@ impl<T: Read + Seek> ZipFileReader<T> {
.seek(SeekFrom::End(
-(EndCentralDirectory::MIN_SIZE as i64) - comment_size,
))
.ok()?;
sig = Signature::deserialize(reader).ok()?;
.unwrap();
sig = Signature::deserialize(reader).unwrap();
comment_size += 1;
if comment_size > 65536
|| comment_size as usize + EndCentralDirectory::MIN_SIZE > file_size as usize
@ -296,8 +253,8 @@ impl<T: Read + Seek> ZipFileReader<T> {
let mut lst_offset = 0;
for file in files.iter() {
if file.get_offset_local_header() != lst_offset {
info!(
"Hole in zip before {} between 0x{:x} and 0x{:x}",
println!(
"Hole before {} between 0x{:x} and 0x{:x}",
file.get_name(),
lst_offset,
file.get_offset_local_header()
@ -309,8 +266,8 @@ impl<T: Read + Seek> ZipFileReader<T> {
if let Some(apk_sign_block) = &self.apk_sign_block {
let apk_sb_off = self.get_cd_offset() - apk_sign_block.size() as u64;
if apk_sb_off != lst_offset {
info!(
"Hole in zip before apk signing block, between 0x{:x} and 0x{:x}",
println!(
"Hole before apk signing block, between 0x{:x} and 0x{:x}",
lst_offset, apk_sb_off
);
}
@ -318,58 +275,21 @@ impl<T: Read + Seek> ZipFileReader<T> {
lst_offset = self.get_cd_offset();
}
if self.get_cd_offset() != lst_offset {
info!(
"Hole in zip before central directory between 0x{:x} and 0x{:x}",
println!(
"Hole before central directory between 0x{:x} and 0x{:x}",
lst_offset,
self.get_cd_offset()
);
}
}
pub fn get_bin(&mut self, offset: u64, size: usize) -> Result<Vec<u8>> {
self.data
.seek(SeekFrom::Start(offset))
.context("Failed to seek to data")?;
let mut data = vec![0u8; size];
self.data
.read_exact(&mut data)
.context("failed to read data")?;
/*
pub fn get_bin(&mut self, offset: u64, size: usize) -> Vec<u8> {
self.data.seek(SeekFrom::Start(offset)).unwrap();
let mut data = vec![];
for _ in 0..size {
data.push(u8::deserialize(&mut self.data).unwrap());
}
*/
Ok(data)
}
pub fn read_file_as_vec(&mut self, name: &str) -> Result<Vec<u8>> {
let file = self
.get_file_info(name)
.with_context(|| format!("Failed to get info for {name}"))?;
let offset = file.get_file_offset();
let size_c = file.header.compressed_size as usize;
let size = file.header.uncompressed_size as usize;
let compression_method = file.header.compression_method;
let mut data = vec![0u8; size_c];
self.data
.seek(SeekFrom::Start(offset))
.with_context(|| format!("Failed to seek to start of file {name} (at 0x{offset:x})"))?;
self.data
.read_exact(&mut data)
.with_context(|| format!("Failed to read data for file {name}"))?;
match compression_method {
CompressionMethod::Stored => {}
CompressionMethod::Deflated => {
let mut decomp_data = vec![0u8; size];
let mut deflater = DeflateDecoder::new(&data[..]);
deflater
.read_exact(&mut decomp_data)
.with_context(|| format!("Failed to decompress data for file {name}"))?;
data = decomp_data
}
_ => unimplemented!(),
}
Ok(data)
data
}
pub fn get_file_info(&self, name: &str) -> Option<&FileInfo> {
@ -377,29 +297,10 @@ impl<T: Read + Seek> ZipFileReader<T> {
}
pub fn get_classes_file_info(&self) -> Vec<&FileInfo> {
let files_map: HashMap<String, &FileInfo> = self
.files
self.files
.iter()
.by_ref()
.filter(|&file| match_dexfile_name(&file.get_name()))
.map(|file| (file.get_name(), file))
.collect();
let mut files = vec![];
let mut i = 0;
loop {
let name = if i == 0 {
"classes.dex".into()
} else {
format!("classes{}.dex", i + 1)
};
if let Some(file) = files_map.get(&name) {
files.push(*file);
} else {
break;
}
i += 1;
}
files
.collect()
}
}

View file

@ -2,13 +2,10 @@ use std::io;
use std::io::{Cursor, Read, Seek, SeekFrom, Write};
use crate::compression::CompressionMethod;
use crate::data_descriptor::{DataDescriptor, DataDescriptor32, DataDescriptor64};
use crate::end_of_central_directory::{
EndCentralDirectory, Zip64EndCentralDirectory, Zip64EndCentralDirectoryLocator,
};
use crate::{
general_purpose_flags, ExtraField, FileHeader, FileInfo, LocalFileHeader, ZipFileReader,
};
use crate::{general_purpose_flags, FileHeader, FileInfo, LocalFileHeader, ZipFileReader};
use androscalpel_serializer::Serializable;
use flate2::write::DeflateEncoder;
use flate2::{Compression, CrcWriter};
@ -57,6 +54,24 @@ impl<T: Write> ZipFileWriter<T> {
self.files.push(file_info);
}
/// Insert a file at the begining of archive that will not be tracked.
pub fn insert_untracked_file<U: Read + Seek>(&mut self, file: &mut U) {
let pos = file.stream_position().unwrap();
let pos_end = file.seek(SeekFrom::End(0)).unwrap();
file.seek(SeekFrom::Start(pos)).unwrap();
let file_size = pos_end - pos;
let mut remaining_size = file_size;
let mut buffer = [0u8; 4096];
while remaining_size != 0 {
let read = file
.read(&mut buffer[0..core::cmp::min(4096, remaining_size as usize)])
.unwrap();
self.data.write_all(&buffer[0..read]).unwrap();
remaining_size -= read as u64;
}
self.current_offset += file_size;
}
/// Insert a file
/// `header` provide for the values that are not computed from the file.
/// `local_header` can be provided is information in the local header differ
@ -66,16 +81,11 @@ impl<T: Write> ZipFileWriter<T> {
file: &mut U,
mut header: FileHeader,
local_header: Option<LocalFileHeader>,
data_descriptor: Option<DataDescriptor>,
) {
assert!(header.general_purpose_flags & general_purpose_flags::MASK_ENCRYPTED == 0);
//assert!(
// header.general_purpose_flags & general_purpose_flags::MASK_USE_DATA_DESCRIPTOR == 0,
// "Writing file with data_descriptor is not yet implemented"
//); // TODO
let mut use_data_descriptor = data_descriptor.is_some()
|| ((header.general_purpose_flags & general_purpose_flags::MASK_USE_DATA_DESCRIPTOR)
!= 0);
assert!(
header.general_purpose_flags & general_purpose_flags::MASK_USE_DATA_DESCRIPTOR == 0
); // TODO
assert!(header.general_purpose_flags & general_purpose_flags::MASK_STRONG_ENCRYPTION == 0);
assert!(
header.general_purpose_flags & general_purpose_flags::MASK_ENCRYPTED_CENTRAL_DIR == 0
@ -83,10 +93,9 @@ impl<T: Write> ZipFileWriter<T> {
assert!(
header.general_purpose_flags
& (general_purpose_flags::MASK_COMPRESS_OPTION_1
| general_purpose_flags::MASK_COMPRESS_OPTION_2)
| general_purpose_flags::MASK_COMPRESS_OPTION_2
| general_purpose_flags::MASK_UTF8_FILENAME)
== 0
|| header.compression_method == CompressionMethod::Deflated
|| header.compression_method == CompressionMethod::Stored
);
assert!(
header.compression_method == CompressionMethod::Deflated
@ -94,12 +103,9 @@ impl<T: Write> ZipFileWriter<T> {
);
let mut local_header = if let Some(header) = local_header {
assert!(header.general_purpose_flags & general_purpose_flags::MASK_ENCRYPTED == 0);
//assert!(
// header.general_purpose_flags & general_purpose_flags::MASK_USE_DATA_DESCRIPTOR == 0,
// "Writing file with data_descriptor is not yet implemented"
//); // TODO
use_data_descriptor |=
header.general_purpose_flags & general_purpose_flags::MASK_USE_DATA_DESCRIPTOR != 0;
assert!(
header.general_purpose_flags & general_purpose_flags::MASK_USE_DATA_DESCRIPTOR == 0
); // TODO
assert!(
header.general_purpose_flags & general_purpose_flags::MASK_STRONG_ENCRYPTION == 0
);
@ -110,29 +116,23 @@ impl<T: Write> ZipFileWriter<T> {
assert!(
header.general_purpose_flags
& (general_purpose_flags::MASK_COMPRESS_OPTION_1
| general_purpose_flags::MASK_COMPRESS_OPTION_2)
| general_purpose_flags::MASK_COMPRESS_OPTION_2
| general_purpose_flags::MASK_UTF8_FILENAME)
== 0
|| header.compression_method == CompressionMethod::Deflated
|| header.compression_method == CompressionMethod::Stored
);
assert!(
header.compression_method == CompressionMethod::Deflated
|| header.compression_method == CompressionMethod::Stored
);
let mut general_purpose_flags = header.general_purpose_flags;
// We only support options for Deflate parameter and data descriptor
if header.compression_method == CompressionMethod::Deflated {
general_purpose_flags &= general_purpose_flags::MASK_COMPRESS_OPTION_1
| general_purpose_flags::MASK_COMPRESS_OPTION_2;
} else {
general_purpose_flags = 0
}
if use_data_descriptor {
general_purpose_flags |= general_purpose_flags::MASK_USE_DATA_DESCRIPTOR;
}
LocalFileHeader {
version_needed_to_extract: header.version_needed_to_extract,
general_purpose_flags,
general_purpose_flags: if header.compression_method == CompressionMethod::Deflated {
header.general_purpose_flags
& (general_purpose_flags::MASK_COMPRESS_OPTION_1
| general_purpose_flags::MASK_COMPRESS_OPTION_2)
} else {
0
},
compression_method: header.compression_method,
last_mod_file_time: header.last_mod_file_time,
last_mod_file_data: header.last_mod_file_data,
@ -153,10 +153,6 @@ impl<T: Write> ZipFileWriter<T> {
| general_purpose_flags::MASK_COMPRESS_OPTION_2)
} else {
0
} | if use_data_descriptor {
general_purpose_flags::MASK_USE_DATA_DESCRIPTOR
} else {
0
},
compression_method: header.compression_method,
last_mod_file_time: header.last_mod_file_time,
@ -188,17 +184,9 @@ impl<T: Write> ZipFileWriter<T> {
CompressionMethod::Deflated => {
// TODO: find a way to do this in place, the compressed data can be large, and storing it
// in memory is bad, but Deflate consume the Reader, so we cannot juste give it self.data
let option = match header.general_purpose_flags
& (general_purpose_flags::MASK_COMPRESS_OPTION_1
| general_purpose_flags::MASK_COMPRESS_OPTION_2)
{
0b000 => Compression::default(),
0b010 => Compression::best(),
0b100 => Compression::fast(),
0b110 => panic!("Super Fast deflate compression not supported"),
flag => panic!("Something is verry wrong: {flag:b}"),
};
let mut compressor = CrcWriter::new(DeflateEncoder::new(Vec::new(), option));
// TODO: Compression::default -> use flag?
let mut compressor =
CrcWriter::new(DeflateEncoder::new(Vec::new(), Compression::default()));
io::copy(file, &mut compressor).unwrap();
local_header.crc_32 = compressor.crc().sum();
compressor.into_inner().finish().unwrap()
@ -216,78 +204,15 @@ impl<T: Write> ZipFileWriter<T> {
header.set_uncompressed_size(local_header.get_uncompressed_size());
header.set_offset_local_header(header_offset);
let use_z64 = header
.extra_field
.iter()
.any(|f| matches!(f, ExtraField::Zip64(_)));
let data_descriptor = if use_data_descriptor {
match data_descriptor {
None if use_z64 => Some(DataDescriptor::Zip64(DataDescriptor64 {
crc_32: local_header.crc_32,
compressed_size: local_header.get_compressed_size(),
uncompressed_size: local_header.get_uncompressed_size(),
use_signature: true,
})),
None => Some(DataDescriptor::Zip32(DataDescriptor32 {
crc_32: local_header.crc_32,
compressed_size: local_header.get_compressed_size() as u32,
uncompressed_size: local_header.get_uncompressed_size() as u32,
use_signature: true,
})),
Some(DataDescriptor::Zip32(DataDescriptor32 { use_signature, .. })) if use_z64 => {
Some(DataDescriptor::Zip64(DataDescriptor64 {
crc_32: local_header.crc_32,
compressed_size: local_header.get_compressed_size(),
uncompressed_size: local_header.get_uncompressed_size(),
use_signature,
}))
}
Some(DataDescriptor::Zip64(DataDescriptor64 { use_signature, .. })) if !use_z64 => {
Some(DataDescriptor::Zip32(DataDescriptor32 {
crc_32: local_header.crc_32,
compressed_size: local_header.get_compressed_size() as u32,
uncompressed_size: local_header.get_uncompressed_size() as u32,
use_signature,
}))
}
Some(DataDescriptor::Zip64(DataDescriptor64 { use_signature, .. })) => {
Some(DataDescriptor::Zip64(DataDescriptor64 {
crc_32: local_header.crc_32,
compressed_size: local_header.get_compressed_size(),
uncompressed_size: local_header.get_uncompressed_size(),
use_signature,
}))
}
Some(DataDescriptor::Zip32(DataDescriptor32 { use_signature, .. })) => {
Some(DataDescriptor::Zip32(DataDescriptor32 {
crc_32: local_header.crc_32,
compressed_size: local_header.get_compressed_size() as u32,
uncompressed_size: local_header.get_uncompressed_size() as u32,
use_signature,
}))
}
}
} else {
None
};
match &data_descriptor {
Some(DataDescriptor::Zip32(data_descriptor)) => {
data_descriptor.serialize(&mut self.data).unwrap();
self.current_offset += data_descriptor.size() as u64;
}
Some(DataDescriptor::Zip64(data_descriptor)) => {
data_descriptor.serialize(&mut self.data).unwrap();
self.current_offset += data_descriptor.size() as u64;
}
_ => {}
}
// TODO: compression flags are not used right now, set to zero
local_header.general_purpose_flags &= !(general_purpose_flags::MASK_COMPRESS_OPTION_1
| general_purpose_flags::MASK_COMPRESS_OPTION_2);
header.general_purpose_flags &= !(general_purpose_flags::MASK_COMPRESS_OPTION_1
| general_purpose_flags::MASK_COMPRESS_OPTION_2);
let file_info = FileInfo {
local_header,
header,
data_descriptor,
};
self.files.push(file_info);
}