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 {
31 return Some(result);
32 }
33 shift += 7;
34 if shift >= 35 {
35 return None;
36 }
37 }
38}
39
40const ZIP_EOCD_SIG: [u8; 4] = [0x50, 0x4b, 0x05, 0x06];
43const ZIP_CD_SIG: [u8; 4] = [0x50, 0x4b, 0x01, 0x02];
44const ZIP_LOCAL_SIG: [u8; 4] = [0x50, 0x4b, 0x03, 0x04];
45const ZIP_STORED: u16 = 0;
46
47struct ZipEntry {
48 local_off: usize,
49 comp_size: usize,
50}
51
52fn zip_find_dex_entries(data: &[u8]) -> Vec<ZipEntry> {
53 let scan_start = data.len().saturating_sub(65558);
55 let eocd_pos = match data[scan_start..]
56 .windows(4)
57 .rposition(|w| w == ZIP_EOCD_SIG)
58 {
59 Some(p) => scan_start + p,
60 None => return Vec::new(),
61 };
62
63 let cd_size = match u32le(data, eocd_pos + 12) {
64 Some(v) => v as usize,
65 None => return Vec::new(),
66 };
67 let cd_off = match u32le(data, eocd_pos + 16) {
68 Some(v) => v as usize,
69 None => return Vec::new(),
70 };
71
72 let mut pos = cd_off;
73 let cd_end = cd_off.saturating_add(cd_size);
74 let mut entries = Vec::new();
75
76 while pos + 46 <= cd_end && pos + 46 <= data.len() {
77 if data.get(pos..pos + 4) != Some(&ZIP_CD_SIG) {
78 break;
79 }
80
81 let compression = match u16le(data, pos + 10) {
82 Some(v) => v,
83 None => break,
84 };
85 let comp_size = match u32le(data, pos + 20) {
86 Some(v) => v as usize,
87 None => break,
88 };
89 let local_off = match u32le(data, pos + 42) {
90 Some(v) => v as usize,
91 None => break,
92 };
93 let fname_len = match u16le(data, pos + 28) {
94 Some(v) => v as usize,
95 None => break,
96 };
97 let extra_len = match u16le(data, pos + 30) {
98 Some(v) => v as usize,
99 None => break,
100 };
101 let comment_len = match u16le(data, pos + 32) {
102 Some(v) => v as usize,
103 None => break,
104 };
105
106 let fname_end = pos + 46 + fname_len;
107 if fname_end > data.len() {
108 break;
109 }
110 let fname = &data[pos + 46..fname_end];
111
112 let is_dex = fname.starts_with(b"classes") && fname.ends_with(b".dex");
114 if is_dex && compression == ZIP_STORED {
115 entries.push(ZipEntry {
116 local_off,
117 comp_size,
118 });
119 }
120
121 pos = match pos.checked_add(46 + fname_len + extra_len + comment_len) {
122 Some(v) => v,
123 None => break,
124 };
125 }
126
127 entries.sort_by_key(|e| std::cmp::Reverse(e.comp_size));
130 entries
131}
132
133fn zip_entry_data<'a>(data: &'a [u8], entry: &ZipEntry) -> Option<&'a [u8]> {
134 let lh = entry.local_off;
135 if data.get(lh..lh + 4) != Some(&ZIP_LOCAL_SIG) {
136 return None;
137 }
138 let fname_len = u16le(data, lh + 26)? as usize;
139 let extra_len = u16le(data, lh + 28)? as usize;
140 let data_start = lh + 30 + fname_len + extra_len;
141 data.get(data_start..data_start + entry.comp_size)
142}
143
144const DEX_MAGIC: &[u8] = b"dex\n";
147
148fn dex_string(dex: &[u8], string_ids_off: usize, idx: usize) -> Option<&[u8]> {
149 let str_data_off = u32le(dex, string_ids_off + idx * 4)? as usize;
150 let mut off = str_data_off;
152 loop {
153 let b = *dex.get(off)?;
154 off += 1;
155 if b & 0x80 == 0 {
156 break;
157 }
158 }
159 let start = off;
161 while *dex.get(off)? != 0 {
162 off += 1;
163 }
164 dex.get(start..off)
165}
166
167fn skip_encoded_value(dex: &[u8], off: &mut usize) -> Option<()> {
168 let vbyte = *dex.get(*off)?;
169 *off += 1;
170 let vtype = vbyte & 0x1f;
171 let varg = (vbyte >> 5) as usize;
172 match vtype {
173 0x00 | 0x02 | 0x03 | 0x04 | 0x06 | 0x10 | 0x11 | 0x15 | 0x16 | 0x17 | 0x18 | 0x19
175 | 0x1a | 0x1b => {
176 *off = off.checked_add(varg + 1)?;
177 }
178 0x1c => {
179 let size = uleb128(dex, off)?;
181 for _ in 0..size {
182 skip_encoded_value(dex, off)?;
183 }
184 }
185 0x1d => {
186 uleb128(dex, off)?; let size = uleb128(dex, off)?;
189 for _ in 0..size {
190 uleb128(dex, off)?; skip_encoded_value(dex, off)?;
192 }
193 }
194 0x1e | 0x1f => {} _ => return None,
196 }
197 Some(())
198}
199
200fn read_int_encoded_value(dex: &[u8], off: &mut usize) -> Option<u32> {
201 let vbyte = *dex.get(*off)?;
202 *off += 1;
203 let vtype = vbyte & 0x1f;
204 let varg = (vbyte >> 5) as usize;
205 if vtype != 0x04 {
207 return None;
208 }
209 let size = varg + 1;
210 if size > 4 {
211 return None;
212 }
213 let mut val = 0u32;
214 for i in 0..size {
215 val |= (*dex.get(*off + i)? as u32) << (i * 8);
216 }
217 *off += size;
218 Some(val)
219}
220
221fn find_in_dex(dex: &[u8], class_desc: &[u8], field_name: &[u8]) -> Option<u32> {
222 if !dex.starts_with(DEX_MAGIC) || dex.len() < 112 {
223 return None;
224 }
225
226 let string_ids_size = u32le(dex, 56)? as usize;
227 let string_ids_off = u32le(dex, 60)? as usize;
228 let type_ids_size = u32le(dex, 64)? as usize;
229 let type_ids_off = u32le(dex, 68)? as usize;
230 let field_ids_size = u32le(dex, 80)? as usize;
231 let field_ids_off = u32le(dex, 84)? as usize;
232 let class_defs_size = u32le(dex, 96)? as usize;
233 let class_defs_off = u32le(dex, 100)? as usize;
234
235 let mut class_str_idx: Option<usize> = None;
237 for i in 0..string_ids_size {
238 if dex_string(dex, string_ids_off, i) == Some(class_desc) {
239 class_str_idx = Some(i);
240 break;
241 }
242 }
243 let class_str_idx = class_str_idx?;
244
245 let mut class_type_idx: Option<usize> = None;
247 for i in 0..type_ids_size {
248 if u32le(dex, type_ids_off + i * 4)? as usize == class_str_idx {
249 class_type_idx = Some(i);
250 break;
251 }
252 }
253 let class_type_idx = class_type_idx?;
254
255 let mut field_str_idx: Option<u32> = None;
257 for i in 0..string_ids_size {
258 if dex_string(dex, string_ids_off, i) == Some(field_name) {
259 field_str_idx = Some(i as u32);
260 break;
261 }
262 }
263 let field_str_idx = field_str_idx?;
264
265 let mut target_field_idx: Option<u32> = None;
267 for i in 0..field_ids_size {
268 let foff = field_ids_off + i * 8;
269 let fclass = u16le(dex, foff)? as usize;
270 let fname = u32le(dex, foff + 4)?;
271 if fclass == class_type_idx && fname == field_str_idx {
272 target_field_idx = Some(i as u32);
273 break;
274 }
275 }
276 let target_field_idx = target_field_idx?;
277
278 let mut class_data_off = None;
280 let mut static_vals_off = None;
281 for i in 0..class_defs_size {
282 let coff = class_defs_off + i * 32;
283 if u32le(dex, coff)? as usize == class_type_idx {
284 class_data_off = Some(u32le(dex, coff + 24)? as usize);
285 static_vals_off = Some(u32le(dex, coff + 28)? as usize);
286 break;
287 }
288 }
289 let class_data_off = class_data_off?;
290 let static_vals_off = static_vals_off?;
291 if class_data_off == 0 || static_vals_off == 0 {
292 return None;
293 }
294
295 let mut off = class_data_off;
297 let static_fields_size = uleb128(dex, &mut off)?;
298 let _instance_fields = uleb128(dex, &mut off)?;
299 let _direct_methods = uleb128(dex, &mut off)?;
300 let _virtual_methods = uleb128(dex, &mut off)?;
301
302 let mut field_pos: Option<usize> = None;
303 let mut cur_field_idx = 0u32;
304 for i in 0..static_fields_size as usize {
305 let diff = uleb128(dex, &mut off)?;
306 let _access_flags = uleb128(dex, &mut off)?;
307 cur_field_idx += diff;
308 if cur_field_idx == target_field_idx {
309 field_pos = Some(i);
310 break;
311 }
312 }
313 let field_pos = field_pos?;
314
315 let mut sv = static_vals_off;
317 let sv_size = uleb128(dex, &mut sv)? as usize;
318 if field_pos >= sv_size {
319 return None;
320 }
321
322 for i in 0..=field_pos {
323 if i == field_pos {
324 return read_int_encoded_value(dex, &mut sv);
325 }
326 skip_encoded_value(dex, &mut sv)?;
327 }
328 None
329}
330
331fn method_param_types<'a>(
341 dex: &'a [u8],
342 class_desc: &[u8],
343 method_name: &[u8],
344) -> Option<Vec<&'a [u8]>> {
345 if !dex.starts_with(DEX_MAGIC) || dex.len() < 112 {
346 return None;
347 }
348
349 let string_ids_size = u32le(dex, 56)? as usize;
350 let string_ids_off = u32le(dex, 60)? as usize;
351 let type_ids_size = u32le(dex, 64)? as usize;
352 let type_ids_off = u32le(dex, 68)? as usize;
353 let proto_ids_size = u32le(dex, 72)? as usize;
354 let proto_ids_off = u32le(dex, 76)? as usize;
355 let method_ids_size = u32le(dex, 88)? as usize;
356 let method_ids_off = u32le(dex, 92)? as usize;
357
358 let mut class_str_idx: Option<usize> = None;
360 for i in 0..string_ids_size {
361 if dex_string(dex, string_ids_off, i) == Some(class_desc) {
362 class_str_idx = Some(i);
363 break;
364 }
365 }
366 let class_str_idx = class_str_idx?;
367
368 let mut class_type_idx: Option<usize> = None;
370 for i in 0..type_ids_size {
371 if u32le(dex, type_ids_off + i * 4)? as usize == class_str_idx {
372 class_type_idx = Some(i);
373 break;
374 }
375 }
376 let class_type_idx = class_type_idx?;
377
378 let mut method_str_idx: Option<u32> = None;
380 for i in 0..string_ids_size {
381 if dex_string(dex, string_ids_off, i) == Some(method_name) {
382 method_str_idx = Some(i as u32);
383 break;
384 }
385 }
386 let method_str_idx = method_str_idx?;
387
388 let mut proto_idx: Option<usize> = None;
390 for i in 0..method_ids_size {
391 let moff = method_ids_off + i * 8;
392 let fclass = u16le(dex, moff)? as usize;
393 let fproto = u16le(dex, moff + 2)? as usize;
394 let fname = u32le(dex, moff + 4)?;
395 if fclass == class_type_idx && fname == method_str_idx {
396 proto_idx = Some(fproto);
397 break;
398 }
399 }
400 let proto_idx = proto_idx?;
401 if proto_idx >= proto_ids_size {
402 return None;
403 }
404
405 let poff = proto_ids_off + proto_idx * 12;
407 let params_off = u32le(dex, poff + 8)? as usize;
408 if params_off == 0 {
409 return Some(Vec::new());
410 }
411
412 let param_count = u32le(dex, params_off)? as usize;
414 let mut params = Vec::with_capacity(param_count);
415 for i in 0..param_count {
416 let tidx = u16le(dex, params_off + 4 + i * 2)? as usize;
417 let desc_str_idx = u32le(dex, type_ids_off + tidx * 4)? as usize;
418 let desc = dex_string(dex, string_ids_off, desc_str_idx)?;
419 params.push(desc);
420 }
421 Some(params)
422}
423
424pub fn find_transaction_code(jar_path: &str, class_desc: &str, field_name: &str) -> Option<u32> {
434 let data = std::fs::read(jar_path).ok()?;
435 let entries = zip_find_dex_entries(&data);
436 for entry in &entries {
437 if let Some(dex) = zip_entry_data(&data, entry) {
438 if let Some(code) = find_in_dex(dex, class_desc.as_bytes(), field_name.as_bytes()) {
439 return Some(code);
440 }
441 }
442 }
443 None
444}
445
446pub fn resolve_tx_codes_from_dex() -> Option<(u32, u32, u8, 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
458 let observer_code = find_transaction_code(JAR, AM_STUB, "TRANSACTION_registerProcessObserver")?;
459 let fg_code =
460 find_transaction_code(JAR, OBS_STUB, "TRANSACTION_onForegroundActivitiesChanged")?;
461
462 if let Some(query_code) =
463 find_transaction_code(JAR, AM_STUB, "TRANSACTION_getFocusedRootTaskInfo")
464 {
465 return Some((observer_code, query_code, 1, fg_code));
466 }
467 let query_code = find_transaction_code(JAR, AM_STUB, "TRANSACTION_getFocusedStackInfo")?;
469 Some((observer_code, query_code, 2, fg_code))
470}
471
472pub fn resolve_is_interactive_tx() -> Option<u32> {
474 const JAR: &str = "/system/framework/framework.jar";
475 find_transaction_code(
476 JAR,
477 "Landroid/os/IPowerManager$Stub;",
478 "TRANSACTION_isInteractive",
479 )
480}
481
482pub fn resolve_fgproc_codes() -> Option<(u32, u32)> {
488 const JAR: &str = "/system/framework/framework.jar";
489 const AM_STUB: &str = "Landroid/app/IActivityManager$Stub;";
490 const FGPROC_STUB: &str = "Landroid/app/IForegroundProcessObserver$Stub;";
491
492 let register_code = find_transaction_code(
493 JAR,
494 AM_STUB,
495 "TRANSACTION_registerForegroundProcessObserver",
496 )?;
497 let on_change_code =
498 find_transaction_code(JAR, FGPROC_STUB, "TRANSACTION_onForegroundProcessChanged")?;
499 Some((register_code, on_change_code))
500}
501
502pub fn resolve_fgproc_codes_fallback() -> Option<(u32, u32)> {
517 const JAR: &str = "/system/framework/framework.jar";
518 const AM_STUB: &str = "Landroid/app/IActivityManager$Stub;";
519 const OBS_STUB: &str = "Landroid/app/IProcessObserver$Stub;";
520 const OBS_IFACE: &str = "Landroid/app/IProcessObserver;";
521
522 if resolve_fgproc_codes().is_some() {
524 return None;
525 }
526
527 let data = std::fs::read(JAR).ok()?;
528 let entries = zip_find_dex_entries(&data);
529
530 let mut pid_first = false;
533 for entry in &entries {
534 if let Some(dex) = zip_entry_data(&data, entry) {
535 if let Some(params) =
536 method_param_types(dex, OBS_IFACE.as_bytes(), b"onForegroundActivitiesChanged")
537 {
538 pid_first = params.first().map(|t| *t == b"I").unwrap_or(false);
539 break;
540 }
541 }
542 }
543 if !pid_first {
544 return None;
545 }
546
547 let register_code = find_transaction_code(JAR, AM_STUB, "TRANSACTION_registerProcessObserver")?;
548 let on_change_code =
549 find_transaction_code(JAR, OBS_STUB, "TRANSACTION_onForegroundActivitiesChanged")?;
550 Some((register_code, on_change_code))
551}
552
553pub fn resolve_fps_codes() -> Option<(u32, u32, u32)> {
560 const JAR: &str = "/system/framework/framework.jar";
561 const IWM_STUB: &str = "Landroid/view/IWindowManager$Stub;";
562 const FPS_STUB: &str = "Landroid/window/ITaskFpsCallback$Stub;";
563
564 let register_code =
565 find_transaction_code(JAR, IWM_STUB, "TRANSACTION_registerTaskFpsCallback")?;
566 let unregister_code =
567 find_transaction_code(JAR, IWM_STUB, "TRANSACTION_unregisterTaskFpsCallback")?;
568 let on_fps_code = find_transaction_code(JAR, FPS_STUB, "TRANSACTION_onFpsReported")?;
569 Some((register_code, unregister_code, on_fps_code))
570}
571
572pub fn resolve_task_stack_codes() -> Option<(u32, u32)> {
583 const JAR: &str = "/system/framework/framework.jar";
584 const ATM_STUB: &str = "Landroid/app/IActivityTaskManager$Stub;";
585
586 let register_code =
587 find_transaction_code(JAR, ATM_STUB, "TRANSACTION_registerTaskStackListener")?;
588 let unregister_code =
589 find_transaction_code(JAR, ATM_STUB, "TRANSACTION_unregisterTaskStackListener")?;
590 Some((register_code, unregister_code))
591}
592
593#[cfg(test)]
594mod tests {
595 use super::*;
596
597 fn u32put(b: &mut [u8], off: usize, v: u32) {
598 b[off..off + 4].copy_from_slice(&v.to_le_bytes());
599 }
600
601 fn build_dex(class_desc: &[u8], method_name: &[u8], params: &[&[u8]]) -> Vec<u8> {
605 let mut out = vec![0u8; 112];
606 out[0..4].copy_from_slice(DEX_MAGIC);
607 out[4..8].copy_from_slice(b"035\0");
608
609 let mut strings: Vec<Vec<u8>> = vec![class_desc.to_vec(), method_name.to_vec()];
610 for p in params {
611 if !strings.iter().any(|s| s == p) {
612 strings.push(p.to_vec());
613 }
614 }
615 let n_string = strings.len();
616 let mut types: Vec<Vec<u8>> = vec![class_desc.to_vec()];
617 for p in params {
618 if !types.iter().any(|t| t == p) {
619 types.push(p.to_vec());
620 }
621 }
622 let n_type = types.len();
623
624 let off_string_ids = 112;
625 let off_type_ids = off_string_ids + n_string * 4;
626 let off_proto_ids = off_type_ids + n_type * 4;
627 let off_method_ids = off_proto_ids + 12; out.resize(off_method_ids + 8, 0); let mut string_offs = Vec::with_capacity(n_string);
634 for s in &strings {
635 string_offs.push(out.len());
636 let n = s.len() as u8;
637 out.push(n); out.extend_from_slice(s);
639 out.push(0);
640 }
641
642 let params_off = out.len();
644 out.extend_from_slice(&(params.len() as u32).to_le_bytes());
645 for p in params {
646 let ti = types.iter().position(|t| t == p).unwrap();
647 out.extend_from_slice(&(ti as u16).to_le_bytes());
648 }
649
650 u32put(&mut out, 56, n_string as u32);
652 u32put(&mut out, 60, off_string_ids as u32);
653 u32put(&mut out, 64, n_type as u32);
654 u32put(&mut out, 68, off_type_ids as u32);
655 u32put(&mut out, 72, 1); u32put(&mut out, 76, off_proto_ids as u32);
657 u32put(&mut out, 80, 0); u32put(&mut out, 84, 0);
659 u32put(&mut out, 88, 1); u32put(&mut out, 92, off_method_ids as u32);
661 u32put(&mut out, 96, 0); u32put(&mut out, 100, 0);
663
664 for (i, &so) in string_offs.iter().enumerate() {
666 u32put(&mut out, off_string_ids + i * 4, so as u32);
667 }
668 for (i, t) in types.iter().enumerate() {
670 let si = strings.iter().position(|s| s == t).unwrap();
671 u32put(&mut out, off_type_ids + i * 4, si as u32);
672 }
673 u32put(&mut out, off_proto_ids, 0);
675 u32put(&mut out, off_proto_ids + 4, 0);
676 u32put(&mut out, off_proto_ids + 8, params_off as u32);
677 let m = off_method_ids;
679 out[m..m + 2].copy_from_slice(&0u16.to_le_bytes());
680 out[m + 2..m + 4].copy_from_slice(&0u16.to_le_bytes());
681 u32put(&mut out, m + 4, 1);
682
683 out
684 }
685
686 #[test]
687 fn parses_int_first_proto() {
688 let dex = build_dex(
689 b"Landroid/app/IProcessObserver;",
690 b"onForegroundActivitiesChanged",
691 &[b"I", b"I", b"Z"],
692 );
693 let params = method_param_types(
694 &dex,
695 b"Landroid/app/IProcessObserver;",
696 b"onForegroundActivitiesChanged",
697 )
698 .expect("method present");
699 assert_eq!(params, [&b"I"[..], &b"I"[..], &b"Z"[..]]);
700 }
701
702 #[test]
703 fn rejects_string_first_proto() {
704 let dex = build_dex(
706 b"Landroid/app/IProcessObserver;",
707 b"onForegroundActivitiesChanged",
708 &[b"Ljava/lang/String;", b"I", b"Z"],
709 );
710 let params = method_param_types(
711 &dex,
712 b"Landroid/app/IProcessObserver;",
713 b"onForegroundActivitiesChanged",
714 )
715 .expect("method present");
716 assert_eq!(params.first().map(|t| *t == b"I"), Some(false));
717 }
718
719 #[test]
720 fn absent_method_returns_none() {
721 let dex = build_dex(
722 b"Landroid/app/IProcessObserver;",
723 b"onForegroundActivitiesChanged",
724 &[b"I"],
725 );
726 assert!(
727 method_param_types(&dex, b"Landroid/app/IProcessObserver;", b"onProcessDied").is_none()
728 );
729 }
730}