add support for read hiddenapi

This commit is contained in:
Jean-Marie Mineau 2024-06-10 18:20:45 +02:00
parent df9d258780
commit 437ecbeecc
Signed by: histausse
GPG key ID: B66AEEDA9B645AD2
17 changed files with 764 additions and 92 deletions

32
Cargo.lock generated
View file

@ -335,6 +335,12 @@ dependencies = [
"windows-targets", "windows-targets",
] ]
[[package]]
name = "portable-atomic"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0"
[[package]] [[package]]
name = "ppv-lite86" name = "ppv-lite86"
version = "0.2.17" version = "0.2.17"
@ -352,9 +358,9 @@ dependencies = [
[[package]] [[package]]
name = "pyo3" name = "pyo3"
version = "0.20.0" version = "0.21.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04e8453b658fe480c3e70c8ed4e3d3ec33eb74988bd186561b0cc66b85c3bc4b" checksum = "a5e00b96a521718e08e03b1a622f01c8a8deb50719335de3f60b3b3950f069d8"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"cfg-if", "cfg-if",
@ -362,6 +368,7 @@ dependencies = [
"libc", "libc",
"memoffset", "memoffset",
"parking_lot", "parking_lot",
"portable-atomic",
"pyo3-build-config", "pyo3-build-config",
"pyo3-ffi", "pyo3-ffi",
"pyo3-macros", "pyo3-macros",
@ -370,9 +377,9 @@ dependencies = [
[[package]] [[package]]
name = "pyo3-build-config" name = "pyo3-build-config"
version = "0.20.0" version = "0.21.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a96fe70b176a89cff78f2fa7b3c930081e163d5379b4dcdf993e3ae29ca662e5" checksum = "7883df5835fafdad87c0d888b266c8ec0f4c9ca48a5bed6bbb592e8dedee1b50"
dependencies = [ dependencies = [
"once_cell", "once_cell",
"target-lexicon", "target-lexicon",
@ -380,9 +387,9 @@ dependencies = [
[[package]] [[package]]
name = "pyo3-ffi" name = "pyo3-ffi"
version = "0.20.0" version = "0.21.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "214929900fd25e6604661ed9cf349727c8920d47deff196c4e28165a6ef2a96b" checksum = "01be5843dc60b916ab4dad1dca6d20b9b4e6ddc8e15f50c47fe6d85f1fb97403"
dependencies = [ dependencies = [
"libc", "libc",
"pyo3-build-config", "pyo3-build-config",
@ -390,9 +397,9 @@ dependencies = [
[[package]] [[package]]
name = "pyo3-log" name = "pyo3-log"
version = "0.9.0" version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c10808ee7250403bedb24bc30c32493e93875fef7ba3e4292226fe924f398bd" checksum = "2af49834b8d2ecd555177e63b273b708dea75150abc6f5341d0a6e1a9623976c"
dependencies = [ dependencies = [
"arc-swap", "arc-swap",
"log", "log",
@ -401,9 +408,9 @@ dependencies = [
[[package]] [[package]]
name = "pyo3-macros" name = "pyo3-macros"
version = "0.20.0" version = "0.21.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dac53072f717aa1bfa4db832b39de8c875b7c7af4f4a6fe93cdbf9264cf8383b" checksum = "77b34069fc0682e11b31dbd10321cbf94808394c56fd996796ce45217dfac53c"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"pyo3-macros-backend", "pyo3-macros-backend",
@ -413,12 +420,13 @@ dependencies = [
[[package]] [[package]]
name = "pyo3-macros-backend" name = "pyo3-macros-backend"
version = "0.20.0" version = "0.21.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7774b5a8282bd4f25f803b1f0d945120be959a36c72e08e7cd031c792fdfd424" checksum = "08260721f32db5e1a5beae69a55553f56b99bd0e1c3e6e0a5e8851a9d0f5a85c"
dependencies = [ dependencies = [
"heck", "heck",
"proc-macro2", "proc-macro2",
"pyo3-build-config",
"quote", "quote",
"syn", "syn",
] ]

View file

@ -14,8 +14,8 @@ androscalpel_serializer = { version = "0.1.0", path = "../androscalpel_serialize
anyhow = { version = "1.0.75", features = ["backtrace"] } anyhow = { version = "1.0.75", features = ["backtrace"] }
apk_frauder = { version = "0.1.0", path = "../apk_frauder" } apk_frauder = { version = "0.1.0", path = "../apk_frauder" }
log = "0.4.20" log = "0.4.20"
pyo3 = { version = "0.20.0", features = ["anyhow", "abi3-py38"] } pyo3 = { version = "0.21.0", features = ["anyhow", "abi3-py38"] }
pyo3-log = "0.9.0" pyo3-log = "0.10.0"
rayon = "1.9.0" rayon = "1.9.0"
serde = { version = "1.0.195", features = ["derive"] } serde = { version = "1.0.195", features = ["derive"] }
serde_json = "1.0.111" serde_json = "1.0.111"

View file

@ -35,7 +35,8 @@ impl Apk {
let classes = dex let classes = dex
.get_class_defs() .get_class_defs()
.par_iter() .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))) .map(|class| class.map(|class| (class.descriptor.clone(), class)))
.collect::<Result<Vec<_>, _>>()?; .collect::<Result<Vec<_>, _>>()?;
self.classes.par_extend(classes); self.classes.par_extend(classes);
@ -45,9 +46,12 @@ impl Apk {
} }
/// Extract a class from a dex file reader. /// 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( fn get_class_from_dex_file(
&self, &self,
class_item: &ClassDefItem, class_item: &ClassDefItem,
class_item_idx: usize,
dex: &DexFileReader, dex: &DexFileReader,
) -> Result<Class> { ) -> Result<Class> {
let descriptor = Self::get_id_type_from_idx(class_item.class_idx as usize, dex)?; 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 static_fields_list = vec![];
let mut instance_fields_list = vec![]; let mut instance_fields_list = vec![];
let mut direct_methods = HashMap::new(); let mut direct_methods = HashMap::new();
@ -123,12 +129,32 @@ impl Apk {
Self::get_field_list_from_encoded_field_list(&data.static_fields, dex)?; Self::get_field_list_from_encoded_field_list(&data.static_fields, dex)?;
instance_fields_list = instance_fields_list =
Self::get_field_list_from_encoded_field_list(&data.instance_fields, dex)?; 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); 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); virtual_methods.insert(method.descriptor.clone(), method);
} }
} }
@ -617,6 +643,7 @@ impl Apk {
is_synthetic, is_synthetic,
is_enum, is_enum,
value: None, value: None,
hiddenapi: None,
annotations: vec![], annotations: vec![],
}) })
} }
@ -746,6 +773,7 @@ impl Apk {
is_declared_syncrhonized, is_declared_syncrhonized,
annotations: vec![], annotations: vec![],
parameters_annotations: vec![], parameters_annotations: vec![],
hiddenapi: None,
code, code,
}) })
} }
@ -2444,7 +2472,7 @@ impl Apk {
Ok(self Ok(self
.gen_raw_dex()? .gen_raw_dex()?
.into_iter() .into_iter()
.map(|bytes| PyBytes::new(py, &bytes).into()) .map(|bytes| PyBytes::new_bound(py, &bytes).into())
.collect()) .collect())
} }

