1fn 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
36const 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, }
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 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 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 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
118const 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 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 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 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 => { let size = uleb128(dex, off)?;
151 for _ in 0..size { skip_encoded_value(dex, off)?; }
152 }
153 0x1d => { uleb128(dex, off)?; let size = uleb128(dex, off)?;
156 for _ in 0..size {
157 uleb128(dex, off)?; skip_encoded_value(dex, off)?;
159 }
160 }
161 0x1e | 0x1f => {} _ => 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 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 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 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 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 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 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 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 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
288fn method_param_types<'a>(dex: &'a [u8], class_desc: &[u8], method_name: &[u8]) -> Option<Vec<&'a [u8]>> {
298 if !dex.starts_with(DEX_MAGIC) || dex.len() < 112 { return None; }
299
300 let string_ids_size = u32le(dex, 56)? as usize;
301 let string_ids_off = u32le(dex, 60)? as usize;
302 let type_ids_size = u32le(dex, 64)? as usize;
303 let type_ids_off = u32le(dex, 68)? as usize;
304 let proto_ids_size = u32le(dex, 72)? as usize;
305 let proto_ids_off = u32le(dex, 76)? as usize;
306 let method_ids_size = u32le(dex, 88)? as usize;
307 let method_ids_off = u32le(dex, 92)? as usize;
308
309 let mut class_str_idx: Option<usize> = None;
311 for i in 0..string_ids_size {
312 if dex_string(dex, string_ids_off, i) == Some(class_desc) {
313 class_str_idx = Some(i);
314 break;
315 }
316 }
317 let class_str_idx = class_str_idx?;
318
319 let mut class_type_idx: Option<usize> = None;
321 for i in 0..type_ids_size {
322 if u32le(dex, type_ids_off + i * 4)? as usize == class_str_idx {
323 class_type_idx = Some(i);
324 break;
325 }
326 }
327 let class_type_idx = class_type_idx?;
328
329 let mut method_str_idx: Option<u32> = None;
331 for i in 0..string_ids_size {
332 if dex_string(dex, string_ids_off, i) == Some(method_name) {
333 method_str_idx = Some(i as u32);
334 break;
335 }
336 }
337 let method_str_idx = method_str_idx?;
338
339 let mut proto_idx: Option<usize> = None;
341 for i in 0..method_ids_size {
342 let moff = method_ids_off + i * 8;
343 let fclass = u16le(dex, moff)? as usize;
344 let fproto = u16le(dex, moff + 2)? as usize;
345 let fname = u32le(dex, moff + 4)?;
346 if fclass == class_type_idx && fname == method_str_idx {
347 proto_idx = Some(fproto);
348 break;
349 }
350 }
351 let proto_idx = proto_idx?;
352 if proto_idx >= proto_ids_size { return None; }
353
354 let poff = proto_ids_off + proto_idx * 12;
356 let params_off = u32le(dex, poff + 8)? as usize;
357 if params_off == 0 { return Some(Vec::new()); }
358
359 let param_count = u32le(dex, params_off)? as usize;
361 let mut params = Vec::with_capacity(param_count);
362 for i in 0..param_count {
363 let tidx = u16le(dex, params_off + 4 + i * 2)? as usize;
364 let desc_str_idx = u32le(dex, type_ids_off + tidx * 4)? as usize;
365 let desc = dex_string(dex, string_ids_off, desc_str_idx)?;
366 params.push(desc);
367 }
368 Some(params)
369}
370
371pub fn find_transaction_code(jar_path: &str, class_desc: &str, field_name: &str) -> Option<u32> {
381 let data = std::fs::read(jar_path).ok()?;
382 let entries = zip_find_dex_entries(&data);
383 for entry in &entries {
384 if let Some(dex) = zip_entry_data(&data, entry) {
385 if let Some(code) = find_in_dex(dex, class_desc.as_bytes(), field_name.as_bytes()) {
386 return Some(code);
387 }
388 }
389 }
390 None
391}
392
393pub fn resolve_tx_codes_from_dex() -> Option<(u32, u32, u8, u32)> {
401 const JAR: &str = "/system/framework/framework.jar";
402 const AM_STUB: &str = "Landroid/app/IActivityManager$Stub;";
403 const OBS_STUB: &str = "Landroid/app/IProcessObserver$Stub;";
404
405 let observer_code = find_transaction_code(JAR, AM_STUB, "TRANSACTION_registerProcessObserver")?;
406 let fg_code = find_transaction_code(JAR, OBS_STUB, "TRANSACTION_onForegroundActivitiesChanged")?;
407
408 if let Some(query_code) = find_transaction_code(JAR, AM_STUB, "TRANSACTION_getFocusedRootTaskInfo") {
409 return Some((observer_code, query_code, 1, fg_code));
410 }
411 let query_code = find_transaction_code(JAR, AM_STUB, "TRANSACTION_getFocusedStackInfo")?;
413 Some((observer_code, query_code, 2, fg_code))
414}
415
416pub fn resolve_is_interactive_tx() -> Option<u32> {
418 const JAR: &str = "/system/framework/framework.jar";
419 find_transaction_code(JAR, "Landroid/os/IPowerManager$Stub;", "TRANSACTION_isInteractive")
420}
421
422pub fn resolve_fgproc_codes() -> Option<(u32, u32)> {
428 const JAR: &str = "/system/framework/framework.jar";
429 const AM_STUB: &str = "Landroid/app/IActivityManager$Stub;";
430 const FGPROC_STUB: &str = "Landroid/app/IForegroundProcessObserver$Stub;";
431
432 let register_code =
433 find_transaction_code(JAR, AM_STUB, "TRANSACTION_registerForegroundProcessObserver")?;
434 let on_change_code =
435 find_transaction_code(JAR, FGPROC_STUB, "TRANSACTION_onForegroundProcessChanged")?;
436 Some((register_code, on_change_code))
437}
438
439pub fn resolve_fgproc_codes_fallback() -> Option<(u32, u32)> {
454 const JAR: &str = "/system/framework/framework.jar";
455 const AM_STUB: &str = "Landroid/app/IActivityManager$Stub;";
456 const OBS_STUB: &str = "Landroid/app/IProcessObserver$Stub;";
457 const OBS_IFACE: &str = "Landroid/app/IProcessObserver;";
458
459 if resolve_fgproc_codes().is_some() {
461 return None;
462 }
463
464 let data = std::fs::read(JAR).ok()?;
465 let entries = zip_find_dex_entries(&data);
466
467 let mut pid_first = false;
470 for entry in &entries {
471 if let Some(dex) = zip_entry_data(&data, entry) {
472 if let Some(params) = method_param_types(dex, OBS_IFACE.as_bytes(), b"onForegroundActivitiesChanged") {
473 pid_first = params.first().map(|t| *t == b"I").unwrap_or(false);
474 break;
475 }
476 }
477 }
478 if !pid_first { return None; }
479
480 let register_code = find_transaction_code(JAR, AM_STUB, "TRANSACTION_registerProcessObserver")?;
481 let on_change_code =
482 find_transaction_code(JAR, OBS_STUB, "TRANSACTION_onForegroundActivitiesChanged")?;
483 Some((register_code, on_change_code))
484}
485
486pub fn resolve_fps_codes() -> Option<(u32, u32, u32)> {
493 const JAR: &str = "/system/framework/framework.jar";
494 const IWM_STUB: &str = "Landroid/view/IWindowManager$Stub;";
495 const FPS_STUB: &str = "Landroid/window/ITaskFpsCallback$Stub;";
496
497 let register_code = find_transaction_code(JAR, IWM_STUB, "TRANSACTION_registerTaskFpsCallback")?;
498 let unregister_code =
499 find_transaction_code(JAR, IWM_STUB, "TRANSACTION_unregisterTaskFpsCallback")?;
500 let on_fps_code = find_transaction_code(JAR, FPS_STUB, "TRANSACTION_onFpsReported")?;
501 Some((register_code, unregister_code, on_fps_code))
502}
503
504pub fn resolve_task_stack_codes() -> Option<(u32, u32)> {
515 const JAR: &str = "/system/framework/framework.jar";
516 const ATM_STUB: &str = "Landroid/app/IActivityTaskManager$Stub;";
517
518 let register_code =
519 find_transaction_code(JAR, ATM_STUB, "TRANSACTION_registerTaskStackListener")?;
520 let unregister_code =
521 find_transaction_code(JAR, ATM_STUB, "TRANSACTION_unregisterTaskStackListener")?;
522 Some((register_code, unregister_code))
523}
524
525#[cfg(test)]
526mod tests {
527 use super::*;
528
529 fn u32put(b: &mut Vec<u8>, off: usize, v: u32) {
530 b[off..off + 4].copy_from_slice(&v.to_le_bytes());
531 }
532
533 fn build_dex(class_desc: &[u8], method_name: &[u8], params: &[&[u8]]) -> Vec<u8> {
537 let mut out = vec![0u8; 112];
538 out[0..4].copy_from_slice(DEX_MAGIC);
539 out[4..8].copy_from_slice(b"035\0");
540
541 let mut strings: Vec<Vec<u8>> = vec![class_desc.to_vec(), method_name.to_vec()];
542 for p in params {
543 if !strings.iter().any(|s| s == p) {
544 strings.push(p.to_vec());
545 }
546 }
547 let n_string = strings.len();
548 let mut types: Vec<Vec<u8>> = vec![class_desc.to_vec()];
549 for p in params {
550 if !types.iter().any(|t| t == p) {
551 types.push(p.to_vec());
552 }
553 }
554 let n_type = types.len();
555
556 let off_string_ids = 112;
557 let off_type_ids = off_string_ids + n_string * 4;
558 let off_proto_ids = off_type_ids + n_type * 4;
559 let off_method_ids = off_proto_ids + 12; out.resize(off_method_ids + 8, 0); let mut string_offs = Vec::with_capacity(n_string);
566 for s in &strings {
567 string_offs.push(out.len());
568 let n = s.len() as u8;
569 out.push(n); out.extend_from_slice(s);
571 out.push(0);
572 }
573
574 let params_off = out.len();
576 out.extend_from_slice(&(params.len() as u32).to_le_bytes());
577 for p in params {
578 let ti = types.iter().position(|t| t == p).unwrap();
579 out.extend_from_slice(&(ti as u16).to_le_bytes());
580 }
581
582 u32put(&mut out, 56, n_string as u32);
584 u32put(&mut out, 60, off_string_ids as u32);
585 u32put(&mut out, 64, n_type as u32);
586 u32put(&mut out, 68, off_type_ids as u32);
587 u32put(&mut out, 72, 1); u32put(&mut out, 76, off_proto_ids as u32);
589 u32put(&mut out, 80, 0); u32put(&mut out, 84, 0);
591 u32put(&mut out, 88, 1); u32put(&mut out, 92, off_method_ids as u32);
593 u32put(&mut out, 96, 0); u32put(&mut out, 100, 0);
595
596 for (i, &so) in string_offs.iter().enumerate() {
598 u32put(&mut out, off_string_ids + i * 4, so as u32);
599 }
600 for (i, t) in types.iter().enumerate() {
602 let si = strings.iter().position(|s| s == t).unwrap();
603 u32put(&mut out, off_type_ids + i * 4, si as u32);
604 }
605 u32put(&mut out, off_proto_ids + 0, 0);
607 u32put(&mut out, off_proto_ids + 4, 0);
608 u32put(&mut out, off_proto_ids + 8, params_off as u32);
609 let m = off_method_ids;
611 out[m..m + 2].copy_from_slice(&0u16.to_le_bytes());
612 out[m + 2..m + 4].copy_from_slice(&0u16.to_le_bytes());
613 u32put(&mut out, m + 4, 1);
614
615 out
616 }
617
618 #[test]
619 fn parses_int_first_proto() {
620 let dex = build_dex(b"Landroid/app/IProcessObserver;", b"onForegroundActivitiesChanged", &[b"I", b"I", b"Z"]);
621 let params = method_param_types(&dex, b"Landroid/app/IProcessObserver;", b"onForegroundActivitiesChanged")
622 .expect("method present");
623 assert_eq!(params, [&b"I"[..], &b"I"[..], &b"Z"[..]]);
624 }
625
626 #[test]
627 fn rejects_string_first_proto() {
628 let dex = build_dex(b"Landroid/app/IProcessObserver;", b"onForegroundActivitiesChanged", &[b"Ljava/lang/String;", b"I", b"Z"]);
630 let params = method_param_types(&dex, b"Landroid/app/IProcessObserver;", b"onForegroundActivitiesChanged")
631 .expect("method present");
632 assert_eq!(params.first().map(|t| *t == b"I"), Some(false));
633 }
634
635 #[test]
636 fn absent_method_returns_none() {
637 let dex = build_dex(b"Landroid/app/IProcessObserver;", b"onForegroundActivitiesChanged", &[b"I"]);
638 assert!(method_param_types(&dex, b"Landroid/app/IProcessObserver;", b"onProcessDied").is_none());
639 }
640}