droidasc 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. droidasc/__init__.py +3 -0
  2. droidasc/__main__.py +3 -0
  3. droidasc/asc_client/__init__.py +0 -0
  4. droidasc/asc_client/apk_handler.py +463 -0
  5. droidasc/asc_client/asc_handler.py +129 -0
  6. droidasc/asc_client/dex_container.py +149 -0
  7. droidasc/asc_client/gui/__init__.py +1 -0
  8. droidasc/asc_client/gui/app.py +1578 -0
  9. droidasc/asc_client/gui/runtime.py +547 -0
  10. droidasc/asc_client/gui/search_worker.py +36 -0
  11. droidasc/asc_client/gui/settings.py +39 -0
  12. droidasc/asc_client/gui/source_edit.py +301 -0
  13. droidasc/asc_client/gui/text_utils.py +81 -0
  14. droidasc/asc_client/gui/theme.py +194 -0
  15. droidasc/asc_client/gui/widgets.py +527 -0
  16. droidasc/asc_client/manifest_handler.py +29 -0
  17. droidasc/asc_core/__init__.py +1 -0
  18. droidasc/asc_core/core/__init__.py +0 -0
  19. droidasc/asc_core/core/dex/dex_builder.py +483 -0
  20. droidasc/asc_core/core/dex/dex_constructor.py +325 -0
  21. droidasc/asc_core/core/dex/dex_manager.py +104 -0
  22. droidasc/asc_core/core/dex/dex_remapper.py +396 -0
  23. droidasc/asc_core/core/dvm_handlers.py +110 -0
  24. droidasc/asc_core/core/dvm_interpreter.py +55 -0
  25. droidasc/asc_core/findrefs/__init__.py +3 -0
  26. droidasc/asc_core/findrefs/findrefs_manager.py +110 -0
  27. droidasc/asc_core/findrefs/locator/__init__.py +1 -0
  28. droidasc/asc_core/findrefs/locator/base_locator.py +27 -0
  29. droidasc/asc_core/findrefs/locator/field_locator.py +147 -0
  30. droidasc/asc_core/findrefs/locator/insn_locator.py +204 -0
  31. droidasc/asc_core/findrefs/locator/method_locator.py +146 -0
  32. droidasc/asc_core/findrefs/locator/string_locator.py +76 -0
  33. droidasc/asc_core/findrefs/locator/type_locator.py +48 -0
  34. droidasc/asc_core/findrefs/scan/code_item_scan.py +271 -0
  35. droidasc/asc_core/models/__init__.py +0 -0
  36. droidasc/asc_core/models/dvm_opcode.py +298 -0
  37. droidasc/asc_core/resource/dvmopcode_template.py +74 -0
  38. droidasc/asc_core/resource/gen_dvmopcode.py +27 -0
  39. droidasc/asc_core/utils/__init__.py +0 -0
  40. droidasc/asc_core/utils/decompiler.py +285 -0
  41. droidasc/asc_core/utils/decompiler_simple.py +134 -0
  42. droidasc/asc_core/utils/dex_parser.py +294 -0
  43. droidasc/asc_core/utils/dvm_regdec.py +151 -0
  44. droidasc/asc_core/utils/leb128.py +80 -0
  45. droidasc/asc_core/utils/tinydex.py +536 -0
  46. droidasc/cli.py +325 -0
  47. droidasc-0.1.0.dist-info/METADATA +51 -0
  48. droidasc-0.1.0.dist-info/RECORD +52 -0
  49. droidasc-0.1.0.dist-info/WHEEL +5 -0
  50. droidasc-0.1.0.dist-info/entry_points.txt +2 -0
  51. droidasc-0.1.0.dist-info/licenses/LICENSE +202 -0
  52. droidasc-0.1.0.dist-info/top_level.txt +1 -0
