Skip to main content

coreshift_core/
dex.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5//! Minimal ZIP + DEX parser for reading TRANSACTION_* static int field values
6//! from `framework.jar` without any subprocess or external tool.
7//!
8//! Only handles STORED (uncompressed) DEX entries. Android framework JARs
9//! store DEX uncompressed so ART can mmap directly from the ZIP.
10
11// ── Byte readers ──────────────────────────────────────────────────────────────
12
13fn u16le(b: &[u8], off: usize) -> Option<u16> {
14    let s = b.get(off..off + 2)?;
15    Some(u16::from_le_bytes([s[0], s[1]]))
16}
17
18fn u32le(b: &[u8], off: usize) -> Option<u32> {
19    let s = b.get(off..off + 4)?;
20    Some(u32::from_le_bytes([s[0], s[1], s[2], s[3]]))
21}
22
23fn uleb128(b: &[u8], off: &mut usize) -> Option<u32> {
24    let mut result = 0u32;
25    let mut shift = 0u32;
26    loop {
27        let byte = *b.get(*off)?;
28        *off += 1;
29        result |= ((byte & 0x7f) as u32) << shift;
30        if byte & 0x80 == 0 { return Some(result); }
31        shift += 7;
32        if shift >= 35 { return None; }
33    }
34}
35
36// ── ZIP ───────────────────────────────────────────────────────────────────────
37
38const ZIP_EOCD_SIG:   [u8; 4] = [0x50, 0x4b, 0x05, 0x06];
39const ZIP_CD_SIG:     [u8; 4] = [0x50, 0x4b, 0x01, 0x02];
40const ZIP_LOCAL_SIG:  [u8; 4] = [0x50, 0x4b, 0x03, 0x04];
41const ZIP_STORED: u16 = 0;
42
43struct ZipEntry {
44    local_off:  usize,
45    comp_size:  usize,
46    fname_hash: u64, // djb2 of filename
47}
48
49fn djb2(s: &[u8]) -> u64 {
50    let mut h = 5381u64;
51    for &b in s { h = h.wrapping_mul(33).wrapping_add(b as u64); }
52    h
53}
54
55fn zip_find_dex_entries(data: &[u8]) -> Vec<ZipEntry> {
56    // Scan backwards for EOCD (ignore ZIP comment — Android JARs have none).
57    let scan_start = data.len().saturating_sub(65558);
58    let eocd_pos = match data[scan_start..]
59        .windows(4)
60        .rposition(|w| w == ZIP_EOCD_SIG)
61    {
62        Some(p) => scan_start + p,
63        None    => return Vec::new(),
64    };
65
66    let cd_size = match u32le(data, eocd_pos + 12) { Some(v) => v as usize, None => return Vec::new() };
67    let cd_off  = match u32le(data, eocd_pos + 16) { Some(v) => v as usize, None => return Vec::new() };
68
69    let mut pos = cd_off;
70    let cd_end  = cd_off.saturating_add(cd_size);
71    let mut entries = Vec::new();
72
73    while pos + 46 <= cd_end && pos + 46 <= data.len() {
74        if data.get(pos..pos + 4) != Some(&ZIP_CD_SIG) { break; }
75
76        let compression = match u16le(data, pos + 10) { Some(v) => v, None => break };
77        let comp_size   = match u32le(data, pos + 20) { Some(v) => v as usize, None => break };
78        let local_off   = match u32le(data, pos + 42) { Some(v) => v as usize, None => break };
79        let fname_len   = match u16le(data, pos + 28) { Some(v) => v as usize, None => break };
80        let extra_len   = match u16le(data, pos + 30) { Some(v) => v as usize, None => break };
81        let comment_len = match u16le(data, pos + 32) { Some(v) => v as usize, None => break };
82
83        let fname_end = pos + 46 + fname_len;
84        if fname_end > data.len() { break; }
85        let fname = &data[pos + 46..fname_end];
86
87        // Collect classes*.dex entries that are STORED.
88        let is_dex = fname.starts_with(b"classes") && fname.ends_with(b".dex");
89        if is_dex && compression == ZIP_STORED {
90            entries.push(ZipEntry {
91                local_off,
92                comp_size,
93                fname_hash: djb2(fname),
94            });
95        }
96
97        pos = match pos.checked_add(46 + fname_len + extra_len + comment_len) {
98            Some(v) => v,
99            None    => break,
100        };
101    }
102
103    // Sort classes.dex first (djb2 of b"classes.dex" < b"classes2.dex" etc. by content — just sort by hash to be deterministic; classes.dex is shortest so sort by fname_hash ascending mimics alphabetical).
104    // Actually sort by comp_size descending: largest DEX is most likely to contain IActivityManager.
105    entries.sort_by(|a, b| b.comp_size.cmp(&a.comp_size));
106    entries
107}
108
109fn zip_entry_data<'a>(data: &'a [u8], entry: &ZipEntry) -> Option<&'a [u8]> {
110    let lh = entry.local_off;
111    if data.get(lh..lh + 4) != Some(&ZIP_LOCAL_SIG) { return None; }
112    let fname_len = u16le(data, lh + 26)? as usize;
113    let extra_len = u16le(data, lh + 28)? as usize;
114    let data_start = lh + 30 + fname_len + extra_len;
115    data.get(data_start..data_start + entry.comp_size)
116}
117
118// ── DEX ───────────────────────────────────────────────────────────────────────
119
120const DEX_MAGIC: &[u8] = b"dex\n";
121
122fn dex_string<'a>(dex: &'a [u8], string_ids_off: usize, idx: usize) -> Option<&'a [u8]> {
123    let str_data_off = u32le(dex, string_ids_off + idx * 4)? as usize;
124    // Skip ULEB128 UTF-16 length
125    let mut off = str_data_off;
126    loop {
127        let b = *dex.get(off)?;
128        off += 1;
129        if b & 0x80 == 0 { break; }
130    }
131    // Null-terminated MUTF-8 bytes
132    let start = off;
133    while *dex.get(off)? != 0 { off += 1; }
134    dex.get(start..off)
135}
136
137fn skip_encoded_value(dex: &[u8], off: &mut usize) -> Option<()> {
138    let vbyte = *dex.get(*off)?;
139    *off += 1;
140    let vtype = vbyte & 0x1f;
141    let varg  = (vbyte >> 5) as usize;
142    match vtype {
143        // value_arg+1 bytes follow
144        0x00 | 0x02 | 0x03 | 0x04 | 0x06 |
145        0x10 | 0x11 | 0x15 | 0x16 | 0x17 |
146        0x18 | 0x19 | 0x1a | 0x1b => {
147            *off = off.checked_add(varg + 1)?;
148        }
149        0x1c => { // VALUE_ARRAY
150            let size = uleb128(dex, off)?;
151            for _ in 0..size { skip_encoded_value(dex, off)?; }
152        }
153        0x1d => { // VALUE_ANNOTATION
154            uleb128(dex, off)?; // type_idx
155            let size = uleb128(dex, off)?;
156            for _ in 0..size {
157                uleb128(dex, off)?; // name_idx
158                skip_encoded_value(dex, off)?;
159            }
160        }
161        0x1e | 0x1f => {} // VALUE_NULL / VALUE_BOOLEAN — no extra bytes
162        _ => return None,
163    }
164    Some(())
165}
166
167fn read_int_encoded_value(dex: &[u8], off: &mut usize) -> Option<u32> {
168    let vbyte = *dex.get(*off)?;
169    *off += 1;
170    let vtype = vbyte & 0x1f;
171    let varg  = (vbyte >> 5) as usize;
172    // VALUE_INT = 0x04, value_arg+1 bytes, little-endian
173    if vtype != 0x04 { return None; }
174    let size = varg + 1;
175    if size > 4 { return None; }
176    let mut val = 0u32;
177    for i in 0..size {
178        val |= (*dex.get(*off + i)? as u32) << (i * 8);
179    }
180    *off += size;
181    Some(val)
182}
183
184fn find_in_dex(dex: &[u8], class_desc: &[u8], field_name: &[u8]) -> Option<u32> {
185    if !dex.starts_with(DEX_MAGIC) || dex.len() < 112 { return None; }
186
187    let string_ids_size = u32le(dex, 56)? as usize;
188    let string_ids_off  = u32le(dex, 60)? as usize;
189    let type_ids_size   = u32le(dex, 64)? as usize;
190    let type_ids_off    = u32le(dex, 68)? as usize;
191    let field_ids_size  = u32le(dex, 80)? as usize;
192    let field_ids_off   = u32le(dex, 84)? as usize;
193    let class_defs_size = u32le(dex, 96)? as usize;
194    let class_defs_off  = u32le(dex, 100)? as usize;
195
196    // Find string index for class descriptor
197    let mut class_str_idx: Option<usize> = None;
198    for i in 0..string_ids_size {
199        if dex_string(dex, string_ids_off, i) == Some(class_desc) {
200            class_str_idx = Some(i);
201            break;
202        }
203    }
204    let class_str_idx = class_str_idx?;
205
206    // Find type index
207    let mut class_type_idx: Option<usize> = None;
208    for i in 0..type_ids_size {
209        if u32le(dex, type_ids_off + i * 4)? as usize == class_str_idx {
210            class_type_idx = Some(i);
211            break;
212        }
213    }
214    let class_type_idx = class_type_idx?;
215
216    // Find string index for field name
217    let mut field_str_idx: Option<u32> = None;
218    for i in 0..string_ids_size {
219        if dex_string(dex, string_ids_off, i) == Some(field_name) {
220            field_str_idx = Some(i as u32);
221            break;
222        }
223    }
224    let field_str_idx = field_str_idx?;
225
226    // Find global field_idx in field_ids
227    let mut target_field_idx: Option<u32> = None;
228    for i in 0..field_ids_size {
229        let foff = field_ids_off + i * 8;
230        let fclass = u16le(dex, foff)? as usize;
231        let fname  = u32le(dex, foff + 4)?;
232        if fclass == class_type_idx && fname == field_str_idx {
233            target_field_idx = Some(i as u32);
234            break;
235        }
236    }
237    let target_field_idx = target_field_idx?;
238
239    // Find class def
240    let mut class_data_off  = None;
241    let mut static_vals_off = None;
242    for i in 0..class_defs_size {
243        let coff = class_defs_off + i * 32;
244        if u32le(dex, coff)? as usize == class_type_idx {
245            class_data_off  = Some(u32le(dex, coff + 24)? as usize);
246            static_vals_off = Some(u32le(dex, coff + 28)? as usize);
247            break;
248        }
249    }
250    let class_data_off  = class_data_off?;
251    let static_vals_off = static_vals_off?;
252    if class_data_off == 0 || static_vals_off == 0 { return None; }
253
254    // Walk class_data_item static fields to find position of target_field_idx
255    let mut off = class_data_off;
256    let static_fields_size  = uleb128(dex, &mut off)?;
257    let _instance_fields    = uleb128(dex, &mut off)?;
258    let _direct_methods     = uleb128(dex, &mut off)?;
259    let _virtual_methods    = uleb128(dex, &mut off)?;
260
261    let mut field_pos: Option<usize> = None;
262    let mut cur_field_idx = 0u32;
263    for i in 0..static_fields_size as usize {
264        let diff         = uleb128(dex, &mut off)?;
265        let _access_flags = uleb128(dex, &mut off)?;
266        cur_field_idx += diff;
267        if cur_field_idx == target_field_idx {
268            field_pos = Some(i);
269            break;
270        }
271    }
272    let field_pos = field_pos?;
273
274    // Read encoded_array at static_vals_off, skip to field_pos, read int
275    let mut sv = static_vals_off;
276    let sv_size = uleb128(dex, &mut sv)? as usize;
277    if field_pos >= sv_size { return None; }
278
279    for i in 0..=field_pos {
280        if i == field_pos {
281            return read_int_encoded_value(dex, &mut sv);
282        }
283        skip_encoded_value(dex, &mut sv)?;
284    }
285    None
286}
287
288// ── Public API ────────────────────────────────────────────────────────────────
289
290/// Search `framework.jar` for the value of a static int field.
291///
292/// `class_desc` uses DEX descriptor syntax, e.g.
293/// `"Landroid/app/IActivityManager$Stub;"`.
294///
295/// Returns `None` if the JAR is unreadable, the class/field is absent, or
296/// the entry is compressed (DEFLATE — not expected for framework DEX).
297pub fn find_transaction_code(jar_path: &str, class_desc: &str, field_name: &str) -> Option<u32> {
298    let data = std::fs::read(jar_path).ok()?;
299    let entries = zip_find_dex_entries(&data);
300    for entry in &entries {
301        if let Some(dex) = zip_entry_data(&data, entry) {
302            if let Some(code) = find_in_dex(dex, class_desc.as_bytes(), field_name.as_bytes()) {
303                return Some(code);
304            }
305        }
306    }
307    None
308}
309
310/// Resolve all four tx codes needed for binder observer mode.
311///
312/// Returns `(observer_code, query_code, api_mode, fg_code)` where:
313/// - `observer_code` = `TRANSACTION_registerProcessObserver`
314/// - `query_code`    = `TRANSACTION_getFocusedRootTaskInfo` (or StackInfo on API 29)
315/// - `api_mode`      = 1 (RootTaskInfo) or 2 (StackInfo)
316/// - `fg_code`       = `TRANSACTION_onForegroundActivitiesChanged`
317pub fn resolve_tx_codes_from_dex() -> Option<(u32, u32, u8, u32)> {
318    const JAR: &str = "/system/framework/framework.jar";
319    const AM_STUB:  &str = "Landroid/app/IActivityManager$Stub;";
320    const OBS_STUB: &str = "Landroid/app/IProcessObserver$Stub;";
321
322    let observer_code = find_transaction_code(JAR, AM_STUB, "TRANSACTION_registerProcessObserver")?;
323    let fg_code = find_transaction_code(JAR, OBS_STUB, "TRANSACTION_onForegroundActivitiesChanged")?;
324
325    if let Some(query_code) = find_transaction_code(JAR, AM_STUB, "TRANSACTION_getFocusedRootTaskInfo") {
326        return Some((observer_code, query_code, 1, fg_code));
327    }
328    // API 29 fallback
329    let query_code = find_transaction_code(JAR, AM_STUB, "TRANSACTION_getFocusedStackInfo")?;
330    Some((observer_code, query_code, 2, fg_code))
331}