View file

@ -316,7 +316,7 @@ impl Class {
.any(|field| field.value.is_some()) .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 { pub fn has_annotations(&self) -> bool {
!self.annotations.is_empty() !self.annotations.is_empty()
|| self || self
@ -330,11 +330,30 @@ impl Class {
|| self || self
.direct_methods .direct_methods
.values() .values()
.any(|field| field.has_annotations()) .any(|method| method.has_annotations())
|| self || self
.virtual_methods .virtual_methods
.values() .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. /// Return the binary representation of access flags.

View file

@ -667,11 +667,11 @@ impl IdField {
/// ``` /// ```
/// use androscalpel::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!( /// assert_eq!(
/// proto, /// proto,
/// IdField::new( /// IdField::new(
/// "METHOD".into(), /// "FIELD".into(),
/// IdType::class("java/lang/annotation/ElementType"), /// IdType::class("java/lang/annotation/ElementType"),
/// IdType::class("java/lang/annotation/ElementType"), /// IdType::class("java/lang/annotation/ElementType"),
/// ) /// )

View file

@ -138,10 +138,10 @@ impl Hash for DexString {
} }
} }
pub fn as_dex_string(obj: &PyAny) -> PyResult<DexString> { pub fn as_dex_string(obj: &Bound<'_, PyAny>) -> PyResult<DexString> {
if let Ok(string) = DexString::extract(obj) { if let Ok(string) = DexString::extract_bound(obj) {
Ok(string) Ok(string)
} else if let Ok(string) = String::extract(obj) { } else if let Ok(string) = String::extract_bound(obj) {
Ok(string.into()) Ok(string.into())
} else { } else {
Err(PyErr::new::<PyTypeError, _>(format!( Err(PyErr::new::<PyTypeError, _>(format!(
@ -198,10 +198,10 @@ impl DexString {
strings strings
} }
fn __richcmp__(&self, other: &PyAny, op: CompareOp) -> PyResult<bool> { fn __richcmp__(&self, other: &Bound<'_, PyAny>, op: CompareOp) -> PyResult<bool> {
let other: Self = other let other: Self = other
.extract() .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))) Ok(op.matches(self.0.cmp(&other.0)))
} }
} }

View file

@ -6,7 +6,7 @@ use std::io::{Cursor, Seek, SeekFrom, Write};
use adler::Adler32; use adler::Adler32;
use anyhow::{anyhow, bail, Context}; use anyhow::{anyhow, bail, Context};
use log::debug; use log::{debug, warn};
use sha1::{Digest, Sha1}; use sha1::{Digest, Sha1};
use crate::Result; use crate::Result;
@ -180,6 +180,12 @@ impl DexWriter {
pub fn add_class(&mut self, class: &Class) -> Result<(), DexWritterError> { pub fn add_class(&mut self, class: &Class) -> Result<(), DexWritterError> {
debug!("Adding class {} to dex builder", class.descriptor.__str__()); 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_strings = class.get_all_strings();
let new_types = class.get_all_types(); let new_types = class.get_all_types();

View file

@ -6,8 +6,8 @@ use std::collections::HashSet;
use pyo3::prelude::*; use pyo3::prelude::*;
use crate::{ use crate::{
DexAnnotationItem, DexString, DexValue, IdField, IdMethod, IdMethodType, IdType, MethodHandle, DexAnnotationItem, DexString, DexValue, HiddenApiData, IdField, IdMethod, IdMethodType, IdType,
Result, MethodHandle, Result,
}; };
use androscalpel_serializer::consts::*; use androscalpel_serializer::consts::*;
@ -44,6 +44,9 @@ pub struct Field {
/// The annotations for this field /// The annotations for this field
#[pyo3(get)] #[pyo3(get)]
pub annotations: Vec<DexAnnotationItem>, pub annotations: Vec<DexAnnotationItem>,
/// Hidden Api data.
#[pyo3(get)]
pub hiddenapi: Option<HiddenApiData>,
} }
/// Represent the visibility of a field /// Represent the visibility of a field
@ -80,6 +83,7 @@ impl Field {
is_enum: false, is_enum: false,
value: None, value: None,
annotations: vec![], annotations: vec![],
hiddenapi: None,
} }
} }
@ -127,7 +131,7 @@ impl Field {
/// Set the value from a python object /// Set the value from a python object
#[setter] #[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()?); self.value = Some(ob.extract()?);
// TODO: check type match // TODO: check type match
Ok(()) Ok(())

View 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()
}
}

View file

@ -14,6 +14,7 @@ pub mod dex_string;
pub mod dex_writer; pub mod dex_writer;
pub mod field; pub mod field;
mod hashmap_vectorize; mod hashmap_vectorize;
pub mod hiddenapi;
pub mod instructions; pub mod instructions;
pub mod method; pub mod method;
pub mod method_handle; pub mod method_handle;
@ -29,6 +30,7 @@ pub use dex_id::*;
pub use dex_string::*; pub use dex_string::*;
pub use dex_writer::*; pub use dex_writer::*;
pub use field::*; pub use field::*;
pub use hiddenapi::*;
pub use instructions as ins; pub use instructions as ins;
pub use method::*; pub use method::*;
pub use method_handle::*; pub use method_handle::*;
@ -40,7 +42,7 @@ mod tests;
/// Androscalpel. /// Androscalpel.
#[pymodule] #[pymodule]
fn androscalpel(py: Python, m: &PyModule) -> PyResult<()> { fn androscalpel(py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> {
pyo3_log::init(); pyo3_log::init();
m.add_class::<DexNull>()?; m.add_class::<DexNull>()?;
m.add_class::<DexBoolean>()?; m.add_class::<DexBoolean>()?;
@ -69,6 +71,10 @@ fn androscalpel(py: Python, m: &PyModule) -> PyResult<()> {
m.add_class::<InvokeDirect>()?; m.add_class::<InvokeDirect>()?;
m.add_class::<InvokeInterface>()?; m.add_class::<InvokeInterface>()?;
m.add_class::<HiddenApiData>()?;
m.add_class::<HiddenApiPermission>()?;
m.add_class::<HiddenApiDomain>()?;
m.add_class::<DexAnnotationItem>()?; m.add_class::<DexAnnotationItem>()?;
m.add_class::<DexAnnotation>()?; m.add_class::<DexAnnotation>()?;
m.add_class::<DexArray>()?; m.add_class::<DexArray>()?;
@ -80,18 +86,18 @@ fn androscalpel(py: Python, m: &PyModule) -> PyResult<()> {
m.add_class::<Code>()?; m.add_class::<Code>()?;
m.add_class::<Apk>()?; m.add_class::<Apk>()?;
let ins_module = PyModule::new(py, "ins")?; let ins_module = PyModule::new_bound(py, "ins")?;
androscalpel_ins(py, ins_module)?; androscalpel_ins(py, &ins_module)?;
m.add_submodule(ins_module)?; m.add_submodule(&ins_module)?;
let utils_module = PyModule::new(py, "utils")?; let utils_module = PyModule::new_bound(py, "utils")?;
py_utils::export_module(py, utils_module)?; py_utils::export_module(py, &utils_module)?;
m.add_submodule(utils_module)?; m.add_submodule(&utils_module)?;
Ok(()) Ok(())
} }
/// Dalvik opcode for Androscalpel. /// 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::CallSite>()?;
m.add_class::<ins::Nop>()?; m.add_class::<ins::Nop>()?;
m.add_class::<ins::Move>()?; m.add_class::<ins::Move>()?;

View file

@ -6,8 +6,8 @@ use std::collections::HashSet;
use pyo3::prelude::*; use pyo3::prelude::*;
use crate::{ use crate::{
Code, DexAnnotationItem, DexString, IdField, IdMethod, IdMethodType, IdType, MethodHandle, Code, DexAnnotationItem, DexString, HiddenApiData, IdField, IdMethod, IdMethodType, IdType,
Result, MethodHandle, Result,
}; };
use androscalpel_serializer::consts::*; use androscalpel_serializer::consts::*;
@ -62,6 +62,9 @@ pub struct Method {
/// The annotations for the parameters of this method method /// The annotations for the parameters of this method method
#[pyo3(get)] #[pyo3(get)]
pub parameters_annotations: Vec<Vec<DexAnnotationItem>>, pub parameters_annotations: Vec<Vec<DexAnnotationItem>>,
/// Hidden Api data.
#[pyo3(get)]
pub hiddenapi: Option<HiddenApiData>,
/// The code of the method /// The code of the method
#[pyo3(get)] #[pyo3(get)]
@ -109,6 +112,7 @@ impl Method {
annotations: vec![], annotations: vec![],
parameters_annotations: vec![], parameters_annotations: vec![],
code: None, code: None,
hiddenapi: None,
} }
} }

View file

@ -17,18 +17,18 @@ use apk_frauder::{end_of_central_directory::EndCentralDirectory, ZipFileReader};
/// Convert an integer to the uleb128 byte encoding /// Convert an integer to the uleb128 byte encoding
#[pyfunction] #[pyfunction]
pub fn int_to_uleb128(py: Python, x: u32) -> Result<PyObject> { 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 /// Convert an integer to the uleb128p1 byte encoding
#[pyfunction] #[pyfunction]
pub fn int_to_uleb128p1(py: Python, x: u32) -> Result<PyObject> { 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 /// Convert an integer to the sleb128 byte encoding
#[pyfunction] #[pyfunction]
pub fn int_to_sleb128(py: Python, x: i32) -> Result<PyObject> { 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 /// Decode an uleb128 encoded integer
@ -112,17 +112,21 @@ pub fn is_zip(file: PathBuf) -> bool {
pub fn replace_dex( pub fn replace_dex(
apk: PathBuf, apk: PathBuf,
dst: PathBuf, dst: PathBuf,
dexfiles: Vec<&[u8]>, dexfiles: Vec<&PyBytes>,
keystore: PathBuf, keystore: PathBuf,
zipalign: Option<PathBuf>, zipalign: Option<PathBuf>,
apksigner: 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| { let additionnal_files: Option<HashMap<_, _>> = additionnal_files.map(|additionnal_files| {
additionnal_files additionnal_files
.into_iter() .into_iter()
.map(|(k, v)| (k, v.map(Cursor::new))) .map(|(k, v)| (k, v.map(PyBytes::as_bytes).map(Cursor::new)))
.collect() .collect()
}); });
apk_frauder::replace_dex( apk_frauder::replace_dex(
@ -137,7 +141,7 @@ pub fn replace_dex(
} }
/// export the function in a python module /// 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_uleb128, m)?)?;
m.add_function(wrap_pyfunction!(int_to_uleb128p1, m)?)?; m.add_function(wrap_pyfunction!(int_to_uleb128p1, m)?)?;
m.add_function(wrap_pyfunction!(int_to_sleb128, m)?)?; m.add_function(wrap_pyfunction!(int_to_sleb128, m)?)?;

View 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()

File diff suppressed because one or more lines are too long

View file

@ -1,6 +1,7 @@
use super::*; use super::*;
use androscalpel_serializer::Instruction as InsFormat; use androscalpel_serializer::Instruction as InsFormat;
use androscalpel_serializer::*; use androscalpel_serializer::*;
use serde_json as sj;
use std::collections::HashSet; use std::collections::HashSet;
use std::fs::File; use std::fs::File;
use std::io; use std::io;
@ -548,3 +549,130 @@ fn test_2_from_json() {
new_apk.add_dex_file(&dex).unwrap(); new_apk.add_dex_file(&dex).unwrap();
assert_eq!(apk, new_apk); 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(),
);
}
}
}

View file

@ -1,9 +1,9 @@
//! Parser for a .dex file. //! Parser for a .dex file.
use crate::{ use crate::{
CallSiteIdItem, ClassDefItem, EndianConstant, Error, FieldIdItem, HeaderItem, MapItemType, CallSiteIdItem, ClassDataItem, ClassDefItem, EndianConstant, Error, FieldIdItem, HeaderItem,
MapList, MethodHandleItem, MethodIdItem, ProtoIdItem, Result, Serializable, StringDataItem, HiddenApiFlags, HiddenapiClassDataItem, MapItemType, MapList, MethodHandleItem, MethodIdItem,
StringIdItem, TypeIdItem, ProtoIdItem, Result, Serializable, StringDataItem, StringIdItem, TypeIdItem,
}; };
use log::{error, info, warn}; use log::{error, info, warn};
use std::io::{Cursor, Seek, SeekFrom}; use std::io::{Cursor, Seek, SeekFrom};
@ -28,6 +28,7 @@ pub struct DexFileReader<'a> {
class_defs: Vec<ClassDefItem>, class_defs: Vec<ClassDefItem>,
call_site_ids: Vec<CallSiteIdItem>, call_site_ids: Vec<CallSiteIdItem>,
method_handles: Vec<MethodHandleItem>, method_handles: Vec<MethodHandleItem>,
hiddenapi_class_data: Option<HiddenapiClassDataItem>,
map_list: MapList, map_list: MapList,
} }
@ -48,6 +49,7 @@ impl<'a> DexFileReader<'a> {
class_defs: vec![], class_defs: vec![],
call_site_ids: vec![], call_site_ids: vec![],
method_handles: vec![], method_handles: vec![],
hiddenapi_class_data: None,
map_list: MapList { list: vec![] }, map_list: MapList { list: vec![] },
}; };
if tmp_file.header.map_off != 0 { if tmp_file.header.map_off != 0 {
@ -99,6 +101,15 @@ impl<'a> DexFileReader<'a> {
tmp_file.method_handles = tmp_file.method_handles =
tmp_file.get_item_list::<MethodHandleItem>(item.offset, item.size)? 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()?; tmp_file.sanity_check()?;
Ok(tmp_file) Ok(tmp_file)
} }
@ -107,39 +118,39 @@ impl<'a> DexFileReader<'a> {
pub fn get_header(&self) -> &HeaderItem { pub fn get_header(&self) -> &HeaderItem {
&self.header &self.header
} }
/// Retunr the file [`StringIdItem`] list. /// Return the file [`StringIdItem`] list.
pub fn get_string_ids(&self) -> &[StringIdItem] { pub fn get_string_ids(&self) -> &[StringIdItem] {
&self.string_ids &self.string_ids
} }
/// Retunr the file [`TypeIdItem`] list. /// Return the file [`TypeIdItem`] list.
pub fn get_type_ids(&self) -> &[TypeIdItem] { pub fn get_type_ids(&self) -> &[TypeIdItem] {
&self.type_ids &self.type_ids
} }
/// Retunr the file [`ProtoIdItem`] list. /// Return the file [`ProtoIdItem`] list.
pub fn get_proto_ids(&self) -> &[ProtoIdItem] { pub fn get_proto_ids(&self) -> &[ProtoIdItem] {
&self.proto_ids &self.proto_ids
} }
/// Retunr the file [`FieldIdItem`] list. /// Return the file [`FieldIdItem`] list.
pub fn get_field_ids(&self) -> &[FieldIdItem] { pub fn get_field_ids(&self) -> &[FieldIdItem] {
&self.field_ids &self.field_ids
} }
/// Retunr the file [`MethodIdItem`] list. /// Return the file [`MethodIdItem`] list.
pub fn get_method_ids(&self) -> &[MethodIdItem] { pub fn get_method_ids(&self) -> &[MethodIdItem] {
&self.method_ids &self.method_ids
} }
/// Retunr the file [`ClassDefItem`] list. /// Return the file [`ClassDefItem`] list.
pub fn get_class_defs(&self) -> &[ClassDefItem] { pub fn get_class_defs(&self) -> &[ClassDefItem] {
&self.class_defs &self.class_defs
} }
/// Retunr the file [`CallSiteIdItem`] list. /// Return the file [`CallSiteIdItem`] list.
pub fn get_call_site_ids(&self) -> &[CallSiteIdItem] { pub fn get_call_site_ids(&self) -> &[CallSiteIdItem] {
&self.call_site_ids &self.call_site_ids
} }
/// Retunr the file [`MethodHandleItem`] list. /// Return the file [`MethodHandleItem`] list.
pub fn get_method_handles(&self) -> &[MethodHandleItem] { pub fn get_method_handles(&self) -> &[MethodHandleItem] {
&self.method_handles &self.method_handles
} }
/// Retunr the file [`MapList`]. /// Return the file [`MapList`].
pub fn get_map_list(&self) -> &MapList { pub fn get_map_list(&self) -> &MapList {
&self.map_list &self.map_list
} }
@ -421,6 +432,45 @@ impl<'a> DexFileReader<'a> {
r 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. /// Return the strings that where not referenced.
pub fn get_not_resolved_strings(&mut self) -> Result<Vec<StringDataItem>> { pub fn get_not_resolved_strings(&mut self) -> Result<Vec<StringDataItem>> {
// use `&mut self` because using this method at the same time as performing // use `&mut self` because using this method at the same time as performing

View file

@ -1,13 +1,12 @@
//! Hidden api items. //! Hidden api items.
use crate as androscalpel_serializer;
use crate::{Error, ReadSeek, Result, Serializable, Uleb128}; 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> /// <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. /// or the method/field of the classes.
#[derive(Clone, PartialEq, Eq)] #[derive(Clone, PartialEq, Eq, Debug)]
pub struct HiddenapiClassDataItem { pub struct HiddenapiClassDataItem {
//pub size: u32, //pub size: u32,
pub data: Vec<u8>, pub data: Vec<u8>,
@ -18,7 +17,7 @@ impl HiddenapiClassDataItem {
self.data.len() as u32 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. /// If `0`: Either no data for this class or all API flags are zero.
/// Else: offset from the begining of the [`HiddenapiClassDataItem`] /// Else: offset from the begining of the [`HiddenapiClassDataItem`]
@ -26,13 +25,17 @@ impl HiddenapiClassDataItem {
/// ///
/// # Warning /// # Warning
/// ///
/// They are no check weither the `class_idx` is valid one or not. /// `class_def_item_idx` is **NOT** `class_idx` (Contrary to what
/// Giving an invalid idx (like an idx >= nb class) is UB. /// <https://source.android.com/docs/core/runtime/dex-format#hiddenapi-class-data-item> says).
pub fn get_offset(&self, class_idx: u32) -> Result<u32> { /// `class_def_item_idx` is the index of the `class_def_item`.
let index = (class_idx as usize) * 4; ///
if self.data.len() < index - 4 { /// 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!( 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 { } else {
u32::deserialize_from_slice(&self.data[index..]) u32::deserialize_from_slice(&self.data[index..])
@ -44,7 +47,7 @@ impl HiddenapiClassDataItem {
/// # Warning /// # Warning
/// ///
/// They are no check weither the `nb_class` is valid one or not. /// 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>> { pub fn get_offsets(&self, nb_class: u32) -> Result<Vec<u32>> {
let mut offsets = vec![]; let mut offsets = vec![];
let mut buffer = Cursor::new(self.data.as_slice()); let mut buffer = Cursor::new(self.data.as_slice());
@ -60,19 +63,37 @@ impl HiddenapiClassDataItem {
/// ///
/// # Warning /// # 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. /// 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 { if offset == 0 {
Ok(vec![Uleb128(0); nb_flags]) Ok(vec![HiddenApiFlags::DEFAULT; nb_flags])
} else if offset < 4 { } else if offset < 4 {
// < 8 is almost certainly false // < 8 is almost certainly false
panic!() panic!()
} else { } else {
let mut buffer = Cursor::new(self.data.as_slice()); 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![]; let mut flags = vec![];
for _ in 0..nb_flags { for _ in 0..nb_flags {
flags.push(Uleb128::deserialize(&mut buffer)?); flags.push(HiddenApiFlags::from_uleb128(Uleb128::deserialize(
&mut buffer,
)?));
} }
Ok(flags) Ok(flags)
} }
@ -102,40 +123,139 @@ impl Serializable for HiddenapiClassDataItem {
} }
} }
/// Flags for hidden api #[derive(Clone, PartialEq, Eq)]
#[derive(Serializable, Debug, PartialEq, Eq, Copy, Clone)] pub struct HiddenApiFlags {
#[prefix_type(Uleb128)] pub value: HiddenApiValue,
pub enum HiddenApiFlag { 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 /// Interfaces that can be freely used and are supported as
/// part of the officially documented Android framework Package Index /// part of the officially documented Android framework Package Index
#[prefix(Uleb128(0x00))]
Whitelist, Whitelist,
/// Non-SDK interfaces that can be used regardless of the /// Non-SDK interfaces that can be used regardless of the
/// application's target API level /// application's target API level
#[prefix(Uleb128(0x01))]
Greylist, Greylist,
/// Non-SDK interfaces that cannot be used regardless of the /// Non-SDK interfaces that cannot be used regardless of the
/// application's target API level. Accessing one of these /// application's target API level. Accessing one of these
/// interfaces causes a runtime error. /// interfaces causes a runtime error.
#[prefix(Uleb128(0x02))]
Blacklist, Blacklist,
/// Non-SDK interfaces that can be used for Android 8.x and /// Non-SDK interfaces that can be used for Android 8.x and
/// below unless they are restricted. /// below unless they are restricted (targetSdkVersion <= 27 (O_MR1)).
#[prefix(Uleb128(0x03))]
GreylistMaxO, GreylistMaxO,
/// Non-SDK interfaces that can be used for Android 9.x unless /// Non-SDK interfaces that can be used for Android 9.x unless
/// they are restricted. /// they are restricted.
#[prefix(Uleb128(0x04))]
GreylistMaxP, GreylistMaxP,
/// Non-SDK interfaces that can be used for Android 10.x unless /// Non-SDK interfaces that can be used for Android 10.x unless
/// they are restricted. /// they are restricted.
#[prefix(Uleb128(0x05))]
GreylistMaxQ, GreylistMaxQ,
/// Non-SDK interfaces that can be used for Android 11.x unless /// Non-SDK interfaces that can be used for Android 11.x unless
/// they are restricted. /// they are restricted.
#[prefix(Uleb128(0x06))]
GreylistMaxR, GreylistMaxR,
GreylistMaxS,
/// Unknown flag, either an error or this crate is out of date. /// Unknown flag, either an error or this crate is out of date.
#[default_variant] Unknwon(u8),
Unknwon(Uleb128), }
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
}
}
} }