diff --git a/droidasc/asc_client/apk_handler.py b/droidasc/asc_client/apk_handler.py index 39376bc..864aaea 100644 --- a/droidasc/asc_client/apk_handler.py +++ b/droidasc/asc_client/apk_handler.py @@ -415,6 +415,10 @@ def get_class_dex(self, dalvik_class : str): fp.close() def list_classes(self, prefix : str = None): + # deferred like the ProcessPoolExecutor import in for_each_findrefs, so the + # getclass path does not pay for it + from droidasc.asc_core.utils.tinydex import _decode_mutf8_fallback + if self.max_workers <= 0: raise ValueError("Worker count must be greater than zero") if prefix is not None: @@ -425,6 +429,15 @@ def list_classes(self, prefix : str = None): prefix = "L" + prefix.replace(".", "/") prefix_bytes = prefix.encode("ascii") if prefix and prefix.isascii() else None + def decode_name(raw_name : bytes) -> str: + # string_data_item is MUTF-8, so strict UTF-8 rejects a surrogate pair + # above the BMP; decode exactly as DEX.get_string does, or a listing + # names a class differently from every other command + try: + return raw_name.decode("utf-8") + except UnicodeDecodeError: + return _decode_mutf8_fallback(raw_name) + def list_dex_classes(buf): if len(buf) < 0x70 or buf[:3] != b"dex": raise ValueError("invalid DEX header") @@ -464,9 +477,9 @@ def list_dex_classes(buf): if prefix_bytes is not None: if raw_name.startswith(prefix_bytes): - append(raw_name.decode("utf-8", errors="replace")) + append(decode_name(raw_name)) else: - name = raw_name.decode("utf-8", errors="replace") + name = decode_name(raw_name) if prefix is None or name.startswith(prefix): append(name) return names diff --git a/droidasc/asc_core/findrefs/locator/field_locator.py b/droidasc/asc_core/findrefs/locator/field_locator.py index e68e588..4192746 100644 --- a/droidasc/asc_core/findrefs/locator/field_locator.py +++ b/droidasc/asc_core/findrefs/locator/field_locator.py @@ -108,7 +108,12 @@ def locate(self, find : dict) -> set: if clz is None: if field_precise: - return {fid for fid, item in enumerate(self.dex.fields) if item.name == field} + # see MethodLocator.locate: dex.fields is a proxy without __iter__, + # so enumerate() walks off the field table instead of stopping at it + return { + fid for fid in range(len(self.dex.fields)) + if self.dex.fields[fid].name == field + } name_idxs = self.str_locator.locate(field) field_maps = self.field_maps ret = set() diff --git a/droidasc/asc_core/findrefs/locator/method_locator.py b/droidasc/asc_core/findrefs/locator/method_locator.py index ed85f58..bc9b7c8 100644 --- a/droidasc/asc_core/findrefs/locator/method_locator.py +++ b/droidasc/asc_core/findrefs/locator/method_locator.py @@ -107,7 +107,13 @@ def locate(self, find : dict) -> set: if clz is None: if method_precise: - return {mid for mid, item in enumerate(self.dex.methods) if item.name == method} + # dex.methods is a proxy without __iter__, so enumerate() falls back + # to the sequence protocol and never sees an IndexError - it walks off + # the method table into garbage. Bound the walk by the table size. + return { + mid for mid in range(len(self.dex.methods)) + if self.dex.methods[mid].name == method + } name_idxs = self.str_locator.locate(method) method_maps = self.method_maps ret = set()