From 4a61f3beb3d32957e0f8f4964dfdc13d40940b4e Mon Sep 17 00:00:00 2001 From: MG1937 <2586364982@qq.com> Date: Sat, 10 Oct 2026 16:01:00 +0800 Subject: [PATCH 1/3] adopt 3.14 thread-free --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9a25adf..0f7e6fe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "droidasc" -version = "0.1.1.post4" +version = "0.1.1.post5" description = "ASC is a super FAST python Android Decompiler for Agents/Mobile Researchers" readme = "README.md" license = "Apache-2.0" @@ -12,7 +12,7 @@ license-files = ["LICENSE"] requires-python = ">=3.10" dependencies = [ "axml>=0.0.2", - "mutf8>=1.1.0", + "mutf8>=1.0.6", ] [project.scripts] From b4af9e002be6a40f9c691f339443343e8d2b28cf Mon Sep 17 00:00:00 2001 From: Dimitris Chatzis Date: Sat, 10 Oct 2026 22:56:32 +0300 Subject: [PATCH 2/3] fix(listclass): decode class names as MUTF-8 --- droidasc/asc_client/apk_handler.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) 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 From 90e3d0900572b2943400bf4a66951e1cc50711b5 Mon Sep 17 00:00:00 2001 From: Dimitris Chatzis Date: Sat, 10 Oct 2026 22:57:20 +0300 Subject: [PATCH 3/3] fix(findrefs): bound the exact-member scan to the dex tables --- droidasc/asc_core/findrefs/locator/field_locator.py | 7 ++++++- droidasc/asc_core/findrefs/locator/method_locator.py | 8 +++++++- 2 files changed, 13 insertions(+), 2 deletions(-) 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()