Compare commits
1 commit
main
...
self_unpac
| Author | SHA1 | Date | |
|---|---|---|---|
| 76a39f35df |
63 changed files with 32217 additions and 669353 deletions
1129
Cargo.lock
generated
1129
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -4,6 +4,5 @@ members = [
|
|||
"apk_frauder",
|
||||
"androscalpel_serializer",
|
||||
"androscalpel_serializer_derive",
|
||||
"androscalpel", "androscalpel_platform_api_list",
|
||||
"androscalpel",
|
||||
]
|
||||
resolver = "2"
|
||||
|
|
|
|||
17
README.md
17
README.md
|
|
@ -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.
|
||||
8
TODO.md
8
TODO.md
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
@ -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();
|
||||
}
|
||||
16
androscalpel/pyproject.toml
Normal file
16
androscalpel/pyproject.toml
Normal 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"]
|
||||
|
|
@ -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
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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("&", "&")
|
||||
.replace(">", ">")
|
||||
.replace("<", "<")
|
||||
.replace("\"", """)
|
||||
)
|
||||
} 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("&", "&")
|
||||
.replace(">", ">")
|
||||
.replace("<", "<")
|
||||
.replace("\"", """)
|
||||
//.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
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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, ¶meters),
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<_>>()?,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
@ -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": []
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
File diff suppressed because one or more lines are too long
|
|
@ -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());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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": []
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
|
@ -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 = []
|
||||
|
|
@ -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.
|
||||
|
|
@ -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
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
|
|
@ -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" }
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
(
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
- replace panic/unwrap/expect with results
|
||||
- tests
|
||||
- write zip with data descriptor
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue