fn u16le(b: &[u8], off: usize) -> Option<u16> {
let s = b.get(off..off + 2)?;
Some(u16::from_le_bytes([s[0], s[1]]))
}
fn u32le(b: &[u8], off: usize) -> Option<u32> {
let s = b.get(off..off + 4)?;
Some(u32::from_le_bytes([s[0], s[1], s[2], s[3]]))
}
fn uleb128(b: &[u8], off: &mut usize) -> Option<u32> {
let mut result = 0u32;
let mut shift = 0u32;
loop {
let byte = *b.get(*off)?;
*off += 1;
result |= ((byte & 0x7f) as u32) << shift;
if byte & 0x80 == 0 { return Some(result); }
shift += 7;
if shift >= 35 { return None; }
}
}
const ZIP_EOCD_SIG: [u8; 4] = [0x50, 0x4b, 0x05, 0x06];
const ZIP_CD_SIG: [u8; 4] = [0x50, 0x4b, 0x01, 0x02];
const ZIP_LOCAL_SIG: [u8; 4] = [0x50, 0x4b, 0x03, 0x04];
const ZIP_STORED: u16 = 0;
struct ZipEntry {
local_off: usize,
comp_size: usize,
fname_hash: u64, }
fn djb2(s: &[u8]) -> u64 {
let mut h = 5381u64;
for &b in s { h = h.wrapping_mul(33).wrapping_add(b as u64); }
h
}
fn zip_find_dex_entries(data: &[u8]) -> Vec<ZipEntry> {
let scan_start = data.len().saturating_sub(65558);
let eocd_pos = match data[scan_start..]
.windows(4)
.rposition(|w| w == ZIP_EOCD_SIG)
{
Some(p) => scan_start + p,
None => return Vec::new(),
};
let cd_size = match u32le(data, eocd_pos + 12) { Some(v) => v as usize, None => return Vec::new() };
let cd_off = match u32le(data, eocd_pos + 16) { Some(v) => v as usize, None => return Vec::new() };
let mut pos = cd_off;
let cd_end = cd_off.saturating_add(cd_size);
let mut entries = Vec::new();
while pos + 46 <= cd_end && pos + 46 <= data.len() {
if data.get(pos..pos + 4) != Some(&ZIP_CD_SIG) { break; }
let compression = match u16le(data, pos + 10) { Some(v) => v, None => break };
let comp_size = match u32le(data, pos + 20) { Some(v) => v as usize, None => break };
let local_off = match u32le(data, pos + 42) { Some(v) => v as usize, None => break };
let fname_len = match u16le(data, pos + 28) { Some(v) => v as usize, None => break };
let extra_len = match u16le(data, pos + 30) { Some(v) => v as usize, None => break };
let comment_len = match u16le(data, pos + 32) { Some(v) => v as usize, None => break };
let fname_end = pos + 46 + fname_len;
if fname_end > data.len() { break; }
let fname = &data[pos + 46..fname_end];
let is_dex = fname.starts_with(b"classes") && fname.ends_with(b".dex");
if is_dex && compression == ZIP_STORED {
entries.push(ZipEntry {
local_off,
comp_size,
fname_hash: djb2(fname),
});
}
pos = match pos.checked_add(46 + fname_len + extra_len + comment_len) {
Some(v) => v,
None => break,
};
}
entries.sort_by(|a, b| b.comp_size.cmp(&a.comp_size));
entries
}
fn zip_entry_data<'a>(data: &'a [u8], entry: &ZipEntry) -> Option<&'a [u8]> {
let lh = entry.local_off;
if data.get(lh..lh + 4) != Some(&ZIP_LOCAL_SIG) { return None; }
let fname_len = u16le(data, lh + 26)? as usize;
let extra_len = u16le(data, lh + 28)? as usize;
let data_start = lh + 30 + fname_len + extra_len;
data.get(data_start..data_start + entry.comp_size)
}
const DEX_MAGIC: &[u8] = b"dex\n";
fn dex_string<'a>(dex: &'a [u8], string_ids_off: usize, idx: usize) -> Option<&'a [u8]> {
let str_data_off = u32le(dex, string_ids_off + idx * 4)? as usize;
let mut off = str_data_off;
loop {
let b = *dex.get(off)?;
off += 1;
if b & 0x80 == 0 { break; }
}
let start = off;
while *dex.get(off)? != 0 { off += 1; }
dex.get(start..off)
}
fn skip_encoded_value(dex: &[u8], off: &mut usize) -> Option<()> {
let vbyte = *dex.get(*off)?;
*off += 1;
let vtype = vbyte & 0x1f;
let varg = (vbyte >> 5) as usize;
match vtype {
0x00 | 0x02 | 0x03 | 0x04 | 0x06 |
0x10 | 0x11 | 0x15 | 0x16 | 0x17 |
0x18 | 0x19 | 0x1a | 0x1b => {
*off = off.checked_add(varg + 1)?;
}
0x1c => { let size = uleb128(dex, off)?;
for _ in 0..size { skip_encoded_value(dex, off)?; }
}
0x1d => { uleb128(dex, off)?; let size = uleb128(dex, off)?;
for _ in 0..size {
uleb128(dex, off)?; skip_encoded_value(dex, off)?;
}
}
0x1e | 0x1f => {} _ => return None,
}
Some(())
}
fn read_int_encoded_value(dex: &[u8], off: &mut usize) -> Option<u32> {
let vbyte = *dex.get(*off)?;
*off += 1;
let vtype = vbyte & 0x1f;
let varg = (vbyte >> 5) as usize;
if vtype != 0x04 { return None; }
let size = varg + 1;
if size > 4 { return None; }
let mut val = 0u32;
for i in 0..size {
val |= (*dex.get(*off + i)? as u32) << (i * 8);
}
*off += size;
Some(val)
}
fn find_in_dex(dex: &[u8], class_desc: &[u8], field_name: &[u8]) -> Option<u32> {
if !dex.starts_with(DEX_MAGIC) || dex.len() < 112 { return None; }
let string_ids_size = u32le(dex, 56)? as usize;
let string_ids_off = u32le(dex, 60)? as usize;
let type_ids_size = u32le(dex, 64)? as usize;
let type_ids_off = u32le(dex, 68)? as usize;
let field_ids_size = u32le(dex, 80)? as usize;
let field_ids_off = u32le(dex, 84)? as usize;
let class_defs_size = u32le(dex, 96)? as usize;
let class_defs_off = u32le(dex, 100)? as usize;
let mut class_str_idx: Option<usize> = None;
for i in 0..string_ids_size {
if dex_string(dex, string_ids_off, i) == Some(class_desc) {
class_str_idx = Some(i);
break;
}
}
let class_str_idx = class_str_idx?;
let mut class_type_idx: Option<usize> = None;
for i in 0..type_ids_size {
if u32le(dex, type_ids_off + i * 4)? as usize == class_str_idx {
class_type_idx = Some(i);
break;
}
}
let class_type_idx = class_type_idx?;
let mut field_str_idx: Option<u32> = None;
for i in 0..string_ids_size {
if dex_string(dex, string_ids_off, i) == Some(field_name) {
field_str_idx = Some(i as u32);
break;
}
}
let field_str_idx = field_str_idx?;
let mut target_field_idx: Option<u32> = None;
for i in 0..field_ids_size {
let foff = field_ids_off + i * 8;
let fclass = u16le(dex, foff)? as usize;
let fname = u32le(dex, foff + 4)?;
if fclass == class_type_idx && fname == field_str_idx {
target_field_idx = Some(i as u32);
break;
}
}
let target_field_idx = target_field_idx?;
let mut class_data_off = None;
let mut static_vals_off = None;
for i in 0..class_defs_size {
let coff = class_defs_off + i * 32;
if u32le(dex, coff)? as usize == class_type_idx {
class_data_off = Some(u32le(dex, coff + 24)? as usize);
static_vals_off = Some(u32le(dex, coff + 28)? as usize);
break;
}
}
let class_data_off = class_data_off?;
let static_vals_off = static_vals_off?;
if class_data_off == 0 || static_vals_off == 0 { return None; }
let mut off = class_data_off;
let static_fields_size = uleb128(dex, &mut off)?;
let _instance_fields = uleb128(dex, &mut off)?;
let _direct_methods = uleb128(dex, &mut off)?;
let _virtual_methods = uleb128(dex, &mut off)?;
let mut field_pos: Option<usize> = None;
let mut cur_field_idx = 0u32;
for i in 0..static_fields_size as usize {
let diff = uleb128(dex, &mut off)?;
let _access_flags = uleb128(dex, &mut off)?;
cur_field_idx += diff;
if cur_field_idx == target_field_idx {
field_pos = Some(i);
break;
}
}
let field_pos = field_pos?;
let mut sv = static_vals_off;
let sv_size = uleb128(dex, &mut sv)? as usize;
if field_pos >= sv_size { return None; }
for i in 0..=field_pos {
if i == field_pos {
return read_int_encoded_value(dex, &mut sv);
}
skip_encoded_value(dex, &mut sv)?;
}
None
}
pub fn find_transaction_code(jar_path: &str, class_desc: &str, field_name: &str) -> Option<u32> {
let data = std::fs::read(jar_path).ok()?;
let entries = zip_find_dex_entries(&data);
for entry in &entries {
if let Some(dex) = zip_entry_data(&data, entry) {
if let Some(code) = find_in_dex(dex, class_desc.as_bytes(), field_name.as_bytes()) {
return Some(code);
}
}
}
None
}
pub fn resolve_tx_codes_from_dex() -> Option<(u32, u32, u8, u32)> {
const JAR: &str = "/system/framework/framework.jar";
const AM_STUB: &str = "Landroid/app/IActivityManager$Stub;";
const OBS_STUB: &str = "Landroid/app/IProcessObserver$Stub;";
let observer_code = find_transaction_code(JAR, AM_STUB, "TRANSACTION_registerProcessObserver")?;
let fg_code = find_transaction_code(JAR, OBS_STUB, "TRANSACTION_onForegroundActivitiesChanged")?;
if let Some(query_code) = find_transaction_code(JAR, AM_STUB, "TRANSACTION_getFocusedRootTaskInfo") {
return Some((observer_code, query_code, 1, fg_code));
}
let query_code = find_transaction_code(JAR, AM_STUB, "TRANSACTION_getFocusedStackInfo")?;
Some((observer_code, query_code, 2, fg_code))
}