add support for read hiddenapi
This commit is contained in:
parent
df9d258780
commit
437ecbeecc
17 changed files with 764 additions and 92 deletions
|
|
@ -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