From b9bf34e74ca7a644813c2d7200d27cdec9ab3d9f Mon Sep 17 00:00:00 2001 From: pakbaz-dev Date: Sun, 4 May 2025 19:31:08 +0000 Subject: [PATCH 1/6] Fix: use a queue instead of recursion to avoid RecursionError and exclude the duplicates to avoid a cycle --- pyaxmlparser/arscparser.py | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/pyaxmlparser/arscparser.py b/pyaxmlparser/arscparser.py index 5ef5772..be046a6 100644 --- a/pyaxmlparser/arscparser.py +++ b/pyaxmlparser/arscparser.py @@ -553,28 +553,31 @@ def resolve(self, res_id): return result def _resolve_into_result(self, result, res_id, config): - configs = self.resources.get_res_configs(res_id, config) - if configs: - for config, ate in configs: - self.put_ate_value(result, ate, config) - - def put_ate_value(self, result, ate, config): + queue = [(res_id, config)] + index = 0 + + while index < len(queue): + current_res_id, current_config = queue[index] + index += 1 + configs = self.resources.get_res_configs(current_res_id, current_config) + if configs: + for config, ate in configs: + self.put_ate_value(result, ate, config, queue) + + def put_ate_value(self, result, ate, config, queue): if ate.is_complex(): complex_array = [] result.append((config, complex_array)) for _, item in ate.item.items: - self.put_item_value(complex_array, item, config, complex_=True) + self.put_item_value(complex_array, item, config, queue, complex_=True) else: - self.put_item_value(result, ate.key, config, complex_=False) + self.put_item_value(result, ate.key, config, queue, complex_=False) - def put_item_value(self, result, item, config, complex_): + def put_item_value(self, result, item, config, queue, complex_): if item.is_reference(): res_id = item.get_data() - if res_id: - self._resolve_into_result( - result, - item.get_data(), - self.wanted_config) + if res_id and (res_id, self.wanted_config) not in queue: + queue.append((res_id, self.wanted_config)) else: if complex_: result.append(item.format_value()) From 4516dff09473d7f92d00eeb0b634b8b135bdb7e2 Mon Sep 17 00:00:00 2001 From: pakbaz-dev Date: Sun, 4 May 2025 19:32:34 +0000 Subject: [PATCH 2/6] Fix: continue even if res0/res1 is not zero --- pyaxmlparser/arscutil.py | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/pyaxmlparser/arscutil.py b/pyaxmlparser/arscutil.py index 309c9b7..f5a031f 100644 --- a/pyaxmlparser/arscutil.py +++ b/pyaxmlparser/arscutil.py @@ -125,21 +125,16 @@ def __init__(self, buff, parent=None): self.parent = parent self.id = unpack(' Date: Mon, 5 May 2025 14:59:42 +0000 Subject: [PATCH 3/6] Fix: add support for elements --- pyaxmlparser/core.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/pyaxmlparser/core.py b/pyaxmlparser/core.py index f694e76..92dae30 100644 --- a/pyaxmlparser/core.py +++ b/pyaxmlparser/core.py @@ -267,6 +267,7 @@ def __init__(self, filename, raw=False, skip_analysis=False, testzip=False): self.androidversion = {} self.permissions = [] self.uses_permissions = [] + self.uses_permission_sdk_23 = [] self.declared_permissions = {} self.valid_apk = False @@ -358,6 +359,7 @@ def _apk_analysis(self): self.androidversion["Code"] = self.get_attribute_value("manifest", "versionCode") self.androidversion["Name"] = self.get_attribute_value("manifest", "versionName") permission = list(self.get_all_attribute_value("uses-permission", "name")) + permission += list(self.get_all_attribute_value("uses-permission-sdk-23", "name")) self.permissions = list(set(self.permissions + permission)) for uses_permission in self.find_tags("uses-permission"): @@ -366,6 +368,12 @@ def _apk_analysis(self): self._get_permission_maxsdk(uses_permission) ]) + for uses_permission in self.find_tags("uses-permission-sdk-23"): + self.uses_permission_sdk_23.append([ + self.get_value_from_tag(uses_permission, "name"), + self._get_permission_maxsdk(uses_permission) + ]) + # getting details of the declared permissions for d_perm_item in self.find_tags('permission'): d_perm_name = self._get_res_string_value( @@ -439,7 +447,7 @@ def _get_permission_maxsdk(self, item): try: maxSdkVersion = int(self.get_value_from_tag(item, "maxSdkVersion")) except ValueError: - log.warning(self.get_max_sdk_version() + 'is not a valid value for maxSdkVersion') + log.warning(f"{self.get_max_sdk_version()} is not a valid value for <{item.tag}> maxSdkVersion") except TypeError: pass return maxSdkVersion @@ -1206,7 +1214,7 @@ def get_uses_implied_permission_list(self): if (WRITE_EXTERNAL_STORAGE in self.permissions or implied_WRITE_EXTERNAL_STORAGE) \ and READ_EXTERNAL_STORAGE not in self.permissions: maxSdkVersion = None - for name, version in self.uses_permissions: + for name, version in self.uses_permissions + self.uses_permission_sdk_23: if name == WRITE_EXTERNAL_STORAGE: maxSdkVersion = version break From 3c4b4381f922d92fae70e745664beb05f293035d Mon Sep 17 00:00:00 2001 From: pakbaz-dev Date: Wed, 21 May 2025 11:28:43 +0000 Subject: [PATCH 4/6] Fix an edge case in StringBlock.getString() --- pyaxmlparser/stringblock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyaxmlparser/stringblock.py b/pyaxmlparser/stringblock.py index 3a008f4..5a89774 100644 --- a/pyaxmlparser/stringblock.py +++ b/pyaxmlparser/stringblock.py @@ -126,7 +126,7 @@ def getString(self, idx): if idx in self._cache: return self._cache[idx] - if idx < 0 or not self.m_stringOffsets or idx > self.stringCount: + if idx < 0 or not self.m_stringOffsets or idx >= self.stringCount: return "" offset = self.m_stringOffsets[idx] From 6dad2f2b6bb39e94ebb2102521825023bdf2e7c0 Mon Sep 17 00:00:00 2001 From: pakbaz-dev Date: Wed, 21 May 2025 11:49:40 +0000 Subject: [PATCH 5/6] Implement FLAG_SPARSE and FLAG_OFFSET16 defined in ResTable_type --- pyaxmlparser/arscparser.py | 29 ++++++++++++++++++++++------- pyaxmlparser/arscutil.py | 10 ++++++++++ 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/pyaxmlparser/arscparser.py b/pyaxmlparser/arscparser.py index be046a6..115a1f7 100644 --- a/pyaxmlparser/arscparser.py +++ b/pyaxmlparser/arscparser.py @@ -125,16 +125,31 @@ def __init__(self, raw_buff): entries = [] for i in range(0, a_res_type.entryCount): - current_package.mResId = current_package.mResId & 0xffff0000 | i - entries.append((unpack(' Date: Wed, 21 May 2025 12:10:22 +0000 Subject: [PATCH 6/6] Implement FLAG_COMPACT defined in ResTable_entry --- pyaxmlparser/arscparser.py | 28 ++++++++-------- pyaxmlparser/arscutil.py | 67 ++++++++++++++++++++++++-------------- 2 files changed, 57 insertions(+), 38 deletions(-) diff --git a/pyaxmlparser/arscparser.py b/pyaxmlparser/arscparser.py index 115a1f7..1097e6d 100644 --- a/pyaxmlparser/arscparser.py +++ b/pyaxmlparser/arscparser.py @@ -202,7 +202,7 @@ def _analyse(self): self.resource_values[ate.mResId][a_res_type.config] = ate self.resource_keys[package_name][a_res_type.get_type()][ate.get_value()] = ate.mResId - if ate.get_index() != ARSCResType.NO_ENTRY: + if ate.get_key() != ARSCResType.NO_ENTRY: c_value["public"].append( (a_res_type.get_type(), ate.get_value(), ate.mResId)) @@ -241,29 +241,29 @@ def _analyse(self): nb += 1 def get_resource_string(self, ate): - return [ate.get_value(), ate.get_key_data()] + return [ate.get_value(), ate.get_value_data()] def get_resource_id(self, ate): x = [ate.get_value()] - if ate.key.get_data() == 0: + if ate.value.get_data() == 0: x.append("false") - elif ate.key.get_data() == 1: + elif ate.value.get_data() == 1: x.append("true") return x def get_resource_bool(self, ate): x = [ate.get_value()] - if ate.key.get_data() == 0: + if ate.value.get_data() == 0: x.append("false") - elif ate.key.get_data() == 0xFFFFFFFF: + elif ate.value.get_data() == 0xFFFFFFFF: x.append("true") return x def get_resource_integer(self, ate): - return [ate.get_value(), ate.key.get_data()] + return [ate.get_value(), ate.value.get_data()] def get_resource_color(self, ate): - entry_data = ate.key.get_data() + entry_data = ate.value.get_data() return [ ate.get_value(), "#%02x%02x%02x%02x" % ( @@ -277,14 +277,14 @@ def get_resource_dimen(self, ate): try: return [ ate.get_value(), "%s%s" % ( - complexToFloat(ate.key.get_data()), - const.DIMENSION_UNITS[ate.key.get_data() & const.COMPLEX_UNIT_MASK]) + complexToFloat(ate.value.get_data()), + const.DIMENSION_UNITS[ate.value.get_data() & const.COMPLEX_UNIT_MASK]) ] except IndexError: log.debug("Out of range dimension unit index for %s: %s" % ( - complexToFloat(ate.key.get_data()), - ate.key.get_data() & const.COMPLEX_UNIT_MASK)) - return [ate.get_value(), ate.key.get_data()] + complexToFloat(ate.value.get_data()), + ate.value.get_data() & const.COMPLEX_UNIT_MASK)) + return [ate.get_value(), ate.value.get_data()] # FIXME def get_resource_style(self, ate): @@ -586,7 +586,7 @@ def put_ate_value(self, result, ate, config, queue): for _, item in ate.item.items: self.put_item_value(complex_array, item, config, queue, complex_=True) else: - self.put_item_value(result, ate.key, config, queue, complex_=False) + self.put_item_value(result, ate.value, config, queue, complex_=False) def put_item_value(self, result, item, config, queue, complex_): if item.is_reference(): diff --git a/pyaxmlparser/arscutil.py b/pyaxmlparser/arscutil.py index ad325e2..6e8a01d 100644 --- a/pyaxmlparser/arscutil.py +++ b/pyaxmlparser/arscutil.py @@ -526,6 +526,7 @@ class ARSCResTableEntry(object): FLAG_COMPLEX = 1 FLAG_PUBLIC = 2 FLAG_WEAK = 4 + FLAG_COMPACT = 8 def __init__(self, buff, mResId, parent=None): self.start = buff.get_idx() @@ -533,22 +534,27 @@ def __init__(self, buff, mResId, parent=None): self.parent = parent self.size = unpack('> 8, parent=parent) + elif self.is_complex(): self.item = ARSCComplex(buff, parent) else: # If FLAG_COMPLEX is not set, a Res_value structure will follow - self.key = ARSCResStringPoolRef(buff, self.parent) + self.value = ARSCResValue.fetch(buff, parent) - def get_index(self): - return self.index + def get_key(self): + return self.compact_key if self.is_compact() else self.key def get_value(self): - return self.parent.mKeyStrings.getString(self.index) + return self.parent.mKeyStrings.getString(self.key) - def get_key_data(self): - return self.key.get_data_value() + def get_value_data(self): + return self.value.get_data_value() def is_public(self): return (self.flags & self.FLAG_PUBLIC) != 0 @@ -559,16 +565,19 @@ def is_complex(self): def is_weak(self): return (self.flags & self.FLAG_WEAK) != 0 + def is_compact(self): + return (self.flags & self.FLAG_COMPACT) != 0 + def __repr__(self): return ( "" + "flags='0x{:02x}' key='0x{:x}' holding={}>" ).format( self.start, self.mResId, self.size, self.flags, - self.index, + self.key, self.item if self.is_complex() else self.key) @@ -582,24 +591,35 @@ def __init__(self, buff, parent=None): self.items = [] for i in range(0, self.count): - self.items.append((unpack('".format(self.start, self.id_parent, self.count) - -class ARSCResStringPoolRef(object): - def __init__(self, buff, parent=None): - self.start = buff.get_idx() +class ARSCResValue: + def __init__(self, data, data_type, res0=0, size=8, parent=None): + self.size = size + self.res0 = res0 + self.data = data + self.data_type = data_type self.parent = parent - self.size, = unpack("".format( - self.start, + return "".format( self.size, const.TYPE_TABLE.get(self.data_type, "0x%x" % self.data_type), self.data)