add support for read hiddenapi
This commit is contained in:
parent
df9d258780
commit
437ecbeecc
17 changed files with 764 additions and 92 deletions
32
Cargo.lock
generated
32
Cargo.lock
generated
|
|
@ -335,6 +335,12 @@ dependencies = [
|
|||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.17"
|
||||
|
|
@ -352,9 +358,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "pyo3"
|
||||
version = "0.20.0"
|
||||
version = "0.21.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04e8453b658fe480c3e70c8ed4e3d3ec33eb74988bd186561b0cc66b85c3bc4b"
|
||||
checksum = "a5e00b96a521718e08e03b1a622f01c8a8deb50719335de3f60b3b3950f069d8"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cfg-if",
|
||||
|
|
@ -362,6 +368,7 @@ dependencies = [
|
|||
"libc",
|
||||
"memoffset",
|
||||
"parking_lot",
|
||||
"portable-atomic",
|
||||
"pyo3-build-config",
|
||||
"pyo3-ffi",
|
||||
"pyo3-macros",
|
||||
|
|
@ -370,9 +377,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "pyo3-build-config"
|
||||
version = "0.20.0"
|
||||
version = "0.21.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a96fe70b176a89cff78f2fa7b3c930081e163d5379b4dcdf993e3ae29ca662e5"
|
||||
checksum = "7883df5835fafdad87c0d888b266c8ec0f4c9ca48a5bed6bbb592e8dedee1b50"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"target-lexicon",
|
||||
|
|
@ -380,9 +387,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "pyo3-ffi"
|
||||
version = "0.20.0"
|
||||
version = "0.21.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "214929900fd25e6604661ed9cf349727c8920d47deff196c4e28165a6ef2a96b"
|
||||
checksum = "01be5843dc60b916ab4dad1dca6d20b9b4e6ddc8e15f50c47fe6d85f1fb97403"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"pyo3-build-config",
|
||||
|
|
@ -390,9 +397,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "pyo3-log"
|
||||
version = "0.9.0"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c10808ee7250403bedb24bc30c32493e93875fef7ba3e4292226fe924f398bd"
|
||||
checksum = "2af49834b8d2ecd555177e63b273b708dea75150abc6f5341d0a6e1a9623976c"
|
||||
dependencies = [
|
||||
"arc-swap",
|
||||
"log",
|
||||
|
|
@ -401,9 +408,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "pyo3-macros"
|
||||
version = "0.20.0"
|
||||
version = "0.21.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dac53072f717aa1bfa4db832b39de8c875b7c7af4f4a6fe93cdbf9264cf8383b"
|
||||
checksum = "77b34069fc0682e11b31dbd10321cbf94808394c56fd996796ce45217dfac53c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"pyo3-macros-backend",
|
||||
|
|
@ -413,12 +420,13 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "pyo3-macros-backend"
|
||||
version = "0.20.0"
|
||||
version = "0.21.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7774b5a8282bd4f25f803b1f0d945120be959a36c72e08e7cd031c792fdfd424"
|
||||
checksum = "08260721f32db5e1a5beae69a55553f56b99bd0e1c3e6e0a5e8851a9d0f5a85c"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"pyo3-build-config",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -14,8 +14,8 @@ androscalpel_serializer = { version = "0.1.0", path = "../androscalpel_serialize
|
|||
anyhow = { version = "1.0.75", features = ["backtrace"] }
|
||||
apk_frauder = { version = "0.1.0", path = "../apk_frauder" }
|
||||
log = "0.4.20"
|
||||
pyo3 = { version = "0.20.0", features = ["anyhow", "abi3-py38"] }
|
||||
pyo3-log = "0.9.0"
|
||||
pyo3 = { version = "0.21.0", features = ["anyhow", "abi3-py38"] }
|
||||
pyo3-log = "0.10.0"
|
||||
rayon = "1.9.0"
|
||||
serde = { version = "1.0.195", features = ["derive"] }
|
||||
serde_json = "1.0.111"
|
||||
|
|
|
|||
|
|
@ -35,7 +35,8 @@ impl Apk {
|
|||
let classes = dex
|
||||
.get_class_defs()
|
||||
.par_iter()
|
||||
.map(|class| self.get_class_from_dex_file(class, &dex))
|
||||
.enumerate()
|
||||
.map(|(idx, class)| self.get_class_from_dex_file(class, idx, &dex))
|
||||
.map(|class| class.map(|class| (class.descriptor.clone(), class)))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
self.classes.par_extend(classes);
|
||||
|
|
@ -45,9 +46,12 @@ impl Apk {
|
|||
}
|
||||
|
||||
/// Extract a class from a dex file reader.
|
||||
/// `class_item_idx` if the index of the `class_def_item` of the class, **not** the
|
||||
/// `class_idx`.
|
||||
fn get_class_from_dex_file(
|
||||
&self,
|
||||
class_item: &ClassDefItem,
|
||||
class_item_idx: usize,
|
||||
dex: &DexFileReader,
|
||||
) -> Result<Class> {
|
||||
let descriptor = Self::get_id_type_from_idx(class_item.class_idx as usize, dex)?;
|
||||
|
|
@ -112,6 +116,8 @@ impl Apk {
|
|||
}
|
||||
}
|
||||
|
||||
let hiddenapi = dex.get_class_hiddenapi_flags(class_item_idx)?;
|
||||
let mut hiddenapi_i = 0;
|
||||
let mut static_fields_list = vec![];
|
||||
let mut instance_fields_list = vec![];
|
||||
let mut direct_methods = HashMap::new();
|
||||
|
|
@ -123,12 +129,32 @@ impl Apk {
|
|||
Self::get_field_list_from_encoded_field_list(&data.static_fields, dex)?;
|
||||
instance_fields_list =
|
||||
Self::get_field_list_from_encoded_field_list(&data.instance_fields, dex)?;
|
||||
for method in Self::get_method_list_from_encoded_field_list(&data.direct_methods, dex)?
|
||||
if let Some(hiddenapi) = &hiddenapi {
|
||||
for field in &mut static_fields_list {
|
||||
field.hiddenapi = Some((&hiddenapi[hiddenapi_i]).into());
|
||||
hiddenapi_i += 1;
|
||||
}
|
||||
for field in &mut instance_fields_list {
|
||||
field.hiddenapi = Some((&hiddenapi[hiddenapi_i]).into());
|
||||
hiddenapi_i += 1;
|
||||
}
|
||||
}
|
||||
for mut method in
|
||||
Self::get_method_list_from_encoded_field_list(&data.direct_methods, dex)?
|
||||
{
|
||||
if let Some(hiddenapi) = &hiddenapi {
|
||||
method.hiddenapi = Some((&hiddenapi[hiddenapi_i]).into());
|
||||
hiddenapi_i += 1;
|
||||
}
|
||||
direct_methods.insert(method.descriptor.clone(), method);
|
||||
}
|
||||
for method in Self::get_method_list_from_encoded_field_list(&data.virtual_methods, dex)?
|
||||
for mut method in
|
||||
Self::get_method_list_from_encoded_field_list(&data.virtual_methods, dex)?
|
||||
{
|
||||
if let Some(hiddenapi) = &hiddenapi {
|
||||
method.hiddenapi = Some((&hiddenapi[hiddenapi_i]).into());
|
||||
hiddenapi_i += 1;
|
||||
}
|
||||
virtual_methods.insert(method.descriptor.clone(), method);
|
||||
}
|
||||
}
|
||||
|
|
@ -617,6 +643,7 @@ impl Apk {
|
|||
is_synthetic,
|
||||
is_enum,
|
||||
value: None,
|
||||
hiddenapi: None,
|
||||
annotations: vec![],
|
||||
})
|
||||
}
|
||||
|
|
@ -746,6 +773,7 @@ impl Apk {
|
|||
is_declared_syncrhonized,
|
||||
annotations: vec![],
|
||||
parameters_annotations: vec![],
|
||||
hiddenapi: None,
|
||||
code,
|
||||
})
|
||||
}
|
||||
|
|
@ -2444,7 +2472,7 @@ impl Apk {
|
|||
Ok(self
|
||||
.gen_raw_dex()?
|
||||
.into_iter()
|
||||
.map(|bytes| PyBytes::new(py, &bytes).into())
|
||||
.map(|bytes| PyBytes::new_bound(py, &bytes).into())
|
||||
.collect())
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -316,7 +316,7 @@ impl Class {
|
|||
.any(|field| field.value.is_some())
|
||||
}
|
||||
|
||||
/// If the class or its fields/methods have annotations
|
||||
/// Check if the class or its fields/methods have annotations
|
||||
pub fn has_annotations(&self) -> bool {
|
||||
!self.annotations.is_empty()
|
||||
|| self
|
||||
|
|
@ -330,11 +330,30 @@ impl Class {
|
|||
|| self
|
||||
.direct_methods
|
||||
.values()
|
||||
.any(|field| field.has_annotations())
|
||||
.any(|method| method.has_annotations())
|
||||
|| self
|
||||
.virtual_methods
|
||||
.values()
|
||||
.any(|field| field.has_annotations())
|
||||
.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())
|
||||
}
|
||||
|
||||
/// Return the binary representation of access flags.
|
||||
|
|
|
|||
|
|
@ -667,11 +667,11 @@ impl IdField {
|
|||
/// ```
|
||||
/// use androscalpel::IdField;
|
||||
///
|
||||
/// let proto = IdField::from_smali("Ljava/lang/annotation/ElementType;->METHOD:Ljava/lang/annotation/ElementType;").unwrap();
|
||||
/// let proto = IdField::from_smali("Ljava/lang/annotation/ElementType;->FIELD:Ljava/lang/annotation/ElementType;").unwrap();
|
||||
/// assert_eq!(
|
||||
/// proto,
|
||||
/// IdField::new(
|
||||
/// "METHOD".into(),
|
||||
/// "FIELD".into(),
|
||||
/// IdType::class("java/lang/annotation/ElementType"),
|
||||
/// IdType::class("java/lang/annotation/ElementType"),
|
||||
/// )
|
||||
|
|
|
|||
|
|
@ -138,10 +138,10 @@ impl Hash for DexString {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn as_dex_string(obj: &PyAny) -> PyResult<DexString> {
|
||||
if let Ok(string) = DexString::extract(obj) {
|
||||
pub fn as_dex_string(obj: &Bound<'_, PyAny>) -> PyResult<DexString> {
|
||||
if let Ok(string) = DexString::extract_bound(obj) {
|
||||
Ok(string)
|
||||
} else if let Ok(string) = String::extract(obj) {
|
||||
} else if let Ok(string) = String::extract_bound(obj) {
|
||||
Ok(string.into())
|
||||
} else {
|
||||
Err(PyErr::new::<PyTypeError, _>(format!(
|
||||
|
|
@ -198,10 +198,10 @@ impl DexString {
|
|||
strings
|
||||
}
|
||||
|
||||
fn __richcmp__(&self, other: &PyAny, op: CompareOp) -> PyResult<bool> {
|
||||
fn __richcmp__(&self, other: &Bound<'_, PyAny>, op: CompareOp) -> PyResult<bool> {
|
||||
let other: Self = other
|
||||
.extract()
|
||||
.or(<String as FromPyObject>::extract(other).map(|string| string.into()))?;
|
||||
.or(<String as FromPyObject>::extract_bound(other).map(|string| string.into()))?;
|
||||
Ok(op.matches(self.0.cmp(&other.0)))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use std::io::{Cursor, Seek, SeekFrom, Write};
|
|||
|
||||
use adler::Adler32;
|
||||
use anyhow::{anyhow, bail, Context};
|
||||
use log::debug;
|
||||
use log::{debug, warn};
|
||||
use sha1::{Digest, Sha1};
|
||||
|
||||
use crate::Result;
|
||||
|
|
@ -180,6 +180,12 @@ impl DexWriter {
|
|||
|
||||
pub fn add_class(&mut self, class: &Class) -> Result<(), DexWritterError> {
|
||||
debug!("Adding class {} to dex builder", class.descriptor.__str__());
|
||||
if class.has_hiddenapi() {
|
||||
warn!(
|
||||
"Class {} has hidden API data: serialization of hidden API no yet implemented",
|
||||
class.descriptor.__str__()
|
||||
);
|
||||
}
|
||||
let new_strings = class.get_all_strings();
|
||||
|
||||
let new_types = class.get_all_types();
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ use std::collections::HashSet;
|
|||
use pyo3::prelude::*;
|
||||
|
||||
use crate::{
|
||||
DexAnnotationItem, DexString, DexValue, IdField, IdMethod, IdMethodType, IdType, MethodHandle,
|
||||
Result,
|
||||
DexAnnotationItem, DexString, DexValue, HiddenApiData, IdField, IdMethod, IdMethodType, IdType,
|
||||
MethodHandle, Result,
|
||||
};
|
||||
use androscalpel_serializer::consts::*;
|
||||
|
||||
|
|
@ -44,6 +44,9 @@ pub struct Field {
|
|||
/// The annotations for this field
|
||||
#[pyo3(get)]
|
||||
pub annotations: Vec<DexAnnotationItem>,
|
||||
/// Hidden Api data.
|
||||
#[pyo3(get)]
|
||||
pub hiddenapi: Option<HiddenApiData>,
|
||||
}
|
||||
|
||||
/// Represent the visibility of a field
|
||||
|
|
@ -80,6 +83,7 @@ impl Field {
|
|||
is_enum: false,
|
||||
value: None,
|
||||
annotations: vec![],
|
||||
hiddenapi: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -127,7 +131,7 @@ impl Field {
|
|||
|
||||
/// Set the value from a python object
|
||||
#[setter]
|
||||
pub fn set_value(&mut self, ob: &PyAny) -> PyResult<()> {
|
||||
pub fn set_value(&mut self, ob: &Bound<'_, PyAny>) -> PyResult<()> {
|
||||
self.value = Some(ob.extract()?);
|
||||
// TODO: check type match
|
||||
Ok(())
|
||||
|
|
|
|||
191
androscalpel/src/hiddenapi.rs
Normal file
191
androscalpel/src/hiddenapi.rs
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
//! Representation of the hidden API data
|
||||
|
||||
use log::warn;
|
||||
use pyo3::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[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()
|
||||
}
|
||||
}
|
||||
|
||||
#[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()
|
||||
}
|
||||
}
|
||||
|
||||
#[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()
|
||||
}
|
||||
}
|
||||
|
|
@ -14,6 +14,7 @@ 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;
|
||||
|
|
@ -29,6 +30,7 @@ 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 method::*;
|
||||
pub use method_handle::*;
|
||||
|
|
@ -40,7 +42,7 @@ mod tests;
|
|||
|
||||
/// Androscalpel.
|
||||
#[pymodule]
|
||||
fn androscalpel(py: Python, m: &PyModule) -> PyResult<()> {
|
||||
fn androscalpel(py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
pyo3_log::init();
|
||||
m.add_class::<DexNull>()?;
|
||||
m.add_class::<DexBoolean>()?;
|
||||
|
|
@ -69,6 +71,10 @@ fn androscalpel(py: Python, m: &PyModule) -> PyResult<()> {
|
|||
m.add_class::<InvokeDirect>()?;
|
||||
m.add_class::<InvokeInterface>()?;
|
||||
|
||||
m.add_class::<HiddenApiData>()?;
|
||||
m.add_class::<HiddenApiPermission>()?;
|
||||
m.add_class::<HiddenApiDomain>()?;
|
||||
|
||||
m.add_class::<DexAnnotationItem>()?;
|
||||
m.add_class::<DexAnnotation>()?;
|
||||
m.add_class::<DexArray>()?;
|
||||
|
|
@ -80,18 +86,18 @@ fn androscalpel(py: Python, m: &PyModule) -> PyResult<()> {
|
|||
m.add_class::<Code>()?;
|
||||
m.add_class::<Apk>()?;
|
||||
|
||||
let ins_module = PyModule::new(py, "ins")?;
|
||||
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)?;
|
||||
let ins_module = PyModule::new_bound(py, "ins")?;
|
||||
androscalpel_ins(py, &ins_module)?;
|
||||
m.add_submodule(&ins_module)?;
|
||||
let utils_module = PyModule::new_bound(py, "utils")?;
|
||||
py_utils::export_module(py, &utils_module)?;
|
||||
m.add_submodule(&utils_module)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Dalvik opcode for Androscalpel.
|
||||
fn androscalpel_ins(_py: Python, m: &PyModule) -> PyResult<()> {
|
||||
fn androscalpel_ins(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
m.add_class::<ins::CallSite>()?;
|
||||
m.add_class::<ins::Nop>()?;
|
||||
m.add_class::<ins::Move>()?;
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ use std::collections::HashSet;
|
|||
use pyo3::prelude::*;
|
||||
|
||||
use crate::{
|
||||
Code, DexAnnotationItem, DexString, IdField, IdMethod, IdMethodType, IdType, MethodHandle,
|
||||
Result,
|
||||
Code, DexAnnotationItem, DexString, HiddenApiData, IdField, IdMethod, IdMethodType, IdType,
|
||||
MethodHandle, Result,
|
||||
};
|
||||
use androscalpel_serializer::consts::*;
|
||||
|
||||
|
|
@ -62,6 +62,9 @@ pub struct Method {
|
|||
/// The annotations for the parameters of this method method
|
||||
#[pyo3(get)]
|
||||
pub parameters_annotations: Vec<Vec<DexAnnotationItem>>,
|
||||
/// Hidden Api data.
|
||||
#[pyo3(get)]
|
||||
pub hiddenapi: Option<HiddenApiData>,
|
||||
|
||||
/// The code of the method
|
||||
#[pyo3(get)]
|
||||
|
|
@ -109,6 +112,7 @@ impl Method {
|
|||
annotations: vec![],
|
||||
parameters_annotations: vec![],
|
||||
code: None,
|
||||
hiddenapi: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,18 +17,18 @@ use apk_frauder::{end_of_central_directory::EndCentralDirectory, ZipFileReader};
|
|||
/// Convert an integer to the uleb128 byte encoding
|
||||
#[pyfunction]
|
||||
pub fn int_to_uleb128(py: Python, x: u32) -> Result<PyObject> {
|
||||
Ok(PyBytes::new(py, &Uleb128(x).serialize_to_vec()?).into())
|
||||
Ok(PyBytes::new_bound(py, &Uleb128(x).serialize_to_vec()?).into())
|
||||
}
|
||||
|
||||
/// Convert an integer to the uleb128p1 byte encoding
|
||||
#[pyfunction]
|
||||
pub fn int_to_uleb128p1(py: Python, x: u32) -> Result<PyObject> {
|
||||
Ok(PyBytes::new(py, &Uleb128p1(x).serialize_to_vec()?).into())
|
||||
Ok(PyBytes::new_bound(py, &Uleb128p1(x).serialize_to_vec()?).into())
|
||||
}
|
||||
/// Convert an integer to the sleb128 byte encoding
|
||||
#[pyfunction]
|
||||
pub fn int_to_sleb128(py: Python, x: i32) -> Result<PyObject> {
|
||||
Ok(PyBytes::new(py, &Sleb128(x).serialize_to_vec()?).into())
|
||||
Ok(PyBytes::new_bound(py, &Sleb128(x).serialize_to_vec()?).into())
|
||||
}
|
||||
|
||||
/// Decode an uleb128 encoded integer
|
||||
|
|
@ -112,17 +112,21 @@ pub fn is_zip(file: PathBuf) -> bool {
|
|||
pub fn replace_dex(
|
||||
apk: PathBuf,
|
||||
dst: PathBuf,
|
||||
dexfiles: Vec<&[u8]>,
|
||||
dexfiles: Vec<&PyBytes>,
|
||||
keystore: PathBuf,
|
||||
zipalign: Option<PathBuf>,
|
||||
apksigner: Option<PathBuf>,
|
||||
additionnal_files: Option<HashMap<String, Option<&[u8]>>>,
|
||||
additionnal_files: Option<HashMap<String, Option<&PyBytes>>>,
|
||||
) {
|
||||
let mut dexfiles: Vec<_> = dexfiles.iter().map(Cursor::new).collect();
|
||||
let mut dexfiles: Vec<_> = dexfiles
|
||||
.into_iter()
|
||||
.map(PyBytes::as_bytes)
|
||||
.map(Cursor::new)
|
||||
.collect();
|
||||
let additionnal_files: Option<HashMap<_, _>> = additionnal_files.map(|additionnal_files| {
|
||||
additionnal_files
|
||||
.into_iter()
|
||||
.map(|(k, v)| (k, v.map(Cursor::new)))
|
||||
.map(|(k, v)| (k, v.map(PyBytes::as_bytes).map(Cursor::new)))
|
||||
.collect()
|
||||
});
|
||||
apk_frauder::replace_dex(
|
||||
|
|
@ -137,7 +141,7 @@ pub fn replace_dex(
|
|||
}
|
||||
|
||||
/// export the function in a python module
|
||||
pub(crate) fn export_module(_py: Python, m: &PyModule) -> PyResult<()> {
|
||||
pub(crate) fn export_module(_py: Python, m: &Bound<'_, 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)?)?;
|
||||
|
|
|
|||
103
androscalpel/src/tests/apktool_parse_hiddenapi.py
Normal file
103
androscalpel/src/tests/apktool_parse_hiddenapi.py
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
#!/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
androscalpel/src/tests/core-oj-33_hiddenapi.json
Normal file
1
androscalpel/src/tests/core-oj-33_hiddenapi.json
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -1,6 +1,7 @@
|
|||
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;
|
||||
|
|
@ -548,3 +549,130 @@ fn test_2_from_json() {
|
|||
new_apk.add_dex_file(&dex).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(&dex_raw).unwrap();
|
||||
for cls in apktool_result.as_object().unwrap().keys() {
|
||||
assert!(
|
||||
apk.classes
|
||||
.get(&dex_id::IdType(cls.as_str().into()))
|
||||
.is_some(),
|
||||
"{cls} not found in core-oj-33_classes.dex"
|
||||
);
|
||||
}
|
||||
for cls in apk.classes.keys() {
|
||||
assert!(
|
||||
apktool_result.get::<String>((&cls.0).into()).is_some(),
|
||||
"{} not found in core-oj-33_hiddenapi.json",
|
||||
cls.__str__()
|
||||
);
|
||||
}
|
||||
for (_, cls) in &apk.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(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
//! Parser for a .dex file.
|
||||
|
||||
use crate::{
|
||||
CallSiteIdItem, ClassDefItem, EndianConstant, Error, FieldIdItem, HeaderItem, MapItemType,
|
||||
MapList, MethodHandleItem, MethodIdItem, ProtoIdItem, Result, Serializable, StringDataItem,
|
||||
StringIdItem, TypeIdItem,
|
||||
CallSiteIdItem, ClassDataItem, ClassDefItem, EndianConstant, Error, FieldIdItem, HeaderItem,
|
||||
HiddenApiFlags, HiddenapiClassDataItem, MapItemType, MapList, MethodHandleItem, MethodIdItem,
|
||||
ProtoIdItem, Result, Serializable, StringDataItem, StringIdItem, TypeIdItem,
|
||||
};
|
||||
use log::{error, info, warn};
|
||||
use std::io::{Cursor, Seek, SeekFrom};
|
||||
|
|
@ -28,6 +28,7 @@ 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,
|
||||
}
|
||||
|
||||
|
|
@ -48,6 +49,7 @@ 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 {
|
||||
|
|
@ -99,6 +101,15 @@ 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)
|
||||
}
|
||||
|
|
@ -107,39 +118,39 @@ impl<'a> DexFileReader<'a> {
|
|||
pub fn get_header(&self) -> &HeaderItem {
|
||||
&self.header
|
||||
}
|
||||
/// Retunr the file [`StringIdItem`] list.
|
||||
/// Return the file [`StringIdItem`] list.
|
||||
pub fn get_string_ids(&self) -> &[StringIdItem] {
|
||||
&self.string_ids
|
||||
}
|
||||
/// Retunr the file [`TypeIdItem`] list.
|
||||
/// Return the file [`TypeIdItem`] list.
|
||||
pub fn get_type_ids(&self) -> &[TypeIdItem] {
|
||||
&self.type_ids
|
||||
}
|
||||
/// Retunr the file [`ProtoIdItem`] list.
|
||||
/// Return the file [`ProtoIdItem`] list.
|
||||
pub fn get_proto_ids(&self) -> &[ProtoIdItem] {
|
||||
&self.proto_ids
|
||||
}
|
||||
/// Retunr the file [`FieldIdItem`] list.
|
||||
/// Return the file [`FieldIdItem`] list.
|
||||
pub fn get_field_ids(&self) -> &[FieldIdItem] {
|
||||
&self.field_ids
|
||||
}
|
||||
/// Retunr the file [`MethodIdItem`] list.
|
||||
/// Return the file [`MethodIdItem`] list.
|
||||
pub fn get_method_ids(&self) -> &[MethodIdItem] {
|
||||
&self.method_ids
|
||||
}
|
||||
/// Retunr the file [`ClassDefItem`] list.
|
||||
/// Return the file [`ClassDefItem`] list.
|
||||
pub fn get_class_defs(&self) -> &[ClassDefItem] {
|
||||
&self.class_defs
|
||||
}
|
||||
/// Retunr the file [`CallSiteIdItem`] list.
|
||||
/// Return the file [`CallSiteIdItem`] list.
|
||||
pub fn get_call_site_ids(&self) -> &[CallSiteIdItem] {
|
||||
&self.call_site_ids
|
||||
}
|
||||
/// Retunr the file [`MethodHandleItem`] list.
|
||||
/// Return the file [`MethodHandleItem`] list.
|
||||
pub fn get_method_handles(&self) -> &[MethodHandleItem] {
|
||||
&self.method_handles
|
||||
}
|
||||
/// Retunr the file [`MapList`].
|
||||
/// Return the file [`MapList`].
|
||||
pub fn get_map_list(&self) -> &MapList {
|
||||
&self.map_list
|
||||
}
|
||||
|
|
@ -421,6 +432,45 @@ 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
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
//! Hidden api items.
|
||||
|
||||
use crate as androscalpel_serializer;
|
||||
use crate::{Error, ReadSeek, Result, Serializable, Uleb128};
|
||||
use std::io::{Cursor, Write};
|
||||
use std::io::{Cursor, Seek, SeekFrom, 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
|
||||
/// Hard to serialize/deserialize without additional data like the number of classes defs
|
||||
/// or the method/field of the classes.
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub struct HiddenapiClassDataItem {
|
||||
//pub size: u32,
|
||||
pub data: Vec<u8>,
|
||||
|
|
@ -18,7 +17,7 @@ impl HiddenapiClassDataItem {
|
|||
self.data.len() as u32
|
||||
}
|
||||
|
||||
/// Return `hiddenapi_class_data_item.offsets[class_idx]`.
|
||||
/// Return `hiddenapi_class_data_item.offsets[class_def_item_idx]`.
|
||||
///
|
||||
/// If `0`: Either no data for this class or all API flags are zero.
|
||||
/// Else: offset from the begining of the [`HiddenapiClassDataItem`]
|
||||
|
|
@ -26,13 +25,17 @@ impl HiddenapiClassDataItem {
|
|||
///
|
||||
/// # Warning
|
||||
///
|
||||
/// 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 {
|
||||
/// `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 {
|
||||
Err(Error::InconsistantStruct(format!(
|
||||
"class index 0x{class_idx:x} out of bound of HiddenapiClassDataItem data"
|
||||
"class index 0x{class_def_item_idx:x} out of bound of HiddenapiClassDataItem data"
|
||||
)))
|
||||
} else {
|
||||
u32::deserialize_from_slice(&self.data[index..])
|
||||
|
|
@ -44,7 +47,7 @@ impl HiddenapiClassDataItem {
|
|||
/// # Warning
|
||||
///
|
||||
/// They are no check weither the `nb_class` is valid one or not.
|
||||
/// Giving an invalid `nb_class`.
|
||||
/// Giving an invalid `nb_class` is UB.
|
||||
pub fn get_offsets(&self, nb_class: u32) -> Result<Vec<u32>> {
|
||||
let mut offsets = vec![];
|
||||
let mut buffer = Cursor::new(self.data.as_slice());
|
||||
|
|
@ -60,19 +63,37 @@ impl HiddenapiClassDataItem {
|
|||
///
|
||||
/// # Warning
|
||||
///
|
||||
/// They are no check weither the `nb_flags` or `offset`
|
||||
/// `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`
|
||||
/// are valid. Providing invalid values is UB.
|
||||
pub fn get_flags(&self, nb_flags: usize, offset: u32) -> Result<Vec<Uleb128>> {
|
||||
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)?;
|
||||
if offset == 0 {
|
||||
Ok(vec![Uleb128(0); nb_flags])
|
||||
Ok(vec![HiddenApiFlags::DEFAULT; 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(Uleb128::deserialize(&mut buffer)?);
|
||||
flags.push(HiddenApiFlags::from_uleb128(Uleb128::deserialize(
|
||||
&mut buffer,
|
||||
)?));
|
||||
}
|
||||
Ok(flags)
|
||||
}
|
||||
|
|
@ -102,40 +123,139 @@ impl Serializable for HiddenapiClassDataItem {
|
|||
}
|
||||
}
|
||||
|
||||
/// Flags for hidden api
|
||||
#[derive(Serializable, Debug, PartialEq, Eq, Copy, Clone)]
|
||||
#[prefix_type(Uleb128)]
|
||||
pub enum HiddenApiFlag {
|
||||
#[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 {
|
||||
/// 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.
|
||||
#[prefix(Uleb128(0x03))]
|
||||
/// 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.
|
||||
#[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.
|
||||
#[default_variant]
|
||||
Unknwon(Uleb128),
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue