diff --git a/androscalpel_serializer/src/core/mod.rs b/androscalpel_serializer/src/core/mod.rs index 3729230..dd8eb07 100644 --- a/androscalpel_serializer/src/core/mod.rs +++ b/androscalpel_serializer/src/core/mod.rs @@ -16,6 +16,7 @@ pub enum Error { SerializationError(String), DeserializationError(String), InvalidStringEncoding(String), + InconsistantStruct(String), } pub type Result = core::result::Result; @@ -30,6 +31,7 @@ impl std::fmt::Display for Error { Self::SerializationError(msg) => write!(f, "Error: {}", msg), Self::DeserializationError(msg) => write!(f, "Error: {}", msg), Self::InvalidStringEncoding(msg) => write!(f, "Error: {}", msg), + Self::InconsistantStruct(msg) => write!(f, "Error: {}", msg), } } } diff --git a/androscalpel_serializer/src/items/code.rs b/androscalpel_serializer/src/items/code.rs new file mode 100644 index 0000000..e944223 --- /dev/null +++ b/androscalpel_serializer/src/items/code.rs @@ -0,0 +1,323 @@ +//! Code items + +use crate as androscalpel_serializer; +use crate::{Error, ReadSeek, Result, Serializable, Sleb128, Uleb128}; +use std::io::Write; + +/// https://source.android.com/docs/core/runtime/dex-format#code-item +/// alignment: 4 bytes +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct CodeItem { + pub registers_size: u16, + pub ins_size: u16, + pub outs_size: u16, + // pub tries_size: u16, + /// 0 if no debug info, else offset to a [`DebugInfoItem`]. + pub debug_info_off: u32, + // pub insns_size: u32, + pub insns: Vec, + // pub padding: Vec, + /// try items must refere to non overlapping range an order from low to hight addresses. + pub tries: Vec, + pub handlers: Option, +} + +impl CodeItem { + pub fn tries_size_field(&self) -> u16 { + self.tries.len() as u16 + } + + pub fn insns_size_field(&self) -> u32 { + self.insns.len() as u32 + } + + pub fn sanity_check(&self) -> Result<()> { + if self.tries.is_empty() && self.handlers.is_some() { + return Err(Error::InconsistantStruct( + "CodeItem cannot have a `handlers` value if `tries_size` is 0 (CodeItem.tries is empty)".into() + )); + } + if !self.tries.is_empty() && self.handlers.is_none() { + return Err(Error::InconsistantStruct( + "CodeItem must have a `handlers` value if `tries_size` is not 0 (CodeItem.tries not empty)".into() + )); + } + let mut max_addr = 0; + for item in &self.tries { + let addr = item.start_addr; + if addr < max_addr { + return Err(Error::InconsistantStruct( + "try_item in a code_item must be non overlapping and sorted from low to high address".into() + )); + } + max_addr += addr + (item.insn_count as u32); + } + // Necessary? no in spec + if max_addr > self.insns.len() as u32 { + return Err(Error::InconsistantStruct( + "found try_item whose block span outside of the insns array".into(), + )); + } + + for try_ in &self.tries { + let handler = self + .handlers + .as_ref() + .unwrap() + .get_handler_at_offset(try_.handler_off)?; + handler.sanity_check()?; + for handler in &handler.handlers { + // Necessary? no in spec + if handler.addr.0 > self.insns.len() as u32 { + return Err(Error::InconsistantStruct( + "Found an handler whose address is outside of the insns array".into(), + )); + } + } + if let Some(Uleb128(addr)) = handler.catch_all_addr { + // Necessary? no in spec + if addr > self.insns.len() as u32 { + return Err(Error::InconsistantStruct( + "Found a catch all handler whose address is outside of the insns array" + .into(), + )); + } + } + } + Ok(()) + } +} + +impl Serializable for CodeItem { + fn serialize(&self, output: &mut dyn Write) -> Result<()> { + self.sanity_check().map_err(|err| match err { + Error::InconsistantStruct(msg) => { + Error::SerializationError(format!("Inconsistant CodeItem: {msg}")) + } + err => err, + })?; + self.registers_size.serialize(output)?; + self.ins_size.serialize(output)?; + self.outs_size.serialize(output)?; + self.tries_size_field().serialize(output)?; + self.debug_info_off.serialize(output)?; + for insn in &self.insns { + insn.serialize(output)?; + } + if !self.tries.is_empty() && self.insns.len() % 2 == 1 { + 0u16.serialize(output)?; + } + for item in &self.tries { + item.serialize(output)?; + } + if let Some(ref handlers) = self.handlers { + handlers.serialize(output)?; + } + Ok(()) + } + + fn deserialize(input: &mut dyn ReadSeek) -> Result { + let registers_size = u16::deserialize(input)?; + let ins_size = u16::deserialize(input)?; + let outs_size = u16::deserialize(input)?; + let tries_size = u16::deserialize(input)?; + let debug_info_off = u32::deserialize(input)?; + let insns_size = u32::deserialize(input)?; + let mut insns = vec![]; + for _ in 0..insns_size { + insns.push(u16::deserialize(input)?); + } + if tries_size != 0 && insns_size % 2 == 1 { + let _ = u16::deserialize(input)?; + } + let mut tries = vec![]; + for _ in 0..tries_size { + tries.push(TryItem::deserialize(input)?); + } + let handlers = if tries_size != 0 { + Some(EncodedCatchHandlerList::deserialize(input)?) + } else { + None + }; + Ok(Self { + registers_size, + ins_size, + outs_size, + debug_info_off, + insns, + tries, + handlers, + }) + } + fn size(&self) -> usize { + self.registers_size.size() + + self.ins_size.size() + + self.outs_size.size() + + self.tries_size_field().size() + + self.debug_info_off.size() + + self.insns.iter().map(|val| val.size()).sum::() + + if !self.tries.is_empty() && self.insns.len() % 2 == 1 { + 2 + } else { + 0 + } + + self.tries.iter().map(|val| val.size()).sum::() + + self.handlers.as_ref().map(|val| val.size()).unwrap_or(0) + } +} + +/// https://source.android.com/docs/core/runtime/dex-format#type-item +#[derive(Serializable, Clone, Copy, Debug, PartialEq, Eq)] +pub struct TryItem { + /// Start address of the block of code covered. It's a count of 16-bit code unit to the + /// start of the first covered instruction of the block. + pub start_addr: u32, + /// Number of 16-bit code unit covered by the entry. + pub insn_count: u16, + /// **Offset in bytes** from the start of the `EncodedCatchHandlerList` to the + /// `EncodedCatchHandler` associated. + pub handler_off: u16, +} + +/// https://source.android.com/docs/core/runtime/dex-format#encoded-catch-handlerlist +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct EncodedCatchHandlerList { + // pub size: Uleb128, + pub list: Vec, +} + +impl EncodedCatchHandlerList { + pub fn size_field(&self) -> Uleb128 { + Uleb128(self.list.len() as u32) + } + + /// Return a reference to the [`EncodedCatchHandler`] located at `offset` bytes after + /// the begining of the [`EncodedCatchHandlerList`]. Expected to be used to lookup + /// the value refered to by [`TryItem.handler_off`]. + pub fn get_handler_at_offset(&self, offset: u16) -> Result<&EncodedCatchHandler> { + let offset = offset as usize; + let mut current_offset = 0; + for handler in &self.list { + if current_offset == offset { + return Ok(handler); + } + current_offset += handler.size(); + if current_offset > offset { + break; + } + } + Err(Error::InconsistantStruct(format!( + "Offset 0x{offset:x} does not match with the begining of a EncodedCatchHandler in this EncodedCatchHandlerList" + ))) + } +} + +impl Serializable for EncodedCatchHandlerList { + fn serialize(&self, output: &mut dyn Write) -> Result<()> { + self.size_field().serialize(output)?; + for item in &self.list { + item.serialize(output)?; + } + Ok(()) + } + + fn deserialize(input: &mut dyn ReadSeek) -> Result { + let size = i32::deserialize(input)?; + let mut list = vec![]; + for _ in 0..size { + list.push(EncodedCatchHandler::deserialize(input)?); + } + Ok(Self { + // size, + list, + }) + } + + fn size(&self) -> usize { + self.size_field().size() + self.list.iter().map(|val| val.size()).sum::() + } +} + +/// https://source.android.com/docs/core/runtime/dex-format#encoded-catch-handler +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct EncodedCatchHandler { + // pub size: Sleb128, + /// List of handler, one by type caught, in the order of the type tests + pub handlers: Vec, + /// Bytecode address of the catchall handler. + pub catch_all_addr: Option, +} + +impl EncodedCatchHandler { + pub fn size_field(&self) -> Sleb128 { + let sign = if self.catch_all_addr.is_some() { -1 } else { 1 }; + let len = self.handlers.len() as i32; + //if len == 0 && self.catch_all_addr.is_none() { + // Not good. Panic? Error? + //} + Sleb128(len * sign) + } + + pub fn sanity_check(&self) -> Result<()> { + if self.catch_all_addr.is_none() && self.handlers.is_empty() { + return Err(Error::InconsistantStruct( + "EncodedCatchHandler must have at least one handler or catch_all_addr defined" + .into(), + )); + } + Ok(()) + } +} + +impl Serializable for EncodedCatchHandler { + fn serialize(&self, output: &mut dyn Write) -> Result<()> { + self.sanity_check().map_err(|err| match err { + Error::InconsistantStruct(msg) => { + Error::SerializationError(format!("Inconsistant EncodedCatchHandler: {msg}")) + } + err => err, + })?; + self.size_field().serialize(output)?; + for handler in &self.handlers { + handler.serialize(output)?; + } + if let Some(catch_all_addr) = self.catch_all_addr { + catch_all_addr.serialize(output)?; + } + Ok(()) + } + fn deserialize(input: &mut dyn ReadSeek) -> Result { + let Sleb128(size) = Sleb128::deserialize(input)?; + let mut handlers = vec![]; + for _ in 0..size.abs() { + handlers.push(EncodedTypeAddrPair::deserialize(input)?); + } + let catch_all_addr = if size <= 0 { + Some(Uleb128::deserialize(input)?) + } else { + None + }; + Ok(Self { + handlers, + catch_all_addr, + }) + } + fn size(&self) -> usize { + self.size_field().size() + + self.handlers.iter().map(|val| val.size()).sum::() + + self + .catch_all_addr + .as_ref() + .map(|val| val.size()) + .unwrap_or(0) + } +} + +/// https://source.android.com/docs/core/runtime/dex-format#encoded-type-addr-pair +#[derive(Serializable, Clone, Copy, Debug, PartialEq, Eq)] +pub struct EncodedTypeAddrPair { + /// Index of the [`TypeId`] in `type_ids` + pub type_idx: Uleb128, + /// Bytecode address of the exception handler + pub addr: Uleb128, +} diff --git a/androscalpel_serializer/src/items/mod.rs b/androscalpel_serializer/src/items/mod.rs index d8c8c19..c870b28 100644 --- a/androscalpel_serializer/src/items/mod.rs +++ b/androscalpel_serializer/src/items/mod.rs @@ -4,10 +4,12 @@ use crate as androscalpel_serializer; use crate::{EncodedArray, Serializable}; pub mod class; +pub mod code; pub mod header; pub mod map; pub use class::*; +pub use code::*; pub use header::*; pub use map::*;