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}
47
48fn zip_find_dex_entries(data: &[u8]) -> Vec<ZipEntry> {
49 let scan_start = data.len().saturating_sub(65558);
51 let eocd_pos = match data[scan_start..]
52 .windows(4)
53 .rposition(|w| w == ZIP_EOCD_SIG)
54 {
55 Some(p) => scan_start + p,
56 None => return Vec::new(),
57 };
58
59 let cd_size = match u32le(data, eocd_pos + 12) { Some(v) => v as usize, None => return Vec::new() };
60 let cd_off = match u32le(data, eocd_pos + 16) { Some(v) => v as usize, None => return Vec::new() };
61
62 let mut pos = cd_off;
63 let cd_end = cd_off.saturating_add(cd_size);
64 let mut entries = Vec::new();
65
66 while pos + 46 <= cd_end && pos + 46 <= data.len() {
67 if data.get(pos..pos + 4) != Some(&ZIP_CD_SIG) { break; }
68
69 let compression = match u16le(data, pos + 10) { Some(v) => v, None => break };
70 let comp_size = match u32le(data, pos + 20) { Some(v) => v as usize, None => break };
71 let local_off = match u32le(data, pos + 42) { Some(v) => v as usize, None => break };
72 let fname_len = match u16le(data, pos + 28) { Some(v) => v as usize, None => break };
73 let extra_len = match u16le(data, pos + 30) { Some(v) => v as usize, None => break };
74 let comment_len = match u16le(data, pos + 32) { Some(v) => v as usize, None => break };
75
76 let fname_end = pos + 46 + fname_len;
77 if fname_end > data.len() { break; }
78 let fname = &data[pos + 46..fname_end];
79
80 let is_dex = fname.starts_with(b"classes") && fname.ends_with(b".dex");
82 if is_dex && compression == ZIP_STORED {
83 entries.push(ZipEntry {
84 local_off,
85 comp_size,
86 });
87 }
88
89 pos = match pos.checked_add(46 + fname_len + extra_len + comment_len) {
90 Some(v) => v,
91 None => break,
92 };
93 }
94
95 entries.sort_by_key(|e| std::cmp::Reverse(e.comp_size));
98 entries
99}
100
101fn zip_entry_data<'a>(data: &'a [u8], entry: &ZipEntry) -> Option<&'a [u8]> {
102 let lh = entry.local_off;
103 if data.get(lh..lh + 4) != Some(&ZIP_LOCAL_SIG) { return None; }
104 let fname_len = u16le(data, lh + 26)? as usize;
105 let extra_len = u16le(data, lh + 28)? as usize;
106 let data_start = lh + 30 + fname_len + extra_len;
107 data.get(data_start..data_start + entry.comp_size)
108}
109
110const DEX_MAGIC: &[u8] = b"dex\n";
113
114fn dex_string(dex: &[u8], string_ids_off: usize, idx: usize) -> Option<&[u8]> {
115 let str_data_off = u32le(dex, string_ids_off + idx * 4)? as usize;
116 let mut off = str_data_off;
118 loop {
119 let b = *dex.get(off)?;
120 off += 1;
121 if b & 0x80 == 0 { break; }
122 }
123 let start = off;
125 while *dex.get(off)? != 0 { off += 1; }
126 dex.get(start..off)
127}
128
129fn skip_encoded_value(dex: &[u8], off: &mut usize) -> Option<()> {
130 let vbyte = *dex.get(*off)?;
131 *off += 1;
132 let vtype = vbyte & 0x1f;
133 let varg = (vbyte >> 5) as usize;
134 match vtype {
135 0x00 | 0x02 | 0x03 | 0x04 | 0x06 |
137 0x10 | 0x11 | 0x15 | 0x16 | 0x17 |
138 0x18 | 0x19 | 0x1a | 0x1b => {
139 *off = off.checked_add(varg + 1)?;
140 }
141 0x1c => { let size = uleb128(dex, off)?;
143 for _ in 0..size { skip_encoded_value(dex, off)?; }
144 }
145 0x1d => { uleb128(dex, off)?; let size = uleb128(dex, off)?;
148 for _ in 0..size {
149 uleb128(dex, off)?; skip_encoded_value(dex, off)?;
151 }
152 }
153 0x1e | 0x1f => {} _ => return None,
155 }
156 Some(())
157}
158
159fn read_int_encoded_value(dex: &[u8], off: &mut usize) -> Option<u32> {
160 let vbyte = *dex.get(*off)?;
161 *off += 1;
162 let vtype = vbyte & 0x1f;
163 let varg = (vbyte >> 5) as usize;
164 if vtype != 0x04 { return None; }
166 let size = varg + 1;
167 if size > 4 { return None; }
168 let mut val = 0u32;
169 for i in 0..size {
170 val |= (*dex.get(*off + i)? as u32) << (i * 8);
171 }
172 *off += size;
173 Some(val)
174}
175
176fn find_in_dex(dex: &[u8], class_desc: &[u8], field_name: &[u8]) -> Option<u32> {
177 if !dex.starts_with(DEX_MAGIC) || dex.len() < 112 { return None; }
178
179 let string_ids_size = u32le(dex, 56)? as usize;
180 let string_ids_off = u32le(dex, 60)? as usize;
181 let type_ids_size = u32le(dex, 64)? as usize;
182 let type_ids_off = u32le(dex, 68)? as usize;
183 let field_ids_size = u32le(dex, 80)? as usize;
184 let field_ids_off = u32le(dex, 84)? as usize;
185 let class_defs_size = u32le(dex, 96)? as usize;
186 let class_defs_off = u32le(dex, 100)? as usize;
187
188 let mut class_str_idx: Option<usize> = None;
190 for i in 0..string_ids_size {
191 if dex_string(dex, string_ids_off, i) == Some(class_desc) {
192 class_str_idx = Some(i);
193 break;
194 }
195 }
196 let class_str_idx = class_str_idx?;
197
198 let mut class_type_idx: Option<usize> = None;
200 for i in 0..type_ids_size {
201 if u32le(dex, type_ids_off + i * 4)? as usize == class_str_idx {
202 class_type_idx = Some(i);
203 break;
204 }
205 }
206 let class_type_idx = class_type_idx?;
207
208 let mut field_str_idx: Option<u32> = None;
210 for i in 0..string_ids_size {
211 if dex_string(dex, string_ids_off, i) == Some(field_name) {
212 field_str_idx = Some(i as u32);
213 break;
214 }
215 }
216 let field_str_idx = field_str_idx?;
217
218 let mut target_field_idx: Option<u32> = None;
220 for i in 0..field_ids_size {
221 let foff = field_ids_off + i * 8;
222 let fclass = u16le(dex, foff)? as usize;
223 let fname = u32le(dex, foff + 4)?;
224 if fclass == class_type_idx && fname == field_str_idx {
225 target_field_idx = Some(i as u32);
226 break;
227 }
228 }
229 let target_field_idx = target_field_idx?;
230
231 let mut class_data_off = None;
233 let mut static_vals_off = None;
234 for i in 0..class_defs_size {
235 let coff = class_defs_off + i * 32;
236 if u32le(dex, coff)? as usize == class_type_idx {
237 class_data_off = Some(u32le(dex, coff + 24)? as usize);
238 static_vals_off = Some(u32le(dex, coff + 28)? as usize);
239 break;
240 }
241 }
242 let class_data_off = class_data_off?;
243 let static_vals_off = static_vals_off?;
244 if class_data_off == 0 || static_vals_off == 0 { return None; }
245
246 let mut off = class_data_off;
248 let static_fields_size = uleb128(dex, &mut off)?;
249 let _instance_fields = uleb128(dex, &mut off)?;
250 let _direct_methods = uleb128(dex, &mut off)?;
251 let _virtual_methods = uleb128(dex, &mut off)?;
252
253 let mut field_pos: Option<usize> = None;
254 let mut cur_field_idx = 0u32;
255 for i in 0..static_fields_size as usize {
256 let diff = uleb128(dex, &mut off)?;
257 let _access_flags = uleb128(dex, &mut off)?;
258 cur_field_idx += diff;
259 if cur_field_idx == target_field_idx {
260 field_pos = Some(i);
261 break;
262 }
263 }
264 let field_pos = field_pos?;
265
266 let mut sv = static_vals_off;
268 let sv_size = uleb128(dex, &mut sv)? as usize;
269 if field_pos >= sv_size { return None; }
270
271 for i in 0..=field_pos {
272 if i == field_pos {
273 return read_int_encoded_value(dex, &mut sv);
274 }
275 skip_encoded_value(dex, &mut sv)?;
276 }
277 None
278}
279
280fn method_param_types<'a>(dex: &'a [u8], class_desc: &[u8], method_name: &[u8]) -> Option<Vec<&'a [u8]>> {
290 if !dex.starts_with(DEX_MAGIC) || dex.len() < 112 { return None; }
291
292 let string_ids_size = u32le(dex, 56)? as usize;
293 let string_ids_off = u32le(dex, 60)? as usize;
294 let type_ids_size = u32le(dex, 64)? as usize;
295 let type_ids_off = u32le(dex, 68)? as usize;
296 let proto_ids_size = u32le(dex, 72)? as usize;
297 let proto_ids_off = u32le(dex, 76)? as usize;
298 let method_ids_size = u32le(dex, 88)? as usize;
299 let method_ids_off = u32le(dex, 92)? as usize;
300
301 let mut class_str_idx: Option<usize> = None;
303 for i in 0..string_ids_size {
304 if dex_string(dex, string_ids_off, i) == Some(class_desc) {
305 class_str_idx = Some(i);
306 break;
307 }
308 }
309 let class_str_idx = class_str_idx?;
310
311 let mut class_type_idx: Option<usize> = None;
313 for i in 0..type_ids_size {
314 if u32le(dex, type_ids_off + i * 4)? as usize == class_str_idx {
315 class_type_idx = Some(i);
316 break;
317 }
318 }
319 let class_type_idx = class_type_idx?;
320
321 let mut method_str_idx: Option<u32> = None;
323 for i in 0..string_ids_size {
324 if dex_string(dex, string_ids_off, i) == Some(method_name) {
325 method_str_idx = Some(i as u32);
326 break;
327 }
328 }
329 let method_str_idx = method_str_idx?;
330
331 let mut proto_idx: Option<usize> = None;
333 for i in 0..method_ids_size {
334 let moff = method_ids_off + i * 8;
335 let fclass = u16le(dex, moff)? as usize;
336 let fproto = u16le(dex, moff + 2)? as usize;
337 let fname = u32le(dex, moff + 4)?;
338 if fclass == class_type_idx && fname == method_str_idx {
339 proto_idx = Some(fproto);
340 break;
341 }
342 }
343 let proto_idx = proto_idx?;
344 if proto_idx >= proto_ids_size { return None; }
345
346 let poff = proto_ids_off + proto_idx * 12;
348 let params_off = u32le(dex, poff + 8)? as usize;
349 if params_off == 0 { return Some(Vec::new()); }
350
351 let param_count = u32le(dex, params_off)? as usize;
353 let mut params = Vec::with_capacity(param_count);
354 for i in 0..param_count {
355 let tidx = u16le(dex, params_off + 4 + i * 2)? as usize;
356 let desc_str_idx = u32le(dex, type_ids_off + tidx * 4)? as usize;
357 let desc = dex_string(dex, string_ids_off, desc_str_idx)?;
358 params.push(desc);
359 }
360 Some(params)
361}
362
363pub fn find_transaction_code(jar_path: &str, class_desc: &str, field_name: &str) -> Option<u32> {
373 let data = std::fs::read(jar_path).ok()?;
374 let entries = zip_find_dex_entries(&data);
375 for entry in &entries {
376 if let Some(dex) = zip_entry_data(&data, entry) {
377 if let Some(code) = find_in_dex(dex, class_desc.as_bytes(), field_name.as_bytes()) {
378 return Some(code);
379 }
380 }
381 }
382 None
383}
384
385pub fn resolve_tx_codes_from_dex() -> Option<(u32, u32, u8, u32)> {
393 const JAR: &str = "/system/framework/framework.jar";
394 const AM_STUB: &str = "Landroid/app/IActivityManager$Stub;";
395 const OBS_STUB: &str = "Landroid/app/IProcessObserver$Stub;";
396
397 let observer_code = find_transaction_code(JAR, AM_STUB, "TRANSACTION_registerProcessObserver")?;
398 let fg_code = find_transaction_code(JAR, OBS_STUB, "TRANSACTION_onForegroundActivitiesChanged")?;
399
400 if let Some(query_code) = find_transaction_code(JAR, AM_STUB, "TRANSACTION_getFocusedRootTaskInfo") {
401 return Some((observer_code, query_code, 1, fg_code));
402 }
403 let query_code = find_transaction_code(JAR, AM_STUB, "TRANSACTION_getFocusedStackInfo")?;
405 Some((observer_code, query_code, 2, fg_code))
406}
407
408pub fn resolve_is_interactive_tx() -> Option<u32> {
410 const JAR: &str = "/system/framework/framework.jar";
411 find_transaction_code(JAR, "Landroid/os/IPowerManager$Stub;", "TRANSACTION_isInteractive")
412}
413
414pub fn resolve_fgproc_codes() -> Option<(u32, u32)> {
420 const JAR: &str = "/system/framework/framework.jar";
421 const AM_STUB: &str = "Landroid/app/IActivityManager$Stub;";
422 const FGPROC_STUB: &str = "Landroid/app/IForegroundProcessObserver$Stub;";
423
424 let register_code =
425 find_transaction_code(JAR, AM_STUB, "TRANSACTION_registerForegroundProcessObserver")?;
426 let on_change_code =
427 find_transaction_code(JAR, FGPROC_STUB, "TRANSACTION_onForegroundProcessChanged")?;
428 Some((register_code, on_change_code))
429}
430
431pub fn resolve_fgproc_codes_fallback() -> Option<(u32, u32)> {
446 const JAR: &str = "/system/framework/framework.jar";
447 const AM_STUB: &str = "Landroid/app/IActivityManager$Stub;";
448 const OBS_STUB: &str = "Landroid/app/IProcessObserver$Stub;";
449 const OBS_IFACE: &str = "Landroid/app/IProcessObserver;";
450
451 if resolve_fgproc_codes().is_some() {
453 return None;
454 }
455
456 let data = std::fs::read(JAR).ok()?;
457 let entries = zip_find_dex_entries(&data);
458
459 let mut pid_first = false;
462 for entry in &entries {
463 if let Some(dex) = zip_entry_data(&data, entry) {
464 if let Some(params) = method_param_types(dex, OBS_IFACE.as_bytes(), b"onForegroundActivitiesChanged") {
465 pid_first = params.first().map(|t| *t == b"I").unwrap_or(false);
466 break;
467 }
468 }
469 }
470 if !pid_first { return None; }
471
472 let register_code = find_transaction_code(JAR, AM_STUB, "TRANSACTION_registerProcessObserver")?;
473 let on_change_code =
474 find_transaction_code(JAR, OBS_STUB, "TRANSACTION_onForegroundActivitiesChanged")?;
475 Some((register_code, on_change_code))
476}
477
478pub fn resolve_fps_codes() -> Option<(u32, u32, u32)> {
485 const JAR: &str = "/system/framework/framework.jar";
486 const IWM_STUB: &str = "Landroid/view/IWindowManager$Stub;";
487 const FPS_STUB: &str = "Landroid/window/ITaskFpsCallback$Stub;";
488
489 let register_code = find_transaction_code(JAR, IWM_STUB, "TRANSACTION_registerTaskFpsCallback")?;
490 let unregister_code =
491 find_transaction_code(JAR, IWM_STUB, "TRANSACTION_unregisterTaskFpsCallback")?;
492 let on_fps_code = find_transaction_code(JAR, FPS_STUB, "TRANSACTION_onFpsReported")?;
493 Some((register_code, unregister_code, on_fps_code))
494}
495
496pub fn resolve_task_stack_codes() -> Option<(u32, u32)> {
507 const JAR: &str = "/system/framework/framework.jar";
508 const ATM_STUB: &str = "Landroid/app/IActivityTaskManager$Stub;";
509
510 let register_code =
511 find_transaction_code(JAR, ATM_STUB, "TRANSACTION_registerTaskStackListener")?;
512 let unregister_code =
513 find_transaction_code(JAR, ATM_STUB, "TRANSACTION_unregisterTaskStackListener")?;
514 Some((register_code, unregister_code))
515}
516
517#[cfg(test)]
518mod tests {
519 use super::*;
520
521 fn u32put(b: &mut [u8], off: usize, v: u32) {
522 b[off..off + 4].copy_from_slice(&v.to_le_bytes());
523 }
524
525 fn build_dex(class_desc: &[u8], method_name: &[u8], params: &[&[u8]]) -> Vec<u8> {
529 let mut out = vec![0u8; 112];
530 out[0..4].copy_from_slice(DEX_MAGIC);
531 out[4..8].copy_from_slice(b"035\0");
532
533 let mut strings: Vec<Vec<u8>> = vec![class_desc.to_vec(), method_name.to_vec()];
534 for p in params {
535 if !strings.iter().any(|s| s == p) {
536 strings.push(p.to_vec());
537 }
538 }
539 let n_string = strings.len();
540 let mut types: Vec<Vec<u8>> = vec![class_desc.to_vec()];
541 for p in params {
542 if !types.iter().any(|t| t == p) {
543 types.push(p.to_vec());
544 }
545 }
546 let n_type = types.len();
547
548 let off_string_ids = 112;
549 let off_type_ids = off_string_ids + n_string * 4;
550 let off_proto_ids = off_type_ids + n_type * 4;
551 let off_method_ids = off_proto_ids + 12; out.resize(off_method_ids + 8, 0); let mut string_offs = Vec::with_capacity(n_string);
558 for s in &strings {
559 string_offs.push(out.len());
560 let n = s.len() as u8;
561 out.push(n); out.extend_from_slice(s);
563 out.push(0);
564 }
565
566 let params_off = out.len();
568 out.extend_from_slice(&(params.len() as u32).to_le_bytes());
569 for p in params {
570 let ti = types.iter().position(|t| t == p).unwrap();
571 out.extend_from_slice(&(ti as u16).to_le_bytes());
572 }
573
574 u32put(&mut out, 56, n_string as u32);
576 u32put(&mut out, 60, off_string_ids as u32);
577 u32put(&mut out, 64, n_type as u32);
578 u32put(&mut out, 68, off_type_ids as u32);
579 u32put(&mut out, 72, 1); u32put(&mut out, 76, off_proto_ids as u32);
581 u32put(&mut out, 80, 0); u32put(&mut out, 84, 0);
583 u32put(&mut out, 88, 1); u32put(&mut out, 92, off_method_ids as u32);
585 u32put(&mut out, 96, 0); u32put(&mut out, 100, 0);
587
588 for (i, &so) in string_offs.iter().enumerate() {
590 u32put(&mut out, off_string_ids + i * 4, so as u32);
591 }
592 for (i, t) in types.iter().enumerate() {
594 let si = strings.iter().position(|s| s == t).unwrap();
595 u32put(&mut out, off_type_ids + i * 4, si as u32);
596 }
597 u32put(&mut out, off_proto_ids, 0);
599 u32put(&mut out, off_proto_ids + 4, 0);
600 u32put(&mut out, off_proto_ids + 8, params_off as u32);
601 let m = off_method_ids;
603 out[m..m + 2].copy_from_slice(&0u16.to_le_bytes());
604 out[m + 2..m + 4].copy_from_slice(&0u16.to_le_bytes());
605 u32put(&mut out, m + 4, 1);
606
607 out
608 }
609
610 #[test]
611 fn parses_int_first_proto() {
612 let dex = build_dex(b"Landroid/app/IProcessObserver;", b"onForegroundActivitiesChanged", &[b"I", b"I", b"Z"]);
613 let params = method_param_types(&dex, b"Landroid/app/IProcessObserver;", b"onForegroundActivitiesChanged")
614 .expect("method present");
615 assert_eq!(params, [&b"I"[..], &b"I"[..], &b"Z"[..]]);
616 }
617
618 #[test]
619 fn rejects_string_first_proto() {
620 let dex = build_dex(b"Landroid/app/IProcessObserver;", b"onForegroundActivitiesChanged", &[b"Ljava/lang/String;", b"I", b"Z"]);
622 let params = method_param_types(&dex, b"Landroid/app/IProcessObserver;", b"onForegroundActivitiesChanged")
623 .expect("method present");
624 assert_eq!(params.first().map(|t| *t == b"I"), Some(false));
625 }
626
627 #[test]
628 fn absent_method_returns_none() {
629 let dex = build_dex(b"Landroid/app/IProcessObserver;", b"onForegroundActivitiesChanged", &[b"I"]);
630 assert!(method_param_types(&dex, b"Landroid/app/IProcessObserver;", b"onProcessDied").is_none());
631 }
632}