droidasc/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ from droidasc.cli import main
2
+
3
+ __all__ = ["main"]
droidasc/__main__.py ADDED
@@ -0,0 +1,3 @@
1
+ from droidasc.cli import main
2
+
3
+ main()
File without changes
@@ -0,0 +1,463 @@
1
+ import atexit
2
+ import mmap
3
+ import os
4
+ import struct
5
+ import sys
6
+ import threading
7
+ import time
8
+ import types
9
+ import zlib
10
+ from concurrent.futures import ThreadPoolExecutor, FIRST_COMPLETED, wait
11
+
12
+ from droidasc.asc_client.dex_container import iter_logical_dex_buffers
13
+
14
+
15
+ _U16 = struct.Struct("<H")
16
+ _U32 = struct.Struct("<I")
17
+ _U16_FROM = _U16.unpack_from
18
+ _U32_FROM = _U32.unpack_from
19
+ _CD_SIG = b"PK\x01\x02"
20
+ _LH_SIG = b"PK\x03\x04"
21
+ _EOCD_SIG = b"PK\x05\x06"
22
+ _DEX_SUFFIX = b".dex"
23
+ _SLASH = ord("/")
24
+ _DEFLATE_CHUNK = 1 << 19
25
+
26
+ _WORKER_APK_PATH = None
27
+ _WORKER_APK_FP = None
28
+ _WORKER_APK_MM = None
29
+
30
+
31
+ def _process_pool_context():
32
+ """Prefer fork so workers do not have to re-import the whole module graph.
33
+
34
+ spawn re-executes the parent's __main__ and re-imports every module a worker
35
+ touches; measured on the 352MB sample that is 85ms of pool cost versus 19ms for
36
+ fork. fork is only used when it is safe: never on Windows, and never from a
37
+ multi-threaded parent (fork() there can deadlock on inherited locks).
38
+ """
39
+ if os.name == "nt":
40
+ return None
41
+ try:
42
+ import multiprocessing
43
+ # decompiler.py stubs optional imports for startup; a stub is not a real
44
+ # module and would be accepted silently by __getattr__, so verify before
45
+ # handing it to ProcessPoolExecutor (a bogus context hangs instead of raising)
46
+ if not isinstance(multiprocessing, types.ModuleType) or not hasattr(multiprocessing, "get_context"):
47
+ return None
48
+ if threading.active_count() != 1:
49
+ return None
50
+ return multiprocessing.get_context("fork")
51
+ except (ImportError, ValueError, OSError):
52
+ return None
53
+
54
+
55
+ def _skip_uleb128(buf, off : int) -> int:
56
+ while buf[off] & 0x80:
57
+ off += 1
58
+ return off + 1
59
+
60
+
61
+ def _read_string_data_bytes(buf, str_off : int) -> bytes:
62
+ ptr = _skip_uleb128(buf, str_off)
63
+ end = buf.find(b"\x00", ptr)
64
+ if end < 0:
65
+ raise ValueError("unterminated string_data_item")
66
+ return buf[ptr:end]
67
+
68
+
69
+ def _find_type_idx(buf, target_bytes : bytes) -> int:
70
+ if len(buf) < 0x70 or buf[:3] != b"dex":
71
+ return -1
72
+
73
+ string_ids_size = _U32_FROM(buf, 0x38)[0]
74
+ string_ids_off = _U32_FROM(buf, 0x3C)[0]
75
+ type_ids_size = _U32_FROM(buf, 0x40)[0]
76
+ type_ids_off = _U32_FROM(buf, 0x44)[0]
77
+
78
+ if string_ids_size == 0 or type_ids_size == 0:
79
+ return -1
80
+ if string_ids_off + string_ids_size * 4 > len(buf):
81
+ raise ValueError("bad string_ids range")
82
+ if type_ids_off + type_ids_size * 4 > len(buf):
83
+ raise ValueError("bad type_ids range")
84
+
85
+ left = 0
86
+ right = type_ids_size - 1
87
+ while left <= right:
88
+ mid = (left + right) >> 1
89
+ str_idx = _U32_FROM(buf, type_ids_off + (mid << 2))[0]
90
+ if str_idx >= string_ids_size:
91
+ raise ValueError("bad type_id->string_idx")
92
+ str_off = _U32_FROM(buf, string_ids_off + (str_idx << 2))[0]
93
+ if str_off >= len(buf):
94
+ raise ValueError("bad string_data_off")
95
+
96
+ cls_bytes = _read_string_data_bytes(buf, str_off)
97
+ if cls_bytes == target_bytes:
98
+ return mid
99
+ if cls_bytes < target_bytes:
100
+ left = mid + 1
101
+ else:
102
+ right = mid - 1
103
+
104
+ return -1
105
+
106
+
107
+ def _class_defs_contains_type_idx(buf, type_idx : int) -> bool:
108
+ class_defs_size = _U32_FROM(buf, 0x60)[0]
109
+ class_defs_off = _U32_FROM(buf, 0x64)[0]
110
+
111
+ if class_defs_size == 0:
112
+ return False
113
+
114
+ class_defs_end = class_defs_off + (class_defs_size << 5)
115
+ if class_defs_end > len(buf):
116
+ raise ValueError("bad class_defs range")
117
+
118
+ needle = type_idx.to_bytes(4, "little")
119
+ pos = buf.find(needle, class_defs_off, class_defs_end)
120
+ while pos != -1:
121
+ if ((pos - class_defs_off) & 0x1F) == 0:
122
+ return True
123
+ pos = buf.find(needle, pos + 1, class_defs_end)
124
+ return False
125
+
126
+
127
+ def _dex_defines_class(buf, target_bytes : bytes) -> bool:
128
+ type_idx = _find_type_idx(buf, target_bytes)
129
+ if type_idx < 0:
130
+ return False
131
+ return _class_defs_contains_type_idx(buf, type_idx)
132
+
133
+
134
+ def _find_eocd(mm : mmap.mmap) -> int:
135
+ search_start = max(0, len(mm) - 65536 - 22)
136
+ return mm.rfind(_EOCD_SIG, search_start)
137
+
138
+
139
+ def _parse_cd_dex_entries(mm : mmap.mmap):
140
+ eocd_idx = _find_eocd(mm)
141
+ if eocd_idx < 0:
142
+ raise ValueError("EOCD not found")
143
+
144
+ cd_size = _U32_FROM(mm, eocd_idx + 12)[0]
145
+ cd_off = _U32_FROM(mm, eocd_idx + 16)[0]
146
+ cd_end = cd_off + cd_size
147
+ entries = []
148
+ seen_names = set()
149
+ pos = cd_off
150
+
151
+ while True:
152
+ pos = mm.find(b"classes", pos, cd_end)
153
+ if pos < 0:
154
+ break
155
+ header_off = pos - 46
156
+ pos += 7
157
+ if header_off < cd_off or mm[header_off:header_off + 4] != _CD_SIG:
158
+ continue
159
+
160
+ name_len = _U16_FROM(mm, header_off + 28)[0]
161
+ name_start = pos - 7
162
+ name_end = name_start + name_len
163
+ if name_end > cd_end:
164
+ continue
165
+
166
+ name_bytes = mm[name_start:name_end]
167
+ if (
168
+ not name_bytes.endswith(_DEX_SUFFIX)
169
+ or _SLASH in name_bytes
170
+ or name_bytes in seen_names
171
+ ):
172
+ continue
173
+
174
+ seen_names.add(name_bytes)
175
+ entries.append((
176
+ name_bytes.decode("utf-8", errors="ignore"),
177
+ _U32_FROM(mm, header_off + 24)[0],
178
+ _U32_FROM(mm, header_off + 20)[0],
179
+ _U32_FROM(mm, header_off + 42)[0],
180
+ _U16_FROM(mm, header_off + 10)[0],
181
+ ))
182
+
183
+ if entries:
184
+ return entries
185
+
186
+ ptr = cd_off
187
+ while ptr + 46 <= cd_end:
188
+ if mm[ptr:ptr + 4] != _CD_SIG:
189
+ break
190
+
191
+ name_len = _U16_FROM(mm, ptr + 28)[0]
192
+ extra_len = _U16_FROM(mm, ptr + 30)[0]
193
+ comment_len = _U16_FROM(mm, ptr + 32)[0]
194
+ name_start = ptr + 46
195
+ name_end = name_start + name_len
196
+ name_bytes = mm[name_start:name_end]
197
+ if name_bytes.endswith(_DEX_SUFFIX) and _SLASH not in name_bytes:
198
+ entries.append((
199
+ name_bytes.decode("utf-8", errors="ignore"),
200
+ _U32_FROM(mm, ptr + 24)[0],
201
+ _U32_FROM(mm, ptr + 20)[0],
202
+ _U32_FROM(mm, ptr + 42)[0],
203
+ _U16_FROM(mm, ptr + 10)[0],
204
+ ))
205
+
206
+ ptr = name_end + extra_len + comment_len
207
+
208
+ return entries
209
+
210
+
211
+ def _close_worker_apk():
212
+ global _WORKER_APK_PATH, _WORKER_APK_FP, _WORKER_APK_MM
213
+
214
+ mm = _WORKER_APK_MM
215
+ fp = _WORKER_APK_FP
216
+ _WORKER_APK_MM = None
217
+ _WORKER_APK_FP = None
218
+ _WORKER_APK_PATH = None
219
+
220
+ if mm is not None:
221
+ mm.close()
222
+ if fp is not None:
223
+ fp.close()
224
+
225
+
226
+ atexit.register(_close_worker_apk)
227
+
228
+
229
+ def _get_worker_apk_mm(apk_path : str):
230
+ global _WORKER_APK_PATH, _WORKER_APK_FP, _WORKER_APK_MM
231
+
232
+ mm = _WORKER_APK_MM
233
+ if mm is not None and _WORKER_APK_PATH == apk_path:
234
+ return mm
235
+
236
+ _close_worker_apk()
237
+ fp = open(apk_path, "rb")
238
+ mm = mmap.mmap(fp.fileno(), 0, access=mmap.ACCESS_READ)
239
+ _WORKER_APK_PATH = apk_path
240
+ _WORKER_APK_FP = fp
241
+ _WORKER_APK_MM = mm
242
+ return mm
243
+
244
+
245
+ def _inflate_deflate_chunks(comp_view, stop_event):
246
+ if stop_event is None:
247
+ # nothing to cancel: hand zlib the whole stream in one call instead of
248
+ # re-entering it every _DEFLATE_CHUNK bytes (measured ~4% faster per dex)
249
+ return zlib.decompress(comp_view, -15)
250
+ decomp = zlib.decompressobj(-15)
251
+ out = bytearray()
252
+ pos = 0
253
+ total = len(comp_view)
254
+ while pos < total:
255
+ if stop_event.is_set():
256
+ return None
257
+ end = min(pos + _DEFLATE_CHUNK, total)
258
+ out.extend(decomp.decompress(comp_view[pos:end]))
259
+ pos = end
260
+ out.extend(decomp.flush())
261
+ if stop_event.is_set():
262
+ return None
263
+ return bytes(out)
264
+
265
+
266
+ def _inflate_dex(mm : mmap.mmap, entry, stop_event = None):
267
+ name, uncomp_size, comp_size, local_header_off, comp_method = entry
268
+ if mm[local_header_off:local_header_off + 4] != _LH_SIG:
269
+ raise ValueError("bad local header signature")
270
+
271
+ name_len = _U16_FROM(mm, local_header_off + 26)[0]
272
+ extra_len = _U16_FROM(mm, local_header_off + 28)[0]
273
+ data_off = local_header_off + 30 + name_len + extra_len
274
+ comp_view = memoryview(mm)[data_off:data_off + comp_size]
275
+
276
+ if comp_method == 0:
277
+ data = bytes(comp_view)
278
+ elif comp_method == 8:
279
+ data = _inflate_deflate_chunks(comp_view, stop_event)
280
+ if data is None:
281
+ return None
282
+ else:
283
+ raise ValueError(f"unsupported compression method: {comp_method}")
284
+
285
+ if uncomp_size and len(data) != uncomp_size:
286
+ raise ValueError(f"size mismatch: expect {uncomp_size}, got {len(data)}")
287
+ return data
288
+
289
+
290
+ def _findrefs_worker(apk_path : str, entry, find_type : str, find : dict, aggregate : bool = True):
291
+ from droidasc.asc_client.asc_handler import AscHandler
292
+
293
+ mm = _get_worker_apk_mm(apk_path)
294
+ t0 = time.perf_counter()
295
+ data = _inflate_dex(mm, entry)
296
+ t1 = time.perf_counter()
297
+ lines = []
298
+ handler = AscHandler(False)
299
+ for dex_name, dex_buf in iter_logical_dex_buffers(entry[0], data):
300
+ lines.extend(handler.findrefs(dex_name, dex_buf, find_type, find, aggregate=aggregate))
301
+ t2 = time.perf_counter()
302
+ return (
303
+ entry[0],
304
+ lines,
305
+ (t1 - t0) * 1000000,
306
+ (t2 - t1) * 1000000,
307
+ os.getpid(),
308
+ )
309
+
310
+
311
+ def _inflate_and_hit(mm : mmap.mmap, entry, target_bytes : bytes, stop_event : threading.Event, log):
312
+ tid = threading.get_ident() & 0xFFFF
313
+ name = entry[0]
314
+ if stop_event.is_set():
315
+ return False, None, None
316
+
317
+ t0 = time.perf_counter()
318
+ data = _inflate_dex(mm, entry, stop_event)
319
+ if data is None:
320
+ return False, None, None
321
+ t1 = time.perf_counter()
322
+ if stop_event.is_set():
323
+ return False, None, None
324
+
325
+ hit = False
326
+ hit_name = None
327
+ hit_data = None
328
+ for dex_name, dex_buf in iter_logical_dex_buffers(name, data):
329
+ if stop_event.is_set():
330
+ return False, None, None
331
+ if _dex_defines_class(dex_buf, target_bytes):
332
+ hit = True
333
+ hit_name = dex_name
334
+ hit_data = dex_buf
335
+ break
336
+ t2 = time.perf_counter()
337
+ if hit:
338
+ stop_event.set()
339
+ log(
340
+ f"[APK] [T{tid:04x}] '{name}' inflate={(t1 - t0) * 1000000:.2f} us "
341
+ f"lookup={(t2 - t1) * 1000000:.2f} us hit={hit}"
342
+ )
343
+ return hit, hit_name, hit_data
344
+
345
+
346
+ class ApkHandler:
347
+ def __init__(self, apk_path : str, debug : bool = False, max_workers : int = 8):
348
+ self.apk_path = apk_path
349
+ self.debug = debug
350
+ self.max_workers = max_workers
351
+
352
+ def _log(self, msg : str):
353
+ if self.debug:
354
+ print(msg)
355
+
356
+ def _open_apk(self):
357
+ fp = open(self.apk_path, "rb")
358
+ mm = mmap.mmap(fp.fileno(), 0, access=mmap.ACCESS_READ)
359
+ return fp, mm
360
+
361
+ def get_class_dex(self, dalvik_class : str):
362
+ target_bytes = dalvik_class.encode("utf-8")
363
+ t_start = time.perf_counter()
364
+ fp, mm = self._open_apk()
365
+ try:
366
+ entries = _parse_cd_dex_entries(mm)
367
+ entries.sort(key=lambda x: x[2])
368
+ if self.debug:
369
+ t_ready = time.perf_counter()
370
+ self._log(f"[APK] scan setup={(t_ready - t_start) * 1000000:.2f} us entries={len(entries)}")
371
+ if not entries:
372
+ return None
373
+
374
+ stop_event = threading.Event()
375
+ hit_name = None
376
+ hit_data = None
377
+ cap = min(len(entries), max(6, min(self.max_workers, 12)))
378
+
379
+ with ThreadPoolExecutor(max_workers=self.max_workers) as ex:
380
+ inflight = {}
381
+ idx = 0
382
+ while idx < len(entries) or inflight:
383
+ while idx < len(entries) and len(inflight) < cap and not stop_event.is_set():
384
+ entry = entries[idx]
385
+ idx += 1
386
+ fut = ex.submit(_inflate_and_hit, mm, entry, target_bytes, stop_event, self._log)
387
+ inflight[fut] = entry
388
+
389
+ if not inflight:
390
+ break
391
+
392
+ done, _pending = wait(list(inflight.keys()), return_when=FIRST_COMPLETED)
393
+ for fut in done:
394
+ entry = inflight.pop(fut)
395
+ ok, dex_name, data = fut.result()
396
+ if ok:
397
+ hit_name = dex_name
398
+ hit_data = data
399
+ stop_event.set()
400
+ break
401
+
402
+ if hit_name is not None:
403
+ for fut in inflight:
404
+ fut.cancel()
405
+ break
406
+
407
+ if self.debug:
408
+ t_end = time.perf_counter()
409
+ self._log(f"[APK] getclass total={(t_end - t_start) * 1000000:.2f} us")
410
+ if hit_name is None:
411
+ return None
412
+ return hit_name, hit_data
413
+ finally:
414
+ mm.close()
415
+ fp.close()
416
+
417
+ def for_each_findrefs(self, find_type : str, find : dict):
418
+ # imported before the timer so this once-per-process import is not charged to a
419
+ # single search; it used to happen at apk_handler import time, and keeping the
420
+ # getclass path (thread pool only) from paying for it is worth ~8ms of startup
421
+ from concurrent.futures import ProcessPoolExecutor
422
+
423
+ t_start = time.perf_counter()
424
+ fp, mm = self._open_apk()
425
+ try:
426
+ entries = _parse_cd_dex_entries(mm)
427
+ entries.sort(key=lambda x: x[2])
428
+ finally:
429
+ mm.close()
430
+ fp.close()
431
+
432
+ if not entries:
433
+ return
434
+
435
+ # a forked child inherits whatever is still sitting in the parent's stdio
436
+ # buffers; flush first so it cannot be emitted a second time on exit
437
+ sys.stdout.flush()
438
+ sys.stderr.flush()
439
+ with ProcessPoolExecutor(max_workers=self.max_workers,
440
+ mp_context=_process_pool_context()) as ex:
441
+ futures = {}
442
+ for entry in entries:
443
+ fut = ex.submit(_findrefs_worker, self.apk_path, entry, find_type, find)
444
+ futures[fut] = entry[0]
445
+
446
+ while futures:
447
+ done, _pending = wait(list(futures.keys()), return_when=FIRST_COMPLETED)
448
+ for fut in done:
449
+ futures.pop(fut)
450
+ dex_name, lines, inflate_us, process_us, pid = fut.result()
451
+ if self.debug:
452
+ self._log(
453
+ f"[APK] [P{pid}] '{dex_name}' inflate={inflate_us:.2f} us "
454
+ f"process={process_us:.2f} us"
455
+ )
456
+ yield dex_name, lines
457
+
458
+ if self.debug:
459
+ t_end = time.perf_counter()
460
+ self._log(
461
+ f"[APK] for_each_findrefs total={(t_end - t_start) * 1000000:.2f} us "
462
+ f"count={len(entries)} workers={self.max_workers}"
463
+ )
@@ -0,0 +1,129 @@
1
+ import copy
2
+ import importlib.util
3
+ import os
4
+ import sys
5
+ import types
6
+ from collections import defaultdict
7
+
8
+
9
+ _DexManager = None
10
+ _FindRefManager = None
11
+ _decompile_dex_bytes = None
12
+ _DEX = None
13
+
14
+
15
+ def _install_pure_python_mutf8_shim():
16
+ if "mutf8.cmutf8" in sys.modules:
17
+ return
18
+
19
+ pkg_spec = importlib.util.find_spec("mutf8")
20
+ if pkg_spec is None or not pkg_spec.submodule_search_locations:
21
+ return
22
+
23
+ pkg_dir = pkg_spec.submodule_search_locations[0]
24
+ py_impl = os.path.join(pkg_dir, "mutf8.py")
25
+ mod_spec = importlib.util.spec_from_file_location("_asc_client_mutf8_py", py_impl)
26
+ if mod_spec is None or mod_spec.loader is None:
27
+ return
28
+
29
+ module = importlib.util.module_from_spec(mod_spec)
30
+ mod_spec.loader.exec_module(module)
31
+
32
+ shim = types.ModuleType("mutf8.cmutf8")
33
+ shim.decode_modified_utf8 = module.decode_modified_utf8
34
+ shim.encode_modified_utf8 = module.encode_modified_utf8
35
+ sys.modules["mutf8.cmutf8"] = shim
36
+
37
+
38
+ def _lazy_import():
39
+ global _DexManager, _FindRefManager, _decompile_dex_bytes, _DEX
40
+ if _DexManager is not None:
41
+ return
42
+
43
+ _install_pure_python_mutf8_shim()
44
+ from droidasc.asc_core.core.dex.dex_manager import DexManager
45
+ from droidasc.asc_core.findrefs.findrefs_manager import FindRefManager
46
+ from droidasc.asc_core.utils.tinydex import DEX
47
+ from droidasc.asc_core.utils.decompiler import decompile_dex_bytes
48
+
49
+ _DexManager = DexManager
50
+ _FindRefManager = FindRefManager
51
+ _decompile_dex_bytes = decompile_dex_bytes
52
+ _DEX = DEX
53
+
54
+
55
+ class AscHandler:
56
+ def __init__(self, debug : bool = False):
57
+ self.debug = debug
58
+
59
+ def getclass(self, dex_buf : bytes, dalvik_class : str) -> str:
60
+ _lazy_import()
61
+ manager = _DexManager(memoryview(dex_buf), debug=self.debug)
62
+ new_dex_bytes = manager.extract_and_rebuild(dalvik_class)
63
+ return _decompile_dex_bytes(new_dex_bytes, dalvik_class)
64
+
65
+ def _format_method(self, dex, midx : int) -> str:
66
+ method = dex.methods[midx]
67
+ return f"{method.cls.fullname}->{method.name}"
68
+
69
+ def _format_matched_name(self, dex, find_type : str, idx : int) -> str:
70
+ if find_type == "string":
71
+ return str(dex.strings[idx])
72
+ if find_type == "type":
73
+ return dex.types[idx].descriptor
74
+ if find_type == "method":
75
+ method = dex.methods[idx]
76
+ return f"{method.cls.fullname}->{method.name}"
77
+ field = dex.fields[idx]
78
+ return f"{field.cls.fullname}->{field.name}"
79
+
80
+ def findrefs(self, dex_name : str, dex_buf : bytes, find_type : str, find : dict, aggregate : bool = True) -> list:
81
+ from droidasc.asc_core.findrefs.findrefs_manager import FindRefManager
82
+ from droidasc.asc_core.utils.tinydex import DEX
83
+
84
+ dex = DEX.parse(memoryview(dex_buf), dex_name)
85
+ ref_manager = FindRefManager(dex)
86
+ query = copy.deepcopy(find)
87
+ matched_idxs = ref_manager.find_ref(query, True)
88
+ mids = query[find_type]
89
+ if not aggregate:
90
+ ret = []
91
+ for i in range(len(mids)):
92
+ midx = mids[i]
93
+ if midx is None:
94
+ continue
95
+ idx = matched_idxs[i]
96
+ if isinstance(midx, list):
97
+ midxs = midx
98
+ else:
99
+ midxs = [midx]
100
+ matched = self._format_matched_name(dex, find_type, idx)
101
+ for mid in midxs:
102
+ ret.append(
103
+ f"{dex_name} | {self._format_method(dex, mid)} | matched=({matched})"
104
+ )
105
+ return ret
106
+
107
+ grouped = defaultdict(set)
108
+ for i in range(len(mids)):
109
+ midx = mids[i]
110
+ if midx is None:
111
+ continue
112
+ idx = matched_idxs[i]
113
+ if isinstance(midx, list):
114
+ midxs = midx
115
+ else:
116
+ midxs = [midx]
117
+ for mid in midxs:
118
+ grouped[mid].add(idx)
119
+
120
+ ret = []
121
+ for mid in sorted(grouped):
122
+ matched = "; ".join(
123
+ self._format_matched_name(dex, find_type, idx)
124
+ for idx in sorted(grouped[mid])
125
+ )
126
+ ret.append(
127
+ f"{dex_name} | {self._format_method(dex, mid)} | matched=({matched})"
128
+ )
129
+ return ret