From db87703967f598091a63d05c3dc2c2e2d1d927c0 Mon Sep 17 00:00:00 2001 From: MadSquirrel Date: Mon, 8 Apr 2024 08:32:50 +0200 Subject: [PATCH 1/6] Correct attribute name to be the same used by Android --- androguard/core/axml/__init__.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/androguard/core/axml/__init__.py b/androguard/core/axml/__init__.py index b221253a..59cbc529 100644 --- a/androguard/core/axml/__init__.py +++ b/androguard/core/axml/__init__.py @@ -821,14 +821,15 @@ class AXMLParser: res = self.sb[name] # If the result is a (null) string, we need to look it up. - if not res or res == ":": + if name <= len(self.m_resourceIDs): attr = self.m_resourceIDs[name] if attr in public.SYSTEM_RESOURCES['attributes']['inverse']: res = 'android:' + public.SYSTEM_RESOURCES['attributes']['inverse'][attr] - else: - # Attach the HEX Number, so for multiple missing attributes we do not run - # into problems. - res = 'android:UNKNOWN_SYSTEM_ATTRIBUTE_{:08x}'.format(attr) + + if not res or res == ":": + # Attach the HEX Number, so for multiple missing attributes we do not run + # into problems. + res = 'android:UNKNOWN_SYSTEM_ATTRIBUTE_{:08x}'.format(attr) return res def getAttributeValueType(self, index): From 293d85b29ef9c63896c0da67fd8824e3f8cf9fbe Mon Sep 17 00:00:00 2001 From: MadSquirrel Date: Mon, 8 Apr 2024 21:39:38 +0200 Subject: [PATCH 2/6] Fix test case --- androguard/core/axml/__init__.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/androguard/core/axml/__init__.py b/androguard/core/axml/__init__.py index 59cbc529..94b0143e 100644 --- a/androguard/core/axml/__init__.py +++ b/androguard/core/axml/__init__.py @@ -380,6 +380,7 @@ class AXMLParser: self.axml_tampered = False self.buff = io.BufferedReader(io.BytesIO(raw_buff)) self.buff_size = self.buff.raw.getbuffer().nbytes + self.packerwarning = False # Minimum is a single ARSCHeader, which would be a strange edge case... if self.buff_size < 8: @@ -824,7 +825,10 @@ class AXMLParser: if name <= len(self.m_resourceIDs): attr = self.m_resourceIDs[name] if attr in public.SYSTEM_RESOURCES['attributes']['inverse']: - res = 'android:' + public.SYSTEM_RESOURCES['attributes']['inverse'][attr] + res = public.SYSTEM_RESOURCES['attributes']['inverse'][attr].replace("_", + ":") + if res != self.sb[name]: + self.packerwarning = True if not res or res == ":": # Attach the HEX Number, so for multiple missing attributes we do not run @@ -1047,7 +1051,7 @@ class AXMLPrinter: :returns: True if packer detected, False otherwise """ - return self.packerwarning + return self.packerwarning or self.axml.packerwarning def _get_attribute_value(self, index): """ From be77adf9c8bbeb0349548c0f306547114551b888 Mon Sep 17 00:00:00 2001 From: olokos <1755581+olokos@users.noreply.github.com> Date: Mon, 22 Apr 2024 14:03:05 +0200 Subject: [PATCH 3/6] androsign: Fix #1031 & #764 - use oscrypto to load public_key instead of asn1crypto This commit replaces the outdated: asn1crypto.keys.PublicKeyInfo().fingerprint call With the new: oscrypto.asymmetric.PublicKey().fingerprint call ValueError/ve is properly excepted and when printed, it shows "Only DSA keys are generated using a hash algorithm, this key is RSA" for RSA signed apk's. This commit satisfies the: `asn1crypto._errors.APIException: asn1crypto.keys.PublicKeyInfo().fingerprint has been removed, please use oscrypto.asymmetric.PublicKey().fingerprint instead` while still keeping the original behavior. --- androguard/cli/main.py | 7 ++++--- requirements.txt | 1 + 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/androguard/cli/main.py b/androguard/cli/main.py index 2ff4b341..918261db 100644 --- a/androguard/cli/main.py +++ b/androguard/cli/main.py @@ -10,6 +10,7 @@ from loguru import logger from pygments import highlight from pygments.lexers import get_lexer_by_name from pygments.formatters.terminal import TerminalFormatter +from oscrypto import asymmetric # internal modules from androguard.core import androconf @@ -360,14 +361,14 @@ def androsign_main(args_apk, args_hash, args_all, show): for public_key in pkeys: if show: - x509_public_key = keys.PublicKeyInfo.load(public_key) + x509_public_key = asymmetric.load_public_key(public_key) print("PublicKey Algorithm:", x509_public_key.algorithm) print("Bit Size:", x509_public_key.bit_size) print("Fingerprint:", binascii.hexlify(x509_public_key.fingerprint)) try: - print("Hash Algorithm:", x509_public_key.hash_algo) + print("Hash Algorithm:", x509_public_key.asn1.hash_algo) except ValueError as ve: - # RSA pkey does not have an hash algorithm + # RSA pkey does not have a hash algorithm pass print() diff --git a/requirements.txt b/requirements.txt index 0ae9748c..4cfe51e3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,3 +14,4 @@ matplotlib networkx PyQt5 pyyaml +oscrypto>=1.3.0 From 4df9aed6e9a4433f6ff12fd27a2502dfbe08bdc3 Mon Sep 17 00:00:00 2001 From: erev0s <12457993+erev0s@users.noreply.github.com> Date: Sat, 27 Apr 2024 10:56:43 +0300 Subject: [PATCH 4/6] Update pyproject.toml --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index a7a300f9..4d29a040 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,6 +36,7 @@ PyQt5-Qt5 = [ {version = "5.15.2", markers = "sys_platform != 'darwin'"} ] pyyaml = "*" +oscrypto = ">=1.3.0" [tool.setuptools.package_data] "androguard.core.api_specific_resources" = ["aosp_permissions/*.json", "api_permission_mappings/*.json"] From 319c39876857b1dec5303badf7e0d7df090efee8 Mon Sep 17 00:00:00 2001 From: Branden Ehrenreich Date: Sun, 28 Apr 2024 02:40:27 -0400 Subject: [PATCH 5/6] Apply type annotations (#1042) * progress on typing * finish typing analysis.py * progress on typing dex/__init__.py * finish pass at dex.__init__.py typing * more types * more typing, and fix circular imports using 'if TYPE_CHECKING' checking * begin to change Generator->Iterator for typing, begin to returns that are type|None to Union[type,None] since that convention started in Python3.10 and Androguard supports 3.9+, and note current circular import issue. * type|None only works in Python3.10+, which is too high of an assumption for Androguard..change these to Union[type,None] * withoffset->with_offset * fix circular import issue due to adding imports for typing * types for permission loading * apply type hints to bytecode module * convert | to Union for further backwards compatibility, progress towards typing axml * finish typing axml * order imports, standardize type|None -> Union[type,None] * fix type for get_certificate_name_string param * explicitly import Name for typing * standardize type|None -> Union[type,None] * type annotate main * fix some inaccurate hints * type hint fixes * add imports for typing * remove unused import * remove explicit dependence on typing_extensions, as we can do self-referencing type hints using 'from __future__ import annotations'..however note that typing_extensions is still installed by some underlying package. --- androguard/cli/main.py | 50 +- androguard/core/analysis/analysis.py | 622 ++++--- androguard/core/androconf.py | 32 +- .../core/api_specific_resources/__init__.py | 7 +- androguard/core/apk/__init__.py | 232 +-- androguard/core/axml/__init__.py | 232 +-- androguard/core/bytecode.py | 66 +- androguard/core/dex/__init__.py | 1658 +++++++++-------- androguard/decompiler/basic_blocks.py | 13 +- androguard/decompiler/dast.py | 6 +- androguard/decompiler/decompile.py | 63 +- androguard/decompiler/decompiler.py | 28 +- androguard/decompiler/util.py | 20 +- androguard/decompiler/writer.py | 2 +- androguard/misc.py | 63 +- androguard/session.py | 47 +- androguard/util.py | 21 +- requirements.txt | 2 +- tests/test_analysis.py | 20 +- 19 files changed, 1654 insertions(+), 1530 deletions(-) diff --git a/androguard/cli/main.py b/androguard/cli/main.py index 918261db..e8d86332 100644 --- a/androguard/cli/main.py +++ b/androguard/cli/main.py @@ -3,6 +3,7 @@ import os import re import shutil import sys +from typing import Union # 3rd party modules from lxml import etree @@ -13,6 +14,8 @@ from pygments.formatters.terminal import TerminalFormatter from oscrypto import asymmetric # internal modules +from androguard.core.axml import ARSCParser +from androguard.session import Session from androguard.core import androconf from androguard.core import apk from androguard.core.axml import AXMLPrinter @@ -20,8 +23,11 @@ from androguard.core.dex import get_bytecodes_method from androguard.util import readFile from androguard.ui import DynamicUI - -def androaxml_main(inp, outp=None, resource=None): +def androaxml_main( + inp:str, + outp:Union[str,None]=None, + resource:Union[str,None]=None) -> None: + ret_type = androconf.is_android(inp) if ret_type == "APK": a = apk.APK(inp) @@ -47,7 +53,13 @@ def androaxml_main(inp, outp=None, resource=None): sys.stdout.write(highlight(buff.decode("UTF-8"), get_lexer_by_name("xml"), TerminalFormatter())) -def androarsc_main(arscobj, outp=None, package=None, typ=None, locale=None): +def androarsc_main( + arscobj: ARSCParser, + outp:Union[str,None]=None, + package:Union[str,None]=None, + typ:Union[str,None]=None, + locale:Union[str,None]=None) -> None: + package = package or arscobj.get_packages_names()[0] ttype = typ or "public" locale = locale or '\x00\x00' @@ -75,13 +87,15 @@ def androarsc_main(arscobj, outp=None, package=None, typ=None, locale=None): sys.stdout.write(highlight(buff.decode("UTF-8"), get_lexer_by_name("xml"), TerminalFormatter())) -def export_apps_to_format(filename, - s, - output, - methods_filter=None, - jar=None, - decompiler_type=None, - form=None): +def export_apps_to_format( + filename:str, + s: Session, + output: str, + methods_filter:Union[str,None]=None, + jar:bool=False, + decompiler_type:Union[str,None]=None, + form:Union[str,None]=None) -> None: + from androguard.misc import clean_file_name from androguard.core.bytecode import method2dot, method2format from androguard.decompiler import decompiler @@ -188,18 +202,18 @@ def export_apps_to_format(filename, print() -def valid_class_name(class_name): +def valid_class_name(class_name:str) -> str: if class_name[-1] == ";": class_name = class_name[1:-1] return os.path.join(*class_name.split("/")) -def create_directory(pathdir): +def create_directory(pathdir:str) -> None: if not os.path.exists(pathdir): os.makedirs(pathdir) -def androlyze_main(session, filename): +def androlyze_main(session:Session, filename:str) -> None: """ Start an interactive shell @@ -275,7 +289,7 @@ def androlyze_main(session, filename): print(dx) print() - def shutdown_hook(): + def shutdown_hook() -> None: """Save the session on exit, if wanted""" if not s.isOpen(): return @@ -298,7 +312,7 @@ def androlyze_main(session, filename): ipshell() -def androsign_main(args_apk, args_hash, args_all, show): +def androsign_main(args_apk:list[str], args_hash:str, args_all:bool, show:bool) -> None: from androguard.core.apk import APK from androguard.util import get_certificate_name_string @@ -381,7 +395,7 @@ def androsign_main(args_apk, args_hash, args_all, show): print() -def androdis_main(offset, size, dex_file): +def androdis_main(offset:int, size:int, dex_file:str) -> None: from androguard.core.dex import DEX with open(dex_file, "rb") as fp: @@ -414,7 +428,7 @@ def androdis_main(offset, size, dex_file): idx += i.get_length() -def androtrace_main(apk_file, list_modules, live=False, enable_ui=False): +def androtrace_main(apk_file:str, list_modules:list[str], live:bool=False, enable_ui:bool=False) -> None: from androguard.pentest import Pentest from androguard.session import Session @@ -459,7 +473,7 @@ def androtrace_main(apk_file, list_modules, live=False, enable_ui=False): s = input("Type 'e' to exit:") -def androdump_main(package_name, list_modules): +def androdump_main(package_name:str, list_modules:list[str]) -> None: from androguard.pentest import Pentest from androguard.session import Session diff --git a/androguard/core/analysis/analysis.py b/androguard/core/analysis/analysis.py index 5ee3c7d3..4cee6b26 100644 --- a/androguard/core/analysis/analysis.py +++ b/androguard/core/analysis/analysis.py @@ -1,12 +1,17 @@ -from androguard.core.androconf import is_ascii_problem, load_api_specific_resource_module -from androguard.core import bytecode, mutf8, dex +# Allows type hinting of types not-yet-declared +# in Python >= 3.7 +# see https://peps.python.org/pep-0563/ +from __future__ import annotations -import re -import sys import collections -from operator import itemgetter -import time from enum import IntEnum +from operator import itemgetter +import re +import time +from typing import Union, Iterator + +from androguard.core.androconf import is_ascii_problem, load_api_specific_resource_module +from androguard.core import bytecode, dex from loguru import logger import networkx as nx @@ -18,7 +23,6 @@ for i in dex.BRANCH_DEX_OPCODES: if p.match(items[1][0]): BasicOPCODES.add(op) - class REF_TYPE(IntEnum): """ Stores the opcodes for the type of usage in an XREF. @@ -38,15 +42,136 @@ class REF_TYPE(IntEnum): INVOKE_STATIC_RANGE = 0x77 INVOKE_INTERFACE_RANGE = 0x78 +class ExceptionAnalysis: + def __init__(self, exception: list, basic_blocks: BasicBlocks): + self.start = exception[0] + self.end = exception[1] + + self.exceptions = exception[2:] + + for i in self.exceptions: + i.append(basic_blocks.get_basic_block(i[1])) + + def show_buff(self) -> str: + buff = "{:x}:{:x}\n".format(self.start, self.end) + + for i in self.exceptions: + if i[2] is None: + buff += "\t({} -> {:x} {})\n".format(i[0], i[1], i[2]) + else: + buff += "\t({} -> {:x} {})\n".format(i[0], i[1], i[2].get_name()) + + return buff[:-1] + + def get(self) -> dict[str, Union[int, list[dict[str, Union[str, int]]]]]: + d = {"start": self.start, "end": self.end, "list": []} + + for i in self.exceptions: + d["list"].append({"name": i[0], "idx": i[1], "basic_block": i[2].get_name()}) + + return d + + +class Exceptions: + def __init__(self) -> None: + self.exceptions = [] + + def add(self, exceptions: list[list], basic_blocks: BasicBlocks) -> None: + for i in exceptions: + self.exceptions.append(ExceptionAnalysis(i, basic_blocks)) + + def get_exception(self, addr_start: int, addr_end: int) -> Union[ExceptionAnalysis,None]: + for i in self.exceptions: + if i.start >= addr_start and i.end <= addr_end: + return i + + elif addr_end <= i.end and addr_start >= i.start: + return i + + return None + + def gets(self) -> list[ExceptionAnalysis]: + return self.exceptions + + def get(self) -> Iterator[ExceptionAnalysis]: + for i in self.exceptions: + yield i + +class BasicBlocks: + """ + This class represents all basic blocks of a method. + + It is a collection of many :class:`DEXBasicBlock`. + """ + def __init__(self) -> None: + self.bb = [] + + def push(self, bb: DEXBasicBlock) -> None: + """ + Adds another basic block to the collection + + :param :class:`DEXBasicBlock` bb: the DEXBasicBlock to add + """ + self.bb.append(bb) + + def pop(self, idx: int) -> DEXBasicBlock: + return self.bb.pop(idx) + + def get_basic_block(self, idx: int) -> Union[DEXBasicBlock,None]: + for i in self.bb: + if i.get_start() <= idx < i.get_end(): + return i + return None + + def __len__(self): + return len(self.bb) + + def __iter__(self): + """ + :returns: yields each basic block (:class:`DEXBasicBlock` object) + :rtype: Iterator[DEXBasicBlock] + """ + yield from self.bb + + def __getitem__(self, item: int): + """ + Get the basic block at the index + + :param item: index + :return: The basic block + :rtype: DEXBasicBlock + """ + return self.bb[item] + + def gets(self) -> list[DEXBasicBlock]: + """ + :returns: a list of basic blocks (:class:`DEXBasicBlock` objects) + """ + return self.bb + + # Alias for legacy programs + get = __iter__ + get_basic_block_pos = __getitem__ class DEXBasicBlock: """ A simple basic block of a DEX method. - A basic block consists of a series of :class:`~androguard.core.bytecodes.dvm.Instruction` + A basic block consists of a series of :class:`~androguard.core.dex.Instruction` which are not interrupted by branch or jump instructions such as `goto`, `if`, `throw`, `return`, `switch` etc. """ - def __init__(self, start, vm, method, context): + def __init__(self, start: int, vm: dex.DEX, method: dex.EncodedMethod, context: BasicBlocks): + """_summary_ + + :param start: _description_ + :type start: int + :param vm: _description_ + :type vm: dex.DEX + :param method: _description_ + :type method: dex.EncodedMethod + :param context: _description_ + :type context: BasicBlocks + """ self.__vm = vm self.method = method self.context = context @@ -73,19 +198,19 @@ class DEXBasicBlock: self.__cached_instructions = None - def get_notes(self): + def get_notes(self) -> list[str]: return self.notes - def set_notes(self, value): + def set_notes(self, value: str) -> None: self.notes = [value] - def add_note(self, note): + def add_note(self, note: str) -> None: self.notes.append(note) - def clear_notes(self): + def clear_notes(self) -> None: self.notes = [] - def get_instructions(self): + def get_instructions(self) -> Iterator[dex.Instruction]: """ Get all instructions from a basic block. @@ -97,22 +222,22 @@ class DEXBasicBlock: yield i idx += i.get_length() - def get_nb_instructions(self): + def get_nb_instructions(self) -> int: return self.nb_instructions - def get_method(self): + def get_method(self) -> dex.EncodedMethod: """ Returns the originiating method :return: the method - :rtype: androguard.core.bytecodes.dvm.EncodedMethod + :rtype: androguard.core.dex.EncodedMethod """ return self.method - def get_name(self): + def get_name(self) -> str: return self.name - def get_start(self): + def get_start(self) -> int: """ Get the starting offset of this basic block @@ -121,7 +246,7 @@ class DEXBasicBlock: """ return self.start - def get_end(self): + def get_end(self) -> int: """ Get the end offset of this basic block @@ -130,15 +255,15 @@ class DEXBasicBlock: """ return self.end - def get_last(self): + def get_last(self) -> dex.Instruction: """ Get the last instruction in the basic block - :return: androguard.core.bytecodes.dvm.Instruction + :return: androguard.core.dex.Instruction """ return list(self.get_instructions())[-1] - - def get_next(self): + + def get_next(self) -> DEXBasicBlock: """ Get next basic blocks @@ -147,7 +272,7 @@ class DEXBasicBlock: """ return self.childs - def get_prev(self): + def get_prev(self) -> DEXBasicBlock: """ Get previous basic blocks @@ -155,14 +280,14 @@ class DEXBasicBlock: :rtype: DEXBasicBlock """ return self.fathers - - def set_fathers(self, f): + + def set_fathers(self, f: DEXBasicBlock) -> None: self.fathers.append(f) - def get_last_length(self): + def get_last_length(self) -> int: return self.last_length - def set_childs(self, values): + def set_childs(self, values: list[int]) -> None: # print self, self.start, self.end, values if not values: next_block = self.context.get_basic_block(self.end + 1) @@ -181,7 +306,7 @@ class DEXBasicBlock: if c[2] is not None: c[2].set_fathers((c[1], c[0], self)) - def push(self, i): + def push(self, i: DEXBasicBlock) -> None: self.nb_instructions += 1 idx = self.end self.last_length = i.get_length() @@ -192,8 +317,8 @@ class DEXBasicBlock: if op_value == 0x26 or (0x2b <= op_value <= 0x2c): code = self.method.get_code().get_bc() self.special_ins[idx] = code.get_ins_off(idx + i.get_ref_off() * 2) - - def get_special_ins(self, idx): + + def get_special_ins(self, idx: int) -> Union[dex.Instruction,None]: """ Return the associated instruction to a specific instruction (for example a packed/sparse switch) @@ -206,132 +331,19 @@ class DEXBasicBlock: else: return None - def get_exception_analysis(self): + def get_exception_analysis(self) -> ExceptionAnalysis: return self.exception_analysis - def set_exception_analysis(self, exception_analysis): + def set_exception_analysis(self, exception_analysis: ExceptionAnalysis): self.exception_analysis = exception_analysis - def show(self): + def show(self) -> None: print("{}: {:04x} - {:04x}".format(self.get_name(), self.get_start(), self.get_end())) for note in self.get_notes(): print(note) print('=' * 20) -class BasicBlocks: - """ - This class represents all basic blocks of a method. - - It is a collection of many :class:`DEXBasicBlock`. - """ - def __init__(self): - self.bb = [] - - def push(self, bb): - """ - Adds another basic block to the collection - - :param DVBMBasicBlock bb: the DEXBasicBlock to add - """ - self.bb.append(bb) - - def pop(self, idx): - return self.bb.pop(idx) - - def get_basic_block(self, idx): - for i in self.bb: - if i.get_start() <= idx < i.get_end(): - return i - return None - - def __len__(self): - return len(self.bb) - - def __iter__(self): - """ - :returns: yields each basic block (:class:`DEXBasicBlock` object) - :rtype: Iterator[DEXBasicBlock] - """ - yield from self.bb - - def __getitem__(self, item): - """ - Get the basic block at the index - - :param item: index - :return: The basic block - :rtype: DEXBasicBlock - """ - return self.bb[item] - - def gets(self): - """ - :returns: a list of basic blocks (:class:`DEXBasicBlock` objects) - """ - return self.bb - - # Alias for legacy programs - get = __iter__ - get_basic_block_pos = __getitem__ - - -class ExceptionAnalysis: - def __init__(self, exception, bb): - self.start = exception[0] - self.end = exception[1] - - self.exceptions = exception[2:] - - for i in self.exceptions: - i.append(bb.get_basic_block(i[1])) - - def show_buff(self): - buff = "{:x}:{:x}\n".format(self.start, self.end) - - for i in self.exceptions: - if i[2] is None: - buff += "\t({} -> {:x} {})\n".format(i[0], i[1], i[2]) - else: - buff += "\t({} -> {:x} {})\n".format(i[0], i[1], i[2].get_name()) - - return buff[:-1] - - def get(self): - d = {"start": self.start, "end": self.end, "list": []} - - for i in self.exceptions: - d["list"].append({"name": i[0], "idx": i[1], "bb": i[2].get_name()}) - - return d - - -class Exceptions: - def __init__(self): - self.exceptions = [] - - def add(self, exceptions, basic_blocks): - for i in exceptions: - self.exceptions.append(ExceptionAnalysis(i, basic_blocks)) - - def get_exception(self, addr_start, addr_end): - for i in self.exceptions: - if i.start >= addr_start and i.end <= addr_end: - return i - - elif addr_end <= i.end and addr_start >= i.start: - return i - - return None - - def gets(self): - return self.exceptions - - def get(self): - for i in self.exceptions: - yield i - - class MethodAnalysis: """ This class analyses in details a method of a class/dex file @@ -341,7 +353,7 @@ class MethodAnalysis: :type vm: a :class:`DEX` object :type method: a :class:`EncodedMethod` object """ - def __init__(self, vm, method): + def __init__(self, vm: dex.DEX, method: dex.EncodedMethod): logger.debug("Adding new method {} {}".format(method.get_class_name(), method.get_name())) self.__vm = vm @@ -377,42 +389,42 @@ class MethodAnalysis: self._create_basic_block() @property - def name(self): + def name(self) -> str: """Returns the name of this method""" return self.method.get_name() @property - def descriptor(self): + def descriptor(self) -> str: """Returns the type descriptor for this method""" return self.method.get_descriptor() @property - def access(self): + def access(self) -> str: """Returns the access flags to the method as a string""" return self.method.get_access_flags_string() @property - def class_name(self): + def class_name(self) -> str: """Returns the name of the class of this method""" return self.method.class_name @property - def full_name(self): + def full_name(self) -> str: """Returns classname + name + descriptor, separated by spaces (no access flags)""" return self.method.full_name - def get_class_name(self): + def get_class_name(self) -> str: """Return the class name of the method""" return self.class_name - def get_access_flags_string(self): + def get_access_flags_string(self) -> str: """Returns the concatenated access flags string""" return self.access - def get_descriptor(self): + def get_descriptor(self) -> str: return self.descriptor - def _create_basic_block(self): + def _create_basic_block(self) -> None: """ Internal Method to create the basic block structure Parses all instructions and exceptions. @@ -469,7 +481,7 @@ class MethodAnalysis: # setup exception by basic block i.set_exception_analysis(self.exceptions.get_exception(i.start, i.end - 1)) - def add_xref_read(self, classobj, fieldobj, offset): + def add_xref_read(self, classobj:ClassAnalysis, fieldobj: FieldAnalysis, offset: int) -> None: """ :param ClassAnalysis classobj: :param FieldAnalysis fieldobj: @@ -477,7 +489,7 @@ class MethodAnalysis: """ self.xrefread.add((classobj, fieldobj, offset)) - def add_xref_write(self, classobj, fieldobj, offset): + def add_xref_write(self, classobj: ClassAnalysis, fieldobj: FieldAnalysis, offset: int) -> None: """ :param ClassAnalysis classobj: :param FieldAnalysis fieldobj: @@ -485,7 +497,7 @@ class MethodAnalysis: """ self.xrefwrite.add((classobj, fieldobj, offset)) - def get_xref_read(self): + def get_xref_read(self) -> list[tuple[ClassAnalysis, FieldAnalysis]]: """ Returns a list of xrefs where a field is read by this method. @@ -495,7 +507,7 @@ class MethodAnalysis: """ return self.xrefread - def get_xref_write(self): + def get_xref_write(self) -> list[tuple[ClassAnalysis, FieldAnalysis]]: """ Returns a list of xrefs where a field is written to by this method. @@ -505,7 +517,7 @@ class MethodAnalysis: """ return self.xrefwrite - def add_xref_to(self, classobj, methodobj, offset): + def add_xref_to(self, classobj: ClassAnalysis, methodobj: MethodAnalysis, offset: int) -> None: """ Add a crossreference to another method (this method calls another method) @@ -516,7 +528,7 @@ class MethodAnalysis: """ self.xrefto.add((classobj, methodobj, offset)) - def add_xref_from(self, classobj, methodobj, offset): + def add_xref_from(self, classobj: ClassAnalysis, methodobj: MethodAnalysis, offset: int) -> None: """ Add a crossrefernece from another method (this method is called by another method) @@ -527,7 +539,7 @@ class MethodAnalysis: """ self.xreffrom.add((classobj, methodobj, offset)) - def get_xref_from(self): + def get_xref_from(self) -> list[tuple[ClassAnalysis, MethodAnalysis, int]]: """ Returns a list of tuples containing the class, method and offset of the call, from where this object was called. @@ -539,7 +551,7 @@ class MethodAnalysis: """ return self.xreffrom - def get_xref_to(self): + def get_xref_to(self) -> list[tuple[ClassAnalysis, MethodAnalysis, int]]: """ Returns a list of tuples containing the class, method and offset of the call, which are called by this method. @@ -551,7 +563,7 @@ class MethodAnalysis: """ return self.xrefto - def add_xref_new_instance(self, classobj, offset): + def add_xref_new_instance(self, classobj: ClassAnalysis, offset: int) -> None: """ Add a crossreference to another class that is instanced within this method. @@ -561,7 +573,7 @@ class MethodAnalysis: """ self.xrefnewinstance.add((classobj, offset)) - def get_xref_new_instance(self): + def get_xref_new_instance(self) -> list[tuple[ClassAnalysis, int]]: """ Returns a list of tuples containing the class and offset of the creation of a new instance of a class by this method. @@ -572,7 +584,7 @@ class MethodAnalysis: """ return self.xrefnewinstance - def add_xref_const_class(self, classobj, offset): + def add_xref_const_class(self, classobj: ClassAnalysis, offset: int) -> None: """ Add a crossreference to another classtype. @@ -581,7 +593,7 @@ class MethodAnalysis: """ self.xrefconstclass.add((classobj, offset)) - def get_xref_const_class(self): + def get_xref_const_class(self) -> list[tuple[ClassAnalysis, int]]: """ Returns a list of tuples containing the class and offset of the references to another classtype by this method. @@ -592,7 +604,7 @@ class MethodAnalysis: """ return self.xrefconstclass - def is_external(self): + def is_external(self) -> bool: """ Returns True if the underlying method is external @@ -600,7 +612,7 @@ class MethodAnalysis: """ return isinstance(self.method, ExternalMethod) - def is_android_api(self): + def is_android_api(self) -> bool: """ Returns True if the method seems to be an Android API method. @@ -628,7 +640,7 @@ class MethodAnalysis: return False - def get_basic_blocks(self): + def get_basic_blocks(self) -> BasicBlocks: """ Returns the :class:`BasicBlocks` generated for this method. The :class:`BasicBlocks` can be used to get a control flow graph (CFG) of the method. @@ -637,29 +649,29 @@ class MethodAnalysis: """ return self.basic_blocks - def get_length(self): + def get_length(self) -> int: """ :returns: an integer which is the length of the code :rtype: int """ return self.code.get_length() if self.code else 0 - def get_vm(self): + def get_vm(self) -> dex.DEX: """ - :rtype: androguard.core.bytecodes.dvm.DEX + :rtype: androguard.core.dex.DEX :return: """ return self.__vm - def get_method(self): + def get_method(self) -> dex.EncodedMethod: """ - :rtype: androguard.core.bytecodes.dvm.EncodedMethod + :rtype: androguard.core.dex.EncodedMethod :return: """ return self.method - def show(self): + def show(self) -> None: """ Prints the content of this method to stdout. @@ -685,7 +697,7 @@ class MethodAnalysis: if not self.is_external(): bytecode.PrettyShow(self.basic_blocks.gets(), self.method.notes) - def show_xrefs(self): + def show_xrefs(self) -> None: data = "XREFto for %s\n" % self.method for ref_class, ref_method, offset in self.xrefto: data += "in\n" @@ -711,7 +723,7 @@ class StringAnalysis: This Array stores the information in which method the String is used. """ - def __init__(self, value): + def __init__(self, value: str) -> None: """ :param str value: the original string value @@ -720,7 +732,7 @@ class StringAnalysis: self.orig_value = value self.xreffrom = set() - def add_xref_from(self, classobj, methodobj, off): + def add_xref_from(self, classobj: ClassAnalysis, methodobj: MethodAnalysis, off: int) -> None: """ Adds a xref from the given method to this string @@ -730,7 +742,7 @@ class StringAnalysis: """ self.xreffrom.add((classobj, methodobj, off)) - def get_xref_from(self, withoffset=False): + def get_xref_from(self, with_offset:bool = False) -> list[tuple[ClassAnalysis, MethodAnalysis]]: """ Returns a list of xrefs accessing the String. @@ -738,11 +750,11 @@ class StringAnalysis: where the class is represented as a :class:`ClassAnalysis`, while the method is a :class:`MethodAnalysis`. """ - if withoffset: + if with_offset: return self.xreffrom return set(map(itemgetter(slice(0, 2)), self.xreffrom)) - def set_value(self, value): + def set_value(self, value: str) -> None: """ Overwrite the current value of the String with a new value. The original value is not lost and can still be retrieved using :meth:`get_orig_value`. @@ -751,7 +763,7 @@ class StringAnalysis: """ self.value = value - def get_value(self): + def get_value(self) -> str: """ Return the (possible overwritten) value of the String @@ -759,7 +771,7 @@ class StringAnalysis: """ return self.value - def get_orig_value(self): + def get_orig_value(self) -> str: """ Return the original, read only, value of the String @@ -767,7 +779,7 @@ class StringAnalysis: """ return self.orig_value - def is_overwritten(self): + def is_overwritten(self) -> bool: """ Returns True if the string was overwritten :return: @@ -798,18 +810,18 @@ class FieldAnalysis: That means, that it will show you, where the field is read or written. - :param androguard.core.bytecodes.dvm.EncodedField field: `dvm.EncodedField` + :param androguard.core.dex.EncodedField field: `dvm.EncodedField` """ - def __init__(self, field): + def __init__(self, field: dex.EncodedField) -> None: self.field = field self.xrefread = set() self.xrefwrite = set() @property - def name(self): + def name(self) -> str: return self.field.get_name() - def add_xref_read(self, classobj, methodobj, offset): + def add_xref_read(self, classobj: ClassAnalysis, methodobj: MethodAnalysis, offset: int) -> None: """ :param ClassAnalysis classobj: :param MethodAnalysis methodobj: @@ -817,7 +829,7 @@ class FieldAnalysis: """ self.xrefread.add((classobj, methodobj, offset)) - def add_xref_write(self, classobj, methodobj, offset): + def add_xref_write(self, classobj: ClassAnalysis, methodobj: MethodAnalysis, offset: int) -> None: """ :param ClassAnalysis classobj: :param MethodAnalysis methodobj: @@ -825,7 +837,7 @@ class FieldAnalysis: """ self.xrefwrite.add((classobj, methodobj, offset)) - def get_xref_read(self, withoffset=False): + def get_xref_read(self, with_offset:bool = False) -> list[tuple[ClassAnalysis, MethodAnalysis]]: """ Returns a list of xrefs where the field is read. @@ -833,14 +845,14 @@ class FieldAnalysis: where the class is represented as a :class:`ClassAnalysis`, while the method is a :class:`MethodAnalysis`. - :param bool withoffset: return the xrefs including the offset + :param bool with_offset: return the xrefs including the offset """ - if withoffset: + if with_offset: return self.xrefread # Legacy option, might be removed in the future return set(map(itemgetter(slice(0, 2)), self.xrefread)) - def get_xref_write(self, withoffset=False): + def get_xref_write(self, with_offset:bool = False) -> list[tuple[ClassAnalysis, MethodAnalysis]]: """ Returns a list of xrefs where the field is written to. @@ -848,18 +860,18 @@ class FieldAnalysis: where the class is represented as a :class:`ClassAnalysis`, while the method is a :class:`MethodAnalysis`. - :param bool withoffset: return the xrefs including the offset + :param bool with_offset: return the xrefs including the offset """ - if withoffset: + if with_offset: return self.xrefwrite # Legacy option, might be removed in the future return set(map(itemgetter(slice(0, 2)), self.xrefwrite)) - def get_field(self): + def get_field(self) -> dex.EncodedField: """ Returns the actual field object - :rtype: androguard.core.bytecodes.dvm.EncodedField + :rtype: androguard.core.dex.EncodedField """ return self.field @@ -887,21 +899,21 @@ class ExternalClass: :param name: Name of the external class """ - def __init__(self, name): + def __init__(self, name: str) -> None: self.name = name self.methods = [] - def get_methods(self): + def get_methods(self) -> list[MethodAnalysis]: """ Return the stored methods for this external class :return: """ return self.methods - def add_method(self, method): + def add_method(self, method: MethodAnalysis) -> None: self.methods.append(method) - def get_name(self): + def get_name(self) -> str: """ Returns the name of the ExternalClass object """ @@ -919,39 +931,39 @@ class ExternalMethod: 1) The method is defined inside another DEX file which was not loaded into the Analysis 2) The method is an API method, hence it is defined in the Android system - External methods should have a similar API to :class:`~androguard.core.bytecodes.dvm.EncodedMethod` + External methods should have a similar API to :class:`~androguard.core.dex.EncodedMethod` but obviously they have no code attached. The only known information about such methods are the class name, the method name and its descriptor. :param str class_name: name of the class :param str name: name of the method - :param List[str] descriptor: descriptor string + :param str descriptor: descriptor string """ - def __init__(self, class_name, name, descriptor): + def __init__(self, class_name: str, name: str, descriptor: str) -> None: self.class_name = class_name self.name = name self.descriptor = descriptor - def get_name(self): + def get_name(self) -> str: return self.name - def get_class_name(self): + def get_class_name(self) -> str: return self.class_name - def get_descriptor(self): + def get_descriptor(self) -> str: return self.descriptor @property - def full_name(self): + def full_name(self) -> str: """Returns classname + name + descriptor, separated by spaces (no access flags)""" return self.class_name + " " + self.name + " " + str(self.get_descriptor()) @property - def permission_api_name(self): + def permission_api_name(self) -> str: """Returns a name which can be used to look up in the permission maps""" return self.class_name + "-" + self.name + "-" + str(self.get_descriptor()) - def get_access_flags_string(self): + def get_access_flags_string(self) -> str: """ Returns the access flags string. @@ -983,7 +995,7 @@ class ClassAnalysis: :param classobj: class:`~androguard.core.dex.ClassDefItem` or :class:`ExternalClass` """ - def __init__(self, classobj): + def __init__(self, classobj: Union[dex.ClassDefItem,ExternalClass]) -> None: logger.info(f"Adding new ClassAnalysis: {classobj}") # Automatically decide if the class is external or not self.external = isinstance(classobj, ExternalClass) @@ -1005,7 +1017,7 @@ class ClassAnalysis: # Reserved for further use self.apilist = None - def add_method(self, method_analysis): + def add_method(self, method_analysis: MethodAnalysis) -> None: """ Add the given method to this analyis. usually only called during Analysis.add and Analysis._resolve_method @@ -1018,7 +1030,7 @@ class ClassAnalysis: self.orig_class.add_method(method_analysis.get_method()) @property - def implements(self): + def implements(self) -> list[str]: """ Get a list of interfaces which are implemented by this class @@ -1030,7 +1042,7 @@ class ClassAnalysis: return self.orig_class.get_interfaces() @property - def extends(self): + def extends(self) -> str: """ Return the parent class @@ -1044,7 +1056,7 @@ class ClassAnalysis: return self.orig_class.get_superclassname() @property - def name(self): + def name(self) -> str: """ Return the class name @@ -1052,7 +1064,7 @@ class ClassAnalysis: """ return self.orig_class.get_name() - def is_external(self): + def is_external(self) -> bool: """ Tests if this class is an external class @@ -1060,7 +1072,7 @@ class ClassAnalysis: """ return self.external - def is_android_api(self): + def is_android_api(self) -> bool: """ Tries to guess if the current class is an Android API class. @@ -1088,27 +1100,28 @@ class ClassAnalysis: return False - def get_methods(self): + def get_methods(self) -> list[MethodAnalysis]: """ Return all :class:`MethodAnalysis` objects of this class :rtype: Iterator[MethodAnalysis] """ - return list(self._methods.values()) + return self._methods.values() + # return list(self._methods.values()) - def get_fields(self): + def get_fields(self) -> list[FieldAnalysis]: """ Return all `FieldAnalysis` objects of this class """ return self._fields.values() - def get_nb_methods(self): + def get_nb_methods(self) -> int: """ Get the number of methods in this class """ return len(self._methods) - def get_method_analysis(self, method): + def get_method_analysis(self, method: dex.EncodedMethod) -> MethodAnalysis: """ Return the MethodAnalysis object for a given EncodedMethod @@ -1118,10 +1131,17 @@ class ClassAnalysis: """ return self._methods.get(method) - def get_field_analysis(self, field): + def get_field_analysis(self, field: dex.EncodedMethod) -> FieldAnalysis: + """_summary_ + + :param field: _description_ + :type field: dex.EncodedMethod + :return: _description_ + :rtype: FieldAnalysis + """ return self._fields.get(field) - def add_field(self, field_analysis): + def add_field(self, field_analysis: FieldAnalysis) -> None: """ Add the given field to this analyis. usually only called during Analysis.add @@ -1133,13 +1153,13 @@ class ClassAnalysis: # # Propagate ExternalField to ExternalClass # self.orig_class.add_method(field_analysis.get_field()) - def add_field_xref_read(self, method, classobj, field, off): + def add_field_xref_read(self, method: MethodAnalysis, classobj: ClassAnalysis, field: dex.EncodedField, off: int) -> None: """ Add a Field Read to this class :param MethodAnalysis method: :param ClassAnalysis classobj: - :param androguard.code.bytecodes.dvm.EncodedField field: + :param androguard.code.dex.EncodedField field: :param int off: :return: """ @@ -1147,13 +1167,13 @@ class ClassAnalysis: self._fields[field] = FieldAnalysis(field) self._fields[field].add_xref_read(classobj, method, off) - def add_field_xref_write(self, method, classobj, field, off): + def add_field_xref_write(self, method: MethodAnalysis, classobj: ClassAnalysis, field: dex.EncodedField, off: int) -> None: """ Add a Field Write to this class in a given method :param MethodAnalysis method: :param ClassAnalysis classobj: - :param androguard.core.bytecodes.dvm.EncodedField field: + :param androguard.core.dex.EncodedField field: :param int off: :return: """ @@ -1161,7 +1181,7 @@ class ClassAnalysis: self._fields[field] = FieldAnalysis(field) self._fields[field].add_xref_write(classobj, method, off) - def add_method_xref_to(self, method1, classobj, method2, offset): + def add_method_xref_to(self, method1: MethodAnalysis, classobj: ClassAnalysis, method2: MethodAnalysis, offset: int) -> None: """ :param MethodAnalysis method1: the calling method @@ -1177,7 +1197,7 @@ class ClassAnalysis: self._methods[method1.get_method()].add_xref_to(classobj, method2, offset) - def add_method_xref_from(self, method1, classobj, method2, offset): + def add_method_xref_from(self, method1: MethodAnalysis, classobj: ClassAnalysis, method2: MethodAnalysis, offset: int) -> None: """ :param MethodAnalysis method1: @@ -1192,7 +1212,7 @@ class ClassAnalysis: self._methods[method1.get_method()].add_xref_from(classobj, method2, offset) - def add_xref_to(self, ref_kind, classobj, methodobj, offset): + def add_xref_to(self, ref_kind: REF_TYPE, classobj: ClassAnalysis, methodobj: MethodAnalysis, offset: int) -> None: """ Creates a crossreference to another class. XrefTo means, that the current class calls another class. @@ -1211,7 +1231,7 @@ class ClassAnalysis: """ self.xrefto[classobj].add((ref_kind, methodobj, offset)) - def add_xref_from(self, ref_kind, classobj, methodobj, offset): + def add_xref_from(self, ref_kind: REF_TYPE, classobj: ClassAnalysis, methodobj: MethodAnalysis, offset: int) -> None: """ Creates a crossreference from this class. XrefFrom means, that the current class is called by another class. @@ -1224,7 +1244,7 @@ class ClassAnalysis: """ self.xreffrom[classobj].add((ref_kind, methodobj, offset)) - def get_xref_from(self): + def get_xref_from(self) -> dict[ClassAnalysis, tuple[REF_TYPE, MethodAnalysis, int]]: """ Returns a dictionary of all classes calling the current class. This dictionary contains also information from which method the class is accessed. @@ -1250,7 +1270,7 @@ class ClassAnalysis: """ return self.xreffrom - def get_xref_to(self): + def get_xref_to(self) -> dict[ClassAnalysis, tuple[REF_TYPE, MethodAnalysis, int]]: """ Returns a dictionary of all classes which are called by the current class. This dictionary contains also information about the method which is called. @@ -1275,7 +1295,7 @@ class ClassAnalysis: """ return self.xrefto - def add_xref_new_instance(self, methobj, offset): + def add_xref_new_instance(self, methobj: MethodAnalysis, offset: int) -> None: """ Add a crossreference to another method that is instancing this class. @@ -1285,19 +1305,19 @@ class ClassAnalysis: """ self.xrefnewinstance.add((methobj, offset)) - def get_xref_new_instance(self): + def get_xref_new_instance(self) -> list[tuple[MethodAnalysis, int]]: """ Returns a list of tuples containing the set of methods with offsets that instance this class The list of tuples has the form: - (:class:`~MathodAnalysis`, + (:class:`~MethodAnalysis`, :class:`int`) """ return self.xrefnewinstance - def add_xref_const_class(self, methobj, offset): + def add_xref_const_class(self, methobj: MethodAnalysis, offset: int) -> None: """ Add a crossreference to a method referencing this classtype. @@ -1306,7 +1326,7 @@ class ClassAnalysis: """ self.xrefconstclass.add((methobj, offset)) - def get_xref_const_class(self): + def get_xref_const_class(self) -> list[tuple[MethodAnalysis, int]]: """ Returns a list of tuples containing the method and offset referencing this classtype. @@ -1317,22 +1337,22 @@ class ClassAnalysis: """ return self.xrefconstclass - def get_vm_class(self): + def get_vm_class(self) -> Union[dex.ClassDefItem,ExternalClass]: """ Returns the original Dalvik VM class or the external class object. :return: - :rtype: Union[androguard.core.bytecodes.dvm.ClassDefItem, ExternalClass] + :rtype: Union[androguard.core.dex.ClassDefItem, ExternalClass] """ return self.orig_class - def set_restriction_flag(self, flag): + def set_restriction_flag(self, flag: dex.HiddenApiClassDataItem.RestrictionApiFlag) -> None: """ Set the level of restriction for this class (hidden level, from Android 10) (only applicable to internal classes) :param flag: The flag to set to - :type flag: androguard.core.bytecodes.dvm.HiddenApiClassDataItem.RestrictionApiFlag + :type flag: androguard.core.dex.HiddenApiClassDataItem.RestrictionApiFlag """ if self.is_external(): raise RuntimeError( @@ -1340,13 +1360,13 @@ class ClassAnalysis: % (self.orig_class.name,)) self.restriction_flag = flag - def set_domain_flag(self, flag): + def set_domain_flag(self, flag: dex.HiddenApiClassDataItem.DomapiApiFlag) -> None: """ Set the api domain for this class (hidden level, from Android 10) (only applicable to internal classes) :param flag: The flag to set to - :type flag: androguard.core.bytecodes.dvm.HiddenApiClassDataItem.DomainApiFlag + :type flag: androguard.core.dex.HiddenApiClassDataItem.DomainApiFlag """ if self.is_external(): raise RuntimeError( @@ -1402,13 +1422,13 @@ class Analysis: * strings (`StringAnalyis`) * fields (`FieldAnalysis`) - The Analysis should be the only object you are using next to the :class:`~androguard.core.bytecodes.apk.APK`. + The Analysis should be the only object you are using next to the :class:`~androguard.core.apk.APK`. It encapsulates all the Dalvik related functions into a single place, while you have still the ability to use - the functions from :class:`~androguard.core.bytecodes.dvm.DEX` and the related classes. + the functions from :class:`~androguard.core.dex.DEX` and the related classes. - :param Optional[androguard.core.dex.DEX] vm: inital DEX object (default None) + :param Union[androguard.core.dex.DEX, None] vm: inital DEX object (default None) """ - def __init__(self, vm=None): + def __init__(self, vm: Union[dex.DEX, None]=None) -> None: # Contains DEX objects self.vms = [] # A dict of {classname: ClassAnalysis}, populated on add(vm) @@ -1427,11 +1447,11 @@ class Analysis: self.__created_xrefs = False @property - def fields(self): - """Returns FieldAnalysis list""" + def fields(self) -> Iterator[FieldAnalysis]: + """Returns FieldAnalysis generator""" return self.get_fields() - def add(self, vm): + def add(self, vm: dex.DEX) -> None: """ Add a DEX to this Analysis. @@ -1478,7 +1498,7 @@ class Analysis: logger.info("Added DEX in the analysis took : {:0d}min {:02d}s".format(*divmod(int(time.time() - tic), 60))) - def create_xref(self): + def create_xref(self) -> None: """ Create Class, Method, String and Field crossreferences for all classes in the Analysis. @@ -1513,7 +1533,7 @@ class Analysis: logger.info("End of creating cross references (XREF) " "run time: {:0d}min {:02d}s".format(*divmod(int(time.time() - tic), 60))) - def _create_xref(self, current_class): + def _create_xref(self, current_class: dex.ClassDefItem) -> None: """ Create the xref for `current_class` @@ -1632,7 +1652,7 @@ class Analysis: self.classes[cur_cls_name].add_field_xref_write(cur_meth, cur_cls, field_item, off) cur_meth.add_xref_write(cur_cls, field_item, off) - def get_method(self, method): + def get_method(self, method: dex.EncodedMethod) -> Union[MethodAnalysis,None]: """ Get the :class:`MethodAnalysis` object for a given :class:`EncodedMethod`. This Analysis object is used to enhance EncodedMethods. @@ -1648,7 +1668,7 @@ class Analysis: # Alias get_method_analysis = get_method - def _resolve_method(self, class_name, method_name, method_descriptor): + def _resolve_method(self, class_name: str, method_name: str, method_descriptor: list[str]) -> MethodAnalysis: """ Resolves the Method and returns MethodAnalysis. Will automatically create ExternalMethods if can not resolve and add to the ClassAnalysis etc @@ -1677,7 +1697,7 @@ class Analysis: return self.__method_hashes[m_hash] - def get_method_by_name(self, class_name, method_name, method_descriptor): + def get_method_by_name(self, class_name: str, method_name: str, method_descriptor: str) -> Union[dex.EncodedMethod,None]: """ Search for a :class:`EncodedMethod` in all classes in this analysis @@ -1685,14 +1705,14 @@ class Analysis: :param method_name: name of the method, for example 'onCreate' :param method_descriptor: descriptor, for example '(I I Ljava/lang/String)V :return: :class:`EncodedMethod` or None if method was not found - :rtype: androguard.core.bytecodes.dvm.EncodedMethod + :rtype: androguard.core.dex.EncodedMethod """ m_a = self.get_method_analysis_by_name(class_name, method_name, method_descriptor) if m_a and not m_a.is_external(): return m_a.get_method() return None - def get_method_analysis_by_name(self, class_name, method_name, method_descriptor): + def get_method_analysis_by_name(self, class_name: str, method_name: str, method_descriptor: str) -> Union[MethodAnalysis,None]: """ Returns the crossreferencing object for a given method. @@ -1710,11 +1730,11 @@ class Analysis: return None return self.__method_hashes[m_hash] - def get_field_analysis(self, field): + def get_field_analysis(self, field: dex.EncodedField) -> Union[FieldAnalysis,None]: """ Get the FieldAnalysis for a given fieldname - :param androguard.core.bytecodes.dvm.EncodedField field: the field + :param androguard.core.dex.EncodedField field: the field :return: :class:`FieldAnalysis` :rtype: FieldAnalysis """ @@ -1723,7 +1743,7 @@ class Analysis: return class_analysis.get_field_analysis(field) return None - def is_class_present(self, class_name): + def is_class_present(self, class_name: str) -> bool: """ Checks if a given class name is part of this Analysis. @@ -1733,7 +1753,7 @@ class Analysis: """ return class_name in self.classes - def get_class_analysis(self, class_name): + def get_class_analysis(self, class_name: str) -> ClassAnalysis: """ Returns the :class:`ClassAnalysis` object for a given classname. @@ -1743,7 +1763,7 @@ class Analysis: """ return self.classes.get(class_name) - def get_external_classes(self): + def get_external_classes(self) -> Iterator[ClassAnalysis]: """ Returns all external classes, that means all classes that are not defined in the given set of `DalvikVMObjects`. @@ -1754,7 +1774,7 @@ class Analysis: if cls.is_external(): yield cls - def get_internal_classes(self): + def get_internal_classes(self) -> Iterator[ClassAnalysis]: """ Returns all external classes, that means all classes that are defined in the given set of :class:`~DEX`. @@ -1765,7 +1785,7 @@ class Analysis: if not cls.is_external(): yield cls - def get_internal_methods(self): + def get_internal_methods(self) -> Iterator[MethodAnalysis]: """ Returns all internal methods, that means all methods that are defined in the given set of :class:`~DEX`. @@ -1776,7 +1796,7 @@ class Analysis: if not m.is_external(): yield m - def get_external_methods(self): + def get_external_methods(self) -> Iterator[MethodAnalysis]: """ Returns all external methods, that means all methods that are not defined in the given set of :class:`~DEX`. @@ -1787,7 +1807,7 @@ class Analysis: if m.is_external(): yield m - def get_strings_analysis(self): + def get_strings_analysis(self) -> dict[str,StringAnalysis]: """ Returns a dictionary of strings and their corresponding :class:`StringAnalysis` @@ -1795,7 +1815,7 @@ class Analysis: """ return self.strings - def get_strings(self): + def get_strings(self) -> list[StringAnalysis]: """ Returns a list of :class:`StringAnalysis` objects @@ -1803,7 +1823,7 @@ class Analysis: """ return self.strings.values() - def get_classes(self): + def get_classes(self) -> list[ClassAnalysis]: """ Returns a list of :class:`ClassAnalysis` objects @@ -1813,18 +1833,18 @@ class Analysis: """ return self.classes.values() - def get_methods(self): + def get_methods(self) -> Iterator[MethodAnalysis]: """ - Returns a list of `MethodAnalysis` objects + Returns a generator of `MethodAnalysis` objects :rtype: Iterator[MethodAnalysis] """ yield from self.methods.values() - def get_fields(self): + def get_fields(self) -> Iterator[FieldAnalysis]: """ - Returns a list of `FieldAnalysis` objects + Returns a generator of `FieldAnalysis` objects :rtype: Iterator[FieldAnalysis] """ @@ -1832,7 +1852,7 @@ class Analysis: for f in c.get_fields(): yield f - def find_classes(self, name=".*", no_external=False): + def find_classes(self, name:str = ".*", no_external:bool = False) -> Iterator[ClassAnalysis]: """ Find classes by name, using regular expression This method will return all ClassAnalysis Object that match the name of @@ -1848,8 +1868,13 @@ class Analysis: if re.match(name, cname): yield c - def find_methods(self, classname=".*", methodname=".*", descriptor=".*", - accessflags=".*", no_external=False): + def find_methods( + self, + classname:str=".*", + methodname:str=".*", + descriptor:str=".*", + accessflags:str=".*", + no_external:bool=False) -> Iterator[MethodAnalysis]: """ Find a method by name using regular expression. This method will return all MethodAnalysis objects, which match the @@ -1880,7 +1905,7 @@ class Analysis: re.match(accessflags, z.get_access_flags_string()): yield m - def find_strings(self, string=".*"): + def find_strings(self, string:str=".*") -> Iterator[StringAnalysis]: """ Find strings by regex @@ -1891,7 +1916,12 @@ class Analysis: if re.match(string, s): yield sa - def find_fields(self, classname=".*", fieldname=".*", fieldtype=".*", accessflags=".*"): + def find_fields( + self, + classname:str=".*", + fieldname:str=".*", + fieldtype:str=".*", + accessflags:str=".*") -> Iterator[FieldAnalysis]: """ find fields by regex @@ -1915,12 +1945,12 @@ class Analysis: def get_call_graph( self, - classname=".*", - methodname=".*", - descriptor=".*", - accessflags=".*", - no_isolated=False, - entry_points=[]): + classname:str=".*", + methodname:str=".*", + descriptor:str=".*", + accessflags:str=".*", + no_isolated:bool=False, + entry_points:list=[]) -> nx.DiGraph: """ Generate a directed graph based on the methods found by the filters applied. The filters are the same as in @@ -1995,7 +2025,7 @@ class Analysis: return CG - def create_ipython_exports(self): + def create_ipython_exports(self) -> None: """ .. warning:: this feature is experimental and is currently not enabled by default! Use with caution! @@ -2038,11 +2068,11 @@ class Analysis: logger.warning("already existing field: {} at class {}".format(mname, name)) setattr(cls, mname, field) - def get_permissions(self, apilevel=None): + def get_permissions(self, apilevel:Union[str,int,None]=None) -> Iterator[MethodAnalysis, list[str]]: """ Returns the permissions and the API method based on the API level specified. This can be used to find usage of API methods which require a permission. - Should be used in combination with an :class:`~androguard.core.bytecodes.apk.APK`. + Should be used in combination with an :class:`~androguard.core.apk.APK`. The returned permissions are a list, as some API methods require multiple permissions at once. @@ -2083,7 +2113,7 @@ class Analysis: if meth.permission_api_name in permmap: yield meth_analysis, permmap[meth.permission_api_name] - def get_permission_usage(self, permission, apilevel=None): + def get_permission_usage(self, permission:str, apilevel:Union[str,int,None]=None) -> Iterator[MethodAnalysis]: """ Find the usage of a permission inside the Analysis. @@ -2122,7 +2152,7 @@ class Analysis: if meth.permission_api_name in apis: yield meth_analysis - def get_android_api_usage(self): + def get_android_api_usage(self) -> Iterator[MethodAnalysis]: """ Get all usage of the Android APIs inside the Analysis. @@ -2135,12 +2165,12 @@ class Analysis: yield meth_analysis -def is_ascii_obfuscation(vm): +def is_ascii_obfuscation(vm: dex.DEX) -> bool: """ Tests if any class inside a DalvikVMObject uses ASCII Obfuscation (e.g. UTF-8 Chars in Classnames) - :param androguard.core.bytecodes.dvm.DEX vm: `DalvikVMObject` + :param androguard.core.dex.DEX vm: `DalvikVMObject` :return: True if ascii obfuscation otherwise False :rtype: bool """ diff --git a/androguard/core/androconf.py b/androguard/core/androconf.py index f1cbda4c..9c8f2eed 100644 --- a/androguard/core/androconf.py +++ b/androguard/core/androconf.py @@ -1,14 +1,20 @@ -import sys +# Allows type hinting of types not-yet-declared +# in Python >= 3.7 +# see https://peps.python.org/pep-0563/ +from __future__ import annotations + + import os -import logging +import sys import tempfile -from colorama import init, Fore -from loguru import logger +from typing import Union from androguard import __version__ from androguard.core.api_specific_resources import load_permission_mappings, load_permissions ANDROGUARD_VERSION = __version__ +from colorama import init, Fore +from loguru import logger # initialize colorama, only has an effect on windows init() @@ -21,7 +27,7 @@ class InvalidResourceError(Exception): pass -def is_ascii_problem(s): +def is_ascii_problem(s: str) -> bool: """ Test if a string contains other chars than ASCII @@ -85,7 +91,7 @@ default_conf = { class Configuration: instance = None - def __init__(self): + def __init__(self) -> None: """ A Wrapper for the CONF object This creates a singleton, which has the same attributes everywhere. @@ -112,7 +118,7 @@ class Configuration: CONF = Configuration() -def is_android(filename): +def is_android(filename: str) -> str: """ Return the type of the file @@ -127,7 +133,7 @@ def is_android(filename): return is_android_raw(f_bytes) -def is_android_raw(raw): +def is_android_raw(raw: bytes) -> str: """ Returns a string that describes the type of file, for common Android specific formats @@ -155,7 +161,7 @@ def is_android_raw(raw): return val -def rrmdir(directory): +def rrmdir(directory: str) -> None: """ Recursivly delete a directory @@ -169,7 +175,7 @@ def rrmdir(directory): os.rmdir(directory) -def make_color_tuple(color): +def make_color_tuple(color: str) -> tuple[int,int,int]: """ turn something like "#000000" into 0,0,0 or "#FFFFFF into "255,255,255" @@ -185,7 +191,7 @@ def make_color_tuple(color): return R, G, B -def interpolate_tuple(startcolor, goalcolor, steps): +def interpolate_tuple(startcolor: tuple[int,int,int], goalcolor: tuple[int,int,int], steps: int) -> list[str]: """ Take two RGB color sets and mix them over a specified number of steps. Return the list """ @@ -228,7 +234,7 @@ def interpolate_tuple(startcolor, goalcolor, steps): return buffer -def color_range(startcolor, goalcolor, steps): +def color_range(startcolor: tuple[int,int,int], goalcolor: tuple[int,int,int], steps: int) -> list[str]: """ wrapper for interpolate_tuple that accepts colors as html ("#CCCCC" and such) """ @@ -238,7 +244,7 @@ def color_range(startcolor, goalcolor, steps): return interpolate_tuple(start_tuple, goal_tuple, steps) -def load_api_specific_resource_module(resource_name, api=None): +def load_api_specific_resource_module(resource_name: str, api:Union[str,int,None]=None) -> dict: """ Load the module from the JSON files and return a dict, which might be empty if the resource could not be loaded. diff --git a/androguard/core/api_specific_resources/__init__.py b/androguard/core/api_specific_resources/__init__.py index e0820cac..8daa76cb 100644 --- a/androguard/core/api_specific_resources/__init__.py +++ b/androguard/core/api_specific_resources/__init__.py @@ -1,6 +1,8 @@ + import json import os import re +from typing import Union from loguru import logger @@ -8,8 +10,7 @@ from loguru import logger class APILevelNotFoundError(Exception): pass - -def load_permissions(apilevel, permtype='permissions'): +def load_permissions(apilevel:Union[str,int], permtype:str='permissions') -> dict[str, dict[str,str]]: """ Load the Permissions for the given apilevel. @@ -64,7 +65,7 @@ def load_permissions(apilevel, permtype='permissions'): return json.load(fp)[permtype] -def load_permission_mappings(apilevel): +def load_permission_mappings(apilevel:Union[str,int]) -> dict[str, list[str]]: """ Load the API/Permission mapping for the requested API level. If the requetsed level was not found, None is returned. diff --git a/androguard/core/apk/__init__.py b/androguard/core/apk/__init__.py index db4c089c..4d61e8d1 100644 --- a/androguard/core/apk/__init__.py +++ b/androguard/core/apk/__init__.py @@ -1,34 +1,46 @@ +# Allows type hinting of types not-yet-declared +# in Python >= 3.7 +# see https://peps.python.org/pep-0563/ +from __future__ import annotations + +# Python core +import binascii +import hashlib +import io +import os +import re +from struct import unpack +from typing import Iterator, Union +import zipfile +from zlib import crc32 + # Androguard from androguard.core import androconf from androguard.util import get_certificate_name_string from apkInspector.headers import ZipEntry -from androguard.core.axml import ARSCParser, AXMLPrinter, ARSCResTableConfig, AXMLParser, format_value, START_TAG, END_TAG, TEXT, END_DOCUMENT - -# Python core -import io -from zlib import crc32 -import os -import re -import binascii -import zipfile -import logging -from struct import unpack -import hashlib -import warnings +from androguard.core.axml import (ARSCParser, + AXMLPrinter, + ARSCResTableConfig, + AXMLParser, + format_value, + START_TAG, + END_TAG, + TEXT, + END_DOCUMENT) # External dependecies +from lxml.etree import Element import lxml.sax from xml.dom.pulldom import SAX2DOM # Used for reading Certificates from asn1crypto import cms, x509, keys + from loguru import logger NS_ANDROID_URI = 'http://schemas.android.com/apk/res/android' NS_ANDROID = '{{{}}}'.format(NS_ANDROID_URI) # Namespace as used by etree - - def parse_lxml_dom(tree): handler = SAX2DOM() lxml.sax.saxify(tree, handler) @@ -89,7 +101,7 @@ class APKV2SignedData: source : https://source.android.com/security/apksigning/v2.html """ - def __init__(self): + def __init__(self) -> None: self._bytes = None self.digests = None self.certificates = None @@ -125,7 +137,7 @@ class APKV3SignedData(APKV2SignedData): source : https://source.android.com/security/apksigning/v3.html """ - def __init__(self): + def __init__(self) -> None: super().__init__() self.minSDK = None self.maxSDK = None @@ -152,7 +164,7 @@ class APKV2Signer: source : https://source.android.com/security/apksigning/v2.html """ - def __init__(self): + def __init__(self) -> None: self._bytes = None self.signed_data = None self.signatures = None @@ -172,7 +184,7 @@ class APKV3Signer(APKV2Signer): source : https://source.android.com/security/apksigning/v3.html """ - def __init__(self): + def __init__(self) -> None: super().__init__() self.minSDK = None self.maxSDK = None @@ -216,7 +228,7 @@ class APK: __no_magic = False - def __init__(self, filename, raw=False, magic_file=None, skip_analysis=False, testzip=False): + def __init__(self, filename:str, raw:bool=False, magic_file:Union[str,None]=None, skip_analysis:bool=False, testzip:bool=False) -> None: """ This class can access to all elements in an APK file @@ -428,7 +440,7 @@ class APK: pass return maxSdkVersion - def is_valid_APK(self): + def is_valid_APK(self) -> bool: """ Return true if the APK is valid, false otherwise. An APK is seen as valid, if the AndroidManifest.xml could be successful parsed. @@ -439,7 +451,7 @@ class APK: """ return self.valid_apk - def get_filename(self): + def get_filename(self) -> str: """ Return the filename of the APK @@ -447,7 +459,7 @@ class APK: """ return self.filename - def get_app_name(self): + def get_app_name(self) -> str: """ Return the appname of the APK @@ -507,7 +519,7 @@ class APK: logger.warning("Exception selecting app name: %s" % e) return app_name - def get_app_icon(self, max_dpi=65536): + def get_app_icon(self, max_dpi:int=65536) -> Union[str,None]: """ Return the first icon file name, which density is not greater than max_dpi, unless exact icon resolution is set in the manifest, in which case @@ -595,7 +607,7 @@ class APK: return app_icon - def get_package(self): + def get_package(self) -> str: """ Return the name of the package @@ -605,7 +617,7 @@ class APK: """ return self.package - def get_androidversion_code(self): + def get_androidversion_code(self) -> str: """ Return the android version code @@ -615,7 +627,7 @@ class APK: """ return self.androidversion["Code"] - def get_androidversion_name(self): + def get_androidversion_name(self) -> str: """ Return the android version name @@ -625,7 +637,7 @@ class APK: """ return self.androidversion["Name"] - def get_files(self): + def get_files(self) -> list[str]: """ Return the file names inside the APK. @@ -633,7 +645,7 @@ class APK: """ return self.zip.namelist() - def _get_file_magic_name(self, buffer): + def _get_file_magic_name(self, buffer: bytes) -> str: """ Return the filetype guessed for a buffer :param buffer: bytes @@ -685,7 +697,7 @@ class APK: return self._patch_magic(buffer, ftype) @property - def files(self): + def files(self) -> dict[str, str]: """ Returns a dictionary of filenames and detected magic type @@ -693,7 +705,7 @@ class APK: """ return self.get_files_types() - def get_files_types(self): + def get_files_types(self) -> dict[str,str]: """ Return the files inside the APK with their associated types (by using python-magic) @@ -741,7 +753,7 @@ class APK: self.files_crc32[filename])) return buffer - def get_files_crc32(self): + def get_files_crc32(self) -> dict[str, int]: """ Calculates and returns a dictionary of filenames and CRC32 @@ -753,7 +765,7 @@ class APK: return self.files_crc32 - def get_files_information(self): + def get_files_information(self) -> Iterator[tuple[str, str, int]]: """ Return the files inside the APK with their associated types and crc32 @@ -762,7 +774,7 @@ class APK: for k in self.get_files(): yield k, self.get_files_types()[k], self.get_files_crc32()[k] - def get_raw(self): + def get_raw(self) -> bytes: """ Return raw bytes of the APK @@ -776,7 +788,7 @@ class APK: self.__raw = bytearray(f.read()) return self.__raw - def get_file(self, filename): + def get_file(self, filename:str) -> bytes: """ Return the raw data of the specified filename inside the APK @@ -788,7 +800,7 @@ class APK: except KeyError: raise FileNotPresent(filename) - def get_dex(self): + def get_dex(self) -> bytes: """ Return the raw data of the classes dex file @@ -803,7 +815,7 @@ class APK: # TODO is this a good idea to return an empty string? return b"" - def get_dex_names(self): + def get_dex_names(self) -> list[str]: """ Return the names of all DEX files found in the APK. This method only accounts for "offical" dex files, i.e. all files @@ -814,7 +826,7 @@ class APK: dexre = re.compile(r"^classes(\d*).dex$") return filter(lambda x: dexre.match(x), self.get_files()) - def get_all_dex(self): + def get_all_dex(self) -> Iterator[bytes]: """ Return the raw data of all classes dex files @@ -823,7 +835,7 @@ class APK: for dex_name in self.get_dex_names(): yield self.get_file(dex_name) - def is_multidex(self): + def is_multidex(self) -> bool: """ Test if the APK has multiple DEX files @@ -854,8 +866,8 @@ class APK: return value def get_all_attribute_value( - self, tag_name, attribute, format_value=True, **attribute_filter - ): + self, tag_name: str, attribute: str, format_value:bool=True, **attribute_filter + ) -> Iterator[str]: """ Yields all the attribute values in xml files which match with the tag name and the specific attribute @@ -873,8 +885,8 @@ class APK: yield value def get_attribute_value( - self, tag_name, attribute, format_value=False, **attribute_filter - ): + self, tag_name:str, attribute:str, format_value:bool=False, **attribute_filter + ) -> str: """ Return the attribute value in xml files which matches the tag name and the specific attribute @@ -888,7 +900,7 @@ class APK: if value is not None: return value - def get_value_from_tag(self, tag, attribute): + def get_value_from_tag(self, tag: Element, attribute: str) -> Union[str,None]: """ Return the value of the android prefixed attribute in a specific tag. @@ -932,7 +944,7 @@ class APK: "But found the same attribute without namespace!".format(attribute, tag.tag)) return value - def find_tags(self, tag_name, **attribute_filter): + def find_tags(self, tag_name: str, **attribute_filter) -> list[str]: """ Return a list of all the matched tags in all available xml @@ -947,8 +959,8 @@ class APK: return [tag for tag_list in all_tags for tag in tag_list] def find_tags_from_xml( - self, xml_name, tag_name, **attribute_filter - ): + self, xml_name: str, tag_name: str, **attribute_filter + ) -> list[str]: """ Return a list of all the matched tags in a specific xml w @@ -971,7 +983,7 @@ class APK: ) ] - def is_tag_matched(self, tag, **attribute_filter): + def is_tag_matched(self, tag: str, **attribute_filter) -> bool: r""" Return true if the attributes matches in attribute filter. @@ -998,7 +1010,7 @@ class APK: return False return True - def get_main_activities(self): + def get_main_activities(self) -> set[str]: """ Return names of the main activities @@ -1042,7 +1054,7 @@ class APK: return x.intersection(y) - def get_main_activity(self): + def get_main_activity(self) -> Union[str,None]: """ Return the name of the main activity @@ -1064,7 +1076,7 @@ class APK: return sorted(main_activities)[0] return None - def get_activities(self): + def get_activities(self) -> list[str]: """ Return the android:name attribute of all activities @@ -1072,7 +1084,7 @@ class APK: """ return list(self.get_all_attribute_value("activity", "name")) - def get_activity_aliases(self): + def get_activity_aliases(self) -> list[dict[str,str]]: """ Return the android:name and android:targetActivity attribute of all activity aliases. @@ -1091,7 +1103,7 @@ class APK: ali.append(activity_alias) return ali - def get_services(self): + def get_services(self) -> list[str]: """ Return the android:name attribute of all services @@ -1099,7 +1111,7 @@ class APK: """ return list(self.get_all_attribute_value("service", "name")) - def get_receivers(self): + def get_receivers(self) -> list[str]: """ Return the android:name attribute of all receivers @@ -1107,7 +1119,7 @@ class APK: """ return list(self.get_all_attribute_value("receiver", "name")) - def get_providers(self): + def get_providers(self) -> list[str]: """ Return the android:name attribute of all providers @@ -1115,7 +1127,7 @@ class APK: """ return list(self.get_all_attribute_value("provider", "name")) - def get_res_value(self, name): + def get_res_value(self, name:str) -> str: """ Return the literal value with a resource id :rtype: str @@ -1136,7 +1148,7 @@ class APK: return value - def get_intent_filters(self, itemtype, name): + def get_intent_filters(self, itemtype:str, name:str) -> dict[str,list[str]]: """ Find intent filters for a given item and name. @@ -1187,7 +1199,7 @@ class APK: return d - def get_permissions(self): + def get_permissions(self) -> list[str]: """ Return permissions names declared in the AndroidManifest.xml. @@ -1204,7 +1216,7 @@ class APK: """ return self.permissions - def get_uses_implied_permission_list(self): + def get_uses_implied_permission_list(self) -> list[str]: """ Return all permissions implied by the target SDK or other permissions. @@ -1274,7 +1286,7 @@ class APK: label, description] return filled_permissions - def get_details_permissions(self): + def get_details_permissions(self) -> dict[str, list[str]]: """ Return permissions with details. @@ -1295,7 +1307,7 @@ class APK: "Unknown permission from android reference"] return self._fill_deprecated_permissions(l) - def get_requested_aosp_permissions(self): + def get_requested_aosp_permissions(self) -> list[str]: """ Returns requested permissions declared within AOSP project. @@ -1310,7 +1322,7 @@ class APK: aosp_permissions.append(perm) return aosp_permissions - def get_requested_aosp_permissions_details(self): + def get_requested_aosp_permissions_details(self) -> dict[str, list[str]]: """ Returns requested aosp permissions with details. @@ -1325,7 +1337,7 @@ class APK: continue return l - def get_requested_third_party_permissions(self): + def get_requested_third_party_permissions(self) -> list[str]: """ Returns list of requested permissions not declared within AOSP project. @@ -1338,7 +1350,7 @@ class APK: third_party_permissions.append(perm) return third_party_permissions - def get_declared_permissions(self): + def get_declared_permissions(self) -> list[str]: """ Returns list of the declared permissions. @@ -1346,7 +1358,7 @@ class APK: """ return list(self.declared_permissions.keys()) - def get_declared_permissions_details(self): + def get_declared_permissions_details(self) -> dict[str, list[str]]: """ Returns declared permissions with the details. @@ -1354,7 +1366,7 @@ class APK: """ return self.declared_permissions - def get_max_sdk_version(self): + def get_max_sdk_version(self) -> str: """ Return the android:maxSdkVersion attribute @@ -1362,7 +1374,7 @@ class APK: """ return self.get_attribute_value("uses-sdk", "maxSdkVersion") - def get_min_sdk_version(self): + def get_min_sdk_version(self) -> str: """ Return the android:minSdkVersion attribute @@ -1370,7 +1382,7 @@ class APK: """ return self.get_attribute_value("uses-sdk", "minSdkVersion") - def get_target_sdk_version(self): + def get_target_sdk_version(self) -> str: """ Return the android:targetSdkVersion attribute @@ -1378,7 +1390,7 @@ class APK: """ return self.get_attribute_value("uses-sdk", "targetSdkVersion") - def get_effective_target_sdk_version(self): + def get_effective_target_sdk_version(self) -> int: """ Return the effective targetSdkVersion, always returns int > 0. @@ -1396,7 +1408,7 @@ class APK: except (ValueError, TypeError): return 1 - def get_libraries(self): + def get_libraries(self) -> list[str]: """ Return the android:name attributes for libraries @@ -1404,7 +1416,7 @@ class APK: """ return list(self.get_all_attribute_value("uses-library", "name")) - def get_features(self): + def get_features(self) -> list[str]: """ Return a list of all android:names found for the tag uses-feature in the AndroidManifest.xml @@ -1413,7 +1425,7 @@ class APK: """ return list(self.get_all_attribute_value("uses-feature", "name")) - def is_wearable(self): + def is_wearable(self) -> bool: """ Checks if this application is build for wearables by checking if it uses the feature 'android.hardware.type.watch' @@ -1426,7 +1438,7 @@ class APK: """ return 'android.hardware.type.watch' in self.get_features() - def is_leanback(self): + def is_leanback(self) -> bool: """ Checks if this application is build for TV (Leanback support) by checkin if it uses the feature 'android.software.leanback' @@ -1435,7 +1447,7 @@ class APK: """ return 'android.software.leanback' in self.get_features() - def is_androidtv(self): + def is_androidtv(self) -> bool: """ Checks if this application does not require a touchscreen, as this is the rule to get into the TV section of the Play Store @@ -1445,7 +1457,7 @@ class APK: """ return self.get_attribute_value('uses-feature', 'name', required="false", name="android.hardware.touchscreen") == "android.hardware.touchscreen" - def get_certificate_der(self, filename): + def get_certificate_der(self, filename:str) -> bytes: """ Return the DER coded X.509 certificate from the signature file. @@ -1458,7 +1470,7 @@ class APK: cert = pkcs7obj['content']['certificates'][0].chosen.dump() return cert - def get_certificate(self, filename): + def get_certificate(self, filename:str) -> x509.Certificate: """ Return a X.509 certificate object by giving the name in the apk file @@ -1470,7 +1482,7 @@ class APK: return certificate - def new_zip(self, filename, deleted_files=None, new_files={}): + def new_zip(self, filename:str, deleted_files:Union[str,None]=None, new_files:dict={}) -> None: """ Create a new zip file @@ -1511,18 +1523,18 @@ class APK: zout.writestr(item, buffer) zout.close() - def get_android_manifest_axml(self): + def get_android_manifest_axml(self) -> Union[AXMLPrinter,None]: """ Return the :class:`AXMLPrinter` object which corresponds to the AndroidManifest.xml file - :rtype: :class:`~androguard.core.bytecodes.axml.AXMLPrinter` + :rtype: :class:`~androguard.core.axml.AXMLPrinter` """ try: return self.axml["AndroidManifest.xml"] except KeyError: return None - def get_android_manifest_xml(self): + def get_android_manifest_xml(self) -> Union[lxml.etree.Element, None]: """ Return the parsed xml object which corresponds to the AndroidManifest.xml file @@ -1533,11 +1545,11 @@ class APK: except KeyError: return None - def get_android_resources(self): + def get_android_resources(self) -> Union[ARSCParser,None]: """ - Return the :class:`~androguard.core.bytecodes.axml.ARSCParser` object which corresponds to the resources.arsc file + Return the :class:`~androguard.core.axml.ARSCParser` object which corresponds to the resources.arsc file - :rtype: :class:`~androguard.core.bytecodes.axml.ARSCParser` + :rtype: :class:`~androguard.core.axml.ARSCParser` """ try: return self.arsc["resources.arsc"] @@ -1549,13 +1561,13 @@ class APK: self.arsc["resources.arsc"] = ARSCParser(self.zip.read("resources.arsc")) return self.arsc["resources.arsc"] - def is_signed(self): + def is_signed(self) -> bool: """ Returns true if any of v1, v2, or v3 signatures were found. """ return self.is_signed_v1() or self.is_signed_v2() or self.is_signed_v3() - def is_signed_v1(self): + def is_signed_v1(self) -> bool: """ Returns true if a v1 / JAR signature was found. @@ -1564,7 +1576,7 @@ class APK: """ return self.get_signature_name() is not None - def is_signed_v2(self): + def is_signed_v2(self) -> bool: """ Returns true of a v2 / APK signature was found. @@ -1576,7 +1588,7 @@ class APK: return self._is_signed_v2 - def is_signed_v3(self): + def is_signed_v3(self) -> bool: """ Returns true of a v3 / APK signature was found. @@ -1588,11 +1600,11 @@ class APK: return self._is_signed_v3 - def read_uint32_le(self, io_stream): + def read_uint32_le(self, io_stream) -> int: value, = unpack(' list[tuple[int, bytes]]: """ Parse digests """ if not len(digest_bytes): @@ -1612,7 +1624,7 @@ class APK: return digests - def parse_v2_v3_signature(self): + def parse_v2_v3_signature(self) -> None: # Need to find an v2 Block in the APK. # The Google Docs gives you the following rule: # * go to the end of the ZIP File @@ -1694,7 +1706,7 @@ class APK: self._is_signed_v3 = True - def parse_v3_signing_block(self): + def parse_v3_signing_block(self) -> None: """ Parse the V2 signing block and extract all features """ @@ -1793,7 +1805,7 @@ class APK: self._v3_signing_data.append(signer) - def parse_v2_signing_block(self): + def parse_v2_signing_block(self) -> None: """ Parse the V2 signing block and extract all features """ @@ -1875,7 +1887,7 @@ class APK: self._v2_signing_data.append(signer) - def get_public_keys_der_v3(self): + def get_public_keys_der_v3(self) -> list[bytes]: """ Return a list of DER coded X.509 public keys from the v3 signature block """ @@ -1890,7 +1902,7 @@ class APK: return public_keys - def get_public_keys_der_v2(self): + def get_public_keys_der_v2(self) -> list[bytes]: """ Return a list of DER coded X.509 public keys from the v3 signature block """ @@ -1905,7 +1917,7 @@ class APK: return public_keys - def get_certificates_der_v3(self): + def get_certificates_der_v3(self) -> list[bytes]: """ Return a list of DER coded X.509 certificates from the v3 signature block """ @@ -1920,7 +1932,7 @@ class APK: return certs - def get_certificates_der_v2(self): + def get_certificates_der_v2(self) -> list[bytes]: """ Return a list of DER coded X.509 certificates from the v3 signature block """ @@ -1935,21 +1947,21 @@ class APK: return certs - def get_public_keys_v3(self): + def get_public_keys_v3(self) -> list[keys.PublicKeyInfo]: """ Return a list of :class:`asn1crypto.keys.PublicKeyInfo` which are found in the v3 signing block. """ return [ keys.PublicKeyInfo.load(pkey) for pkey in self.get_public_keys_der_v3()] - def get_public_keys_v2(self): + def get_public_keys_v2(self) -> list[keys.PublicKeyInfo]: """ Return a list of :class:`asn1crypto.keys.PublicKeyInfo` which are found in the v2 signing block. """ return [ keys.PublicKeyInfo.load(pkey) for pkey in self.get_public_keys_der_v2()] - def get_certificates_v3(self): + def get_certificates_v3(self) -> list[x509.Certificate]: """ Return a list of :class:`asn1crypto.x509.Certificate` which are found in the v3 signing block. @@ -1958,7 +1970,7 @@ class APK: """ return [ x509.Certificate.load(cert) for cert in self.get_certificates_der_v3()] - def get_certificates_v2(self): + def get_certificates_v2(self) -> list[x509.Certificate]: """ Return a list of :class:`asn1crypto.x509.Certificate` which are found in the v2 signing block. @@ -1967,7 +1979,7 @@ class APK: """ return [ x509.Certificate.load(cert) for cert in self.get_certificates_der_v2()] - def get_certificates_v1(self): + def get_certificates_v1(self) -> list[x509.Certificate]: """ Return a list of :class:`asn1crypto.x509.Certificate` which are found in the META-INF folder (v1 signing). @@ -1980,7 +1992,7 @@ class APK: return certs - def get_certificates(self): + def get_certificates(self) -> list[x509.Certificate]: """ Return a list of unique :class:`asn1crypto.x509.Certificate` which are found in v1, v2 and v3 signing @@ -1995,7 +2007,7 @@ class APK: certs.append(x) return certs - def get_signature_name(self): + def get_signature_name(self) -> Union[str,None]: """ Return the name of the first signature file found. """ @@ -2005,7 +2017,7 @@ class APK: # Unsigned APK return None - def get_signature_names(self): + def get_signature_names(self) -> list[str]: """ Return a list of the signature file names (v1 Signature / JAR Signature) @@ -2024,7 +2036,7 @@ class APK: return signatures - def get_signature(self): + def get_signature(self) -> Union[str,None]: """ Return the data of the first signature file found (v1 Signature / JAR Signature) @@ -2036,7 +2048,7 @@ class APK: else: return None - def get_signatures(self): + def get_signatures(self) -> list[bytes]: """ Return a list of the data of the signature files. Only v1 / JAR Signing. @@ -2052,7 +2064,7 @@ class APK: return signature_datas - def show(self): + def show(self) -> None: self.get_files_types() print("FILES: ") @@ -2105,7 +2117,7 @@ class APK: show_Certificate(c) -def show_Certificate(cert, short=False): +def show_Certificate(cert, short:bool=False) -> None: """ Print Fingerprints, Issuer and Subject of an X509 Certificate. @@ -2121,7 +2133,7 @@ def show_Certificate(cert, short=False): print("Subject: {}".format(get_certificate_name_string(cert.subject.native, short=short))) -def ensure_final_value(packageName, arsc, value): +def ensure_final_value(packageName:str, arsc:ARSCParser, value:str) -> str: """Ensure incoming value is always the value, not the resid androguard will sometimes return the Android "resId" aka @@ -2144,7 +2156,7 @@ def ensure_final_value(packageName, arsc, value): return '' -def get_apkid(apkfile): +def get_apkid(apkfile: str) -> tuple[str,str,str]: """Read (appid, versionCode, versionName) from an APK This first tries to do quick binary XML parsing to just get the diff --git a/androguard/core/axml/__init__.py b/androguard/core/axml/__init__.py index 94b0143e..95ab6e09 100644 --- a/androguard/core/axml/__init__.py +++ b/androguard/core/axml/__init__.py @@ -1,16 +1,22 @@ +# Allows type hinting of types not-yet-declared +# in Python >= 3.7 +# see https://peps.python.org/pep-0563/ +from __future__ import annotations + +import binascii +from collections import defaultdict +import collections +import io +import re +from struct import pack, unpack +from typing import BinaryIO, Union + from androguard.core.resources import public from .types import * -from struct import pack, unpack -from xml.sax.saxutils import escape -import collections -from collections import defaultdict - -from lxml import etree from loguru import logger -import re -import binascii -import io +from lxml import etree +from xml.sax.saxutils import escape # Constants for ARSC Files # see http://aospxref.com/android-13.0.0_r3/xref/frameworks/base/libs/androidfw/include/androidfw/ResourceTypes.h#233 @@ -85,7 +91,7 @@ class ResParserError(Exception): pass -def complexToFloat(xcomplex): +def complexToFloat(xcomplex) -> float: """ Convert a complex unit into float """ @@ -99,7 +105,7 @@ class StringBlock: See http://androidxref.com/9.0.0_r3/xref/frameworks/base/libs/androidfw/include/androidfw/ResourceTypes.h#436 """ - def __init__(self, buff, header): + def __init__(self, buff:BinaryIO, header:ARSCHeader) -> None: """ :param buff: buffer which holds the string block :param header: a instance of :class:`~ARSCHeader` @@ -191,7 +197,7 @@ class StringBlock: for i in range(self.stringCount): yield self.getString(i) - def getString(self, idx): + def getString(self, idx: int) -> str: """ Return the string at the index in the string table @@ -213,7 +219,7 @@ class StringBlock: return self._cache[idx] - def getStyle(self, idx): + def getStyle(self, idx:int) -> int: """ Return the style associated with the index @@ -222,7 +228,7 @@ class StringBlock: """ return self.m_styles[idx] - def _decode8(self, offset): + def _decode8(self, offset:int) -> str: """ Decode an UTF-8 String at the given offset @@ -245,7 +251,7 @@ class StringBlock: return self._decode_bytes(data, 'utf-8', str_len) - def _decode16(self, offset): + def _decode16(self, offset:int) -> str: """ Decode an UTF-16 String at the given offset @@ -266,7 +272,7 @@ class StringBlock: return self._decode_bytes(data, 'utf-16', str_len) @staticmethod - def _decode_bytes(data, encoding, str_len): + def _decode_bytes(data:bytes, encoding:str, str_len:int) -> str: """ Generic decoding with length check. The string is decoded from bytes with the given encoding, then the length @@ -283,7 +289,7 @@ class StringBlock: logger.warning("invalid decoded string length") return string - def _decode_length(self, offset, sizeof_char): + def _decode_length(self, offset:int, sizeof_char:int) -> tuple[int,int]: """ Generic Length Decoding at offset of string @@ -320,7 +326,7 @@ class StringBlock: return length, size - def show(self): + def show(self) -> None: """ Print some information on stdout about the string table """ @@ -371,7 +377,7 @@ class AXMLParser: See http://androidxref.com/9.0.0_r3/xref/frameworks/base/libs/androidfw/include/androidfw/ResourceTypes.h#563 """ - def __init__(self, raw_buff): + def __init__(self, raw_buff:bytes) -> None: logger.debug("AXMLParser") self._reset() @@ -458,7 +464,7 @@ class AXMLParser: # Store a list of prefix/uri mappings encountered self.namespaces = [] - def is_valid(self): + def is_valid(self) -> bool: """ Get the state of the AXMLPrinter. if an error happend somewhere in the process of parsing the file, @@ -671,7 +677,7 @@ class AXMLParser: self.buff.seek(h.end) @property - def name(self): + def name(self) -> str: """ Return the String assosciated with the tag name """ @@ -681,7 +687,7 @@ class AXMLParser: return self.sb[self.m_name] @property - def comment(self): + def comment(self) -> Union[str,None]: """ Return the comment at the current position or None if no comment is given @@ -694,7 +700,7 @@ class AXMLParser: return self.sb[self.m_comment_index] @property - def namespace(self): + def namespace(self) -> str: """ Return the Namespace URI (if any) as a String for the current tag """ @@ -708,7 +714,7 @@ class AXMLParser: return self.sb[self.m_namespaceUri] @property - def nsmap(self): + def nsmap(self) -> dict[str,str]: """ Returns the current namespace mapping as a dictionary @@ -734,7 +740,7 @@ class AXMLParser: return NSMAP @property - def text(self): + def text(self) -> str: """ Return the String assosicated with the current text """ @@ -743,21 +749,21 @@ class AXMLParser: return self.sb[self.m_name] - def getName(self): + def getName(self) -> str: """ Legacy only! use :py:attr:`~androguard.core.bytecodes.AXMLParser.name` instead """ return self.name - def getText(self): + def getText(self) -> str: """ Legacy only! use :py:attr:`~androguard.core.bytecodes.AXMLParser.text` instead """ return self.text - def getPrefix(self): + def getPrefix(self) -> str: """ Legacy only! use :py:attr:`~androguard.core.bytecodes.AXMLParser.namespace` instead @@ -777,7 +783,7 @@ class AXMLParser: return offset - def getAttributeCount(self): + def getAttributeCount(self) -> int: """ Return the number of Attributes for a Tag or -1 if not in a tag @@ -787,7 +793,7 @@ class AXMLParser: return self.m_attribute_count - def getAttributeUri(self, index): + def getAttributeUri(self, index:int): """ Returns the numeric ID for the namespace URI of an attribute """ @@ -798,7 +804,7 @@ class AXMLParser: return uri - def getAttributeNamespace(self, index): + def getAttributeNamespace(self, index:int): """ Return the Namespace URI (if any) for the attribute """ @@ -812,7 +818,7 @@ class AXMLParser: return self.sb[uri] - def getAttributeName(self, index): + def getAttributeName(self, index:int): """ Returns the String which represents the attribute name """ @@ -836,7 +842,7 @@ class AXMLParser: res = 'android:UNKNOWN_SYSTEM_ATTRIBUTE_{:08x}'.format(attr) return res - def getAttributeValueType(self, index): + def getAttributeValueType(self, index:int): """ Return the type of the attribute at the given index @@ -847,7 +853,7 @@ class AXMLParser: offset = self._get_attribute_offset(index) return self.m_attributes[offset + ATTRIBUTE_IX_VALUE_TYPE] - def getAttributeValueData(self, index): + def getAttributeValueData(self, index:int): """ Return the data of the attribute at the given index @@ -877,7 +883,7 @@ class AXMLParser: return '' -def format_value(_type, _data, lookup_string=lambda ix: ""): +def format_value(_type: int, _data: int, lookup_string=lambda ix: "") -> str: """ Format a value based on type and data. By default, no strings are looked up and "" is returned. @@ -941,7 +947,7 @@ class AXMLPrinter: __charrange = None __replacement = None - def __init__(self, raw_buff): + def __init__(self, raw_buff: bytes)->bytes: logger.debug("AXMLPrinter") self.axml = AXMLParser(raw_buff) @@ -1008,7 +1014,7 @@ class AXMLPrinter: logger.warning("Not all namespace mappings were closed! Malformed AXML?") break - def get_buff(self): + def get_buff(self) -> bytes: """ Returns the raw XML file without prettification applied. @@ -1016,7 +1022,7 @@ class AXMLPrinter: """ return self.get_xml(pretty=False) - def get_xml(self, pretty=True): + def get_xml(self, pretty:bool=True) -> bytes: """ Get the XML as an UTF-8 string @@ -1024,7 +1030,7 @@ class AXMLPrinter: """ return etree.tostring(self.root, encoding="utf-8", pretty_print=pretty) - def get_xml_obj(self): + def get_xml_obj(self) -> etree.Element: """ Get the XML as an ElementTree object @@ -1032,7 +1038,7 @@ class AXMLPrinter: """ return self.root - def is_valid(self): + def is_valid(self) -> bool: """ Return the state of the AXMLParser. If this flag is set to False, the parsing has failed, thus @@ -1040,7 +1046,7 @@ class AXMLPrinter: """ return self.axml.is_valid() - def is_packed(self): + def is_packed(self) -> bool: """ Returns True if the AXML is likely to be packed @@ -1053,7 +1059,7 @@ class AXMLPrinter: """ return self.packerwarning or self.axml.packerwarning - def _get_attribute_value(self, index): + def _get_attribute_value(self, index: int): """ Wrapper function for format_value to resolve the actual value of an attribute in a tag :param index: index of the current attribute @@ -1357,7 +1363,7 @@ class ARSCParser: Each package is a chunk of type RES_TABLE_PACKAGE_TYPE. It contains again many more chunks. """ - def __init__(self, raw_buff): + def __init__(self, raw_buff:bytes) -> None: """ :param bytes raw_buff: the raw bytes of the file """ @@ -1586,10 +1592,10 @@ class ARSCParser: nb += 3 + nb_i - 1 # -1 to account for the nb+=1 on the next line nb += 1 - def get_resource_string(self, ate): + def get_resource_string(self, ate:ARSCResTableEntry) -> list: return [ate.get_value(), ate.get_key_data()] - def get_resource_id(self, ate): + def get_resource_id(self, ate:ARSCResTableEntry) -> list[str]: x = [ate.get_value()] if ate.key.get_data() == 0: x.append("false") @@ -1597,7 +1603,7 @@ class ARSCParser: x.append("true") return x - def get_resource_bool(self, ate): + def get_resource_bool(self, ate:ARSCResTableEntry) -> list[str]: x = [ate.get_value()] if ate.key.get_data() == 0: x.append("false") @@ -1605,10 +1611,10 @@ class ARSCParser: x.append("true") return x - def get_resource_integer(self, ate): + def get_resource_integer(self, ate:ARSCResTableEntry) -> list: return [ate.get_value(), ate.key.get_data()] - def get_resource_color(self, ate): + def get_resource_color(self, ate:ARSCResTableEntry) -> list: entry_data = ate.key.get_data() return [ ate.get_value(), @@ -1619,7 +1625,7 @@ class ARSCParser: (entry_data & 0xFF)) ] - def get_resource_dimen(self, ate): + def get_resource_dimen(self, ate:ARSCResTableEntry) -> list: try: return [ ate.get_value(), "{}{}".format( @@ -1633,17 +1639,17 @@ class ARSCParser: return [ate.get_value(), ate.key.get_data()] # FIXME - def get_resource_style(self, ate): + def get_resource_style(self, ate:ARSCResTableEntry) -> list: return ["", ""] - def get_packages_names(self): + def get_packages_names(self) -> list[str]: """ Retrieve a list of all package names, which are available in the given resources.arsc. """ return list(self.packages.keys()) - def get_locales(self, package_name): + def get_locales(self, package_name:str) -> list[str]: """ Retrieve a list of all available locales in a given packagename. @@ -1652,7 +1658,7 @@ class ARSCParser: self._analyse() return list(self.values[package_name].keys()) - def get_types(self, package_name, locale='\x00\x00'): + def get_types(self, package_name:str, locale:str='\x00\x00') -> list[str]: """ Retrieve a list of all types which are available in the given package and locale. @@ -1663,7 +1669,7 @@ class ARSCParser: self._analyse() return list(self.values[package_name][locale].keys()) - def get_public_resources(self, package_name, locale='\x00\x00'): + def get_public_resources(self, package_name:str, locale:str='\x00\x00') -> bytes: """ Get the XML (as string) of all resources of type 'public'. @@ -1689,7 +1695,7 @@ class ARSCParser: return buff.encode('utf-8') - def get_string_resources(self, package_name, locale='\x00\x00'): + def get_string_resources(self, package_name:str, locale:str='\x00\x00') -> bytes: """ Get the XML (as string) of all resources of type 'string'. @@ -1718,7 +1724,7 @@ class ARSCParser: return buff.encode('utf-8') - def get_strings_resources(self): + def get_strings_resources(self) -> bytes: """ Get the XML (as string) of all resources of type 'string'. This is a combined variant, which has all locales and all package names @@ -1751,7 +1757,7 @@ class ARSCParser: return buff.encode('utf-8') - def get_id_resources(self, package_name, locale='\x00\x00'): + def get_id_resources(self, package_name:str, locale:str='\x00\x00') -> bytes: """ Get the XML (as string) of all resources of type 'id'. @@ -1780,7 +1786,7 @@ class ARSCParser: return buff.encode('utf-8') - def get_bool_resources(self, package_name, locale='\x00\x00'): + def get_bool_resources(self, package_name:str, locale:str='\x00\x00') -> bytes: """ Get the XML (as string) of all resources of type 'bool'. @@ -1805,7 +1811,7 @@ class ARSCParser: return buff.encode('utf-8') - def get_integer_resources(self, package_name, locale='\x00\x00'): + def get_integer_resources(self, package_name:str, locale:str='\x00\x00') -> bytes: """ Get the XML (as string) of all resources of type 'integer'. @@ -1830,7 +1836,7 @@ class ARSCParser: return buff.encode('utf-8') - def get_color_resources(self, package_name, locale='\x00\x00'): + def get_color_resources(self, package_name:str, locale:str='\x00\x00') -> bytes: """ Get the XML (as string) of all resources of type 'color'. @@ -1855,7 +1861,7 @@ class ARSCParser: return buff.encode('utf-8') - def get_dimen_resources(self, package_name, locale='\x00\x00'): + def get_dimen_resources(self, package_name:str, locale:str='\x00\x00') -> bytes: """ Get the XML (as string) of all resources of type 'dimen'. @@ -1880,7 +1886,7 @@ class ARSCParser: return buff.encode('utf-8') - def get_id(self, package_name, rid, locale='\x00\x00'): + def get_id(self, package_name:str, rid:int, locale:str='\x00\x00') -> tuple: """ Returns the tuple (resource_type, resource_name, resource_id) for the given resource_id. @@ -1905,7 +1911,7 @@ class ARSCParser: Resolves resources by ID and configuration. This resolver deals with complex resources as well as with references. """ - def __init__(self, android_resources, config=None): + def __init__(self, android_resources:ARSCParser, config:Union[ARSCResTableConfig,None]=None) -> None: """ :param ARSCParser android_resources: A resource parser :param ARSCResTableConfig config: The desired configuration or None to resolve all. @@ -1913,7 +1919,7 @@ class ARSCParser: self.resources = android_resources self.wanted_config = config - def resolve(self, res_id): + def resolve(self, res_id: int) -> list[tuple[ARSCResTableConfig, str]]: """ the given ID into the Resource and returns a list of matching resources. @@ -1932,7 +1938,7 @@ class ARSCParser: # deconstruct them and check if more candidates are generated self.put_ate_value(result, ate, config) - def put_ate_value(self, result, ate, config): + def put_ate_value(self, result: list, ate:ARSCResTableEntry, config:ARSCResTableConfig) -> None: """ Put a ResTableEntry into the list of results :param list result: results array @@ -1948,7 +1954,7 @@ class ARSCParser: else: self.put_item_value(result, ate.key, config, ate, complex_=False) - def put_item_value(self, result, item, config, parent, complex_): + def put_item_value(self, result:list, item:ARSCResStringPoolRef, config:ARSCResTableConfig, parent:ARSCResTableEntry, complex_:bool)->None: """ Put the tuple (ARSCResTableConfig, resolved string) into the result set @@ -1975,7 +1981,7 @@ class ARSCParser: else: result.append((config, item.format_value())) - def get_resolved_res_configs(self, rid, config=None): + def get_resolved_res_configs(self, rid:int, config:Union[ARSCTableResConfig, None]=None) -> list[tuple[ARSCResTableConfig, str]]: """ Return a list of resolved resource IDs with their corresponding configuration. It has a similar return type as :meth:`get_res_configs` but also handles complex entries @@ -1991,7 +1997,7 @@ class ARSCParser: resolver = ARSCParser.ResourceResolver(self, config) return resolver.resolve(rid) - def get_resolved_strings(self): + def get_resolved_strings(self) -> list[str]: self._analyse() if self._resolved_strings: return self._resolved_strings @@ -2026,7 +2032,7 @@ class ARSCParser: self._resolved_strings = r return r - def get_res_configs(self, rid, config=None, fallback=True): + def get_res_configs(self, rid:int, config:Union[ARSCResTableConfig,None]=None, fallback:bool=True) -> list[ARSCResTableConfig]: """ Return the resources found with the ID `rid` and select the right one based on the configuration, or return all if no configuration was set. @@ -2044,7 +2050,7 @@ class ARSCParser: :param rid: resource id as int :param config: a config to resolve from, or None to get all results :param fallback: Enable the fallback for resolving default configuration (default: True) - :return: a list of ARSCResTableConfig: ARSCResTableEntry + :return: a list of ARSCResTableConfig: """ self._analyse() @@ -2069,7 +2075,7 @@ class ARSCParser: else: return list(res_options.items()) - def get_string(self, package_name, name, locale='\x00\x00'): + def get_string(self, package_name:str, name:str, locale:str='\x00\x00') -> Union[str,None]: self._analyse() try: @@ -2102,7 +2108,7 @@ class ARSCParser: return result @staticmethod - def parse_id(name): + def parse_id(name:str) -> tuple[str,str]: """ Resolves an id from a binary XML file in the form "@[package:]DEADBEEF" and returns a tuple of package name and resource id. @@ -2135,7 +2141,7 @@ class ARSCParser: except ValueError: raise ValueError("ID is not a hex ID: '{}'".format(res_id)) - def get_resource_xml_name(self, r_id, package=None): + def get_resource_xml_name(self, r_id:int, package:Union[str,None]=None) -> str: """ Returns the XML name for a resource, including the package name if package is None. A full name might look like `@com.example:string/foobar` @@ -2173,7 +2179,7 @@ class ARSCParser: class PackageContext: - def __init__(self, current_package, stringpool_main, mTableStrings, mKeyStrings): + def __init__(self, current_package: ARSCResTablePackage, stringpool_main: StringBlock, mTableStrings: StringBlock, mKeyStrings: StringBlock) -> None: """ :param ARSCResTablePackage current_package: :param StringBlock stringpool_main: @@ -2185,13 +2191,13 @@ class PackageContext: self.mKeyStrings = mKeyStrings self.current_package = current_package - def get_mResId(self): + def get_mResId(self) -> int: return self.current_package.mResId - def set_mResId(self, mResId): + def set_mResId(self, mResId: int) -> None: self.current_package.mResId = mResId - def get_package_name(self): + def get_package_name(self) -> str: return self.current_package.get_name() def __repr__(self): @@ -2221,9 +2227,9 @@ class ARSCHeader: # This is the minimal size such a header must have. There might be other header data too! SIZE = 2 + 2 + 4 - def __init__(self, buff, expected_type=None, possible_types=None): + def __init__(self, buff: BinaryIO, expected_type:Union[int,None]=None, possible_types:Union[set[int], None]=None) -> None: """ - :param androguard.core.bytecode.BuffHandle buff: the buffer set to the position where the header starts. + :param buff: the buffer set to the position where the header starts. :param int expected_type: the type of the header which is expected. """ self.start = buff.tell() @@ -2268,14 +2274,14 @@ class ARSCHeader: self.start)) @property - def type(self): + def type(self) -> int: """ Type identifier for this chunk """ return self._type @property - def header_size(self): + def header_size(self) -> int: """ Size of the chunk header (in bytes). Adding this value to the address of the chunk allows you to find its associated data @@ -2284,7 +2290,7 @@ class ARSCHeader: return self._header_size @property - def size(self): + def size(self) -> int: """ Total size of this chunk (in bytes). This is the chunkSize plus the size of any data associated with the chunk. Adding this value @@ -2295,7 +2301,7 @@ class ARSCHeader: return self._size @property - def end(self): + def end(self) -> int: """ Get the absolute offset inside the file, where the chunk ends. This is equal to `ARSCHeader.start + ARSCHeader.size`. @@ -2315,7 +2321,7 @@ class ARSCResTablePackage: See http://androidxref.com/9.0.0_r3/xref/frameworks/base/libs/androidfw/include/androidfw/ResourceTypes.h#861 """ - def __init__(self, buff, header): + def __init__(self, buff: BinaryIO, header:ARSCHeader) -> None: self.header = header self.start = buff.tell() self.id = unpack(' None: name = self.name.decode("utf-16", 'replace') name = name[:name.find("\x00")] return name @@ -2336,7 +2342,7 @@ class ARSCResTypeSpec: """ See http://androidxref.com/9.0.0_r3/xref/frameworks/base/libs/androidfw/include/androidfw/ResourceTypes.h#1327 """ - def __init__(self, buff, parent=None): + def __init__(self, buff: BinaryIO, parent:Union[PackageContext, None]=None) -> None: self.start = buff.tell() self.parent = parent self.id = unpack(' None: self.start = buff.tell() self.parent = parent @@ -2383,10 +2389,10 @@ class ARSCResType: logger.debug("Parsed {}".format(self)) - def get_type(self): + def get_type(self) -> str: return self.parent.mTableStrings.getString(self.id - 1) - def get_package_name(self): + def get_package_name(self) -> str: return self.parent.get_package_name() def __repr__(self): @@ -2416,7 +2422,7 @@ class ARSCResTableConfig: cls.DEFAULT = ARSCResTableConfig(None) return cls.DEFAULT - def __init__(self, buff=None, **kwargs): + def __init__(self, buff:Union[BinaryIO, None]=None, **kwargs) -> None: if buff is not None: self.start = buff.tell() @@ -2568,7 +2574,7 @@ class ARSCResTableConfig: char_out += chr(char_in[1]) return char_out - def get_language_and_region(self): + def get_language_and_region(self) -> str: """ Returns the combined language+region string or \x00\x00 for the default locale :return: @@ -2579,7 +2585,7 @@ class ARSCResTableConfig: return (_language + "-r" + _region) if _region else _language return "\x00\x00" - def get_config_name_friendly(self): + def get_config_name_friendly(self) -> str: """ Here for legacy reasons. @@ -2587,7 +2593,7 @@ class ARSCResTableConfig: """ return self.get_qualifier() - def get_qualifier(self): + def get_qualifier(self) -> str: """ Return resource name qualifier for the current configuration. for example @@ -2808,19 +2814,19 @@ class ARSCResTableConfig: return "-".join(res) - def get_language(self): + def get_language(self) -> str: x = self.locale & 0x0000ffff return chr(x & 0x00ff) + chr((x & 0xff00) >> 8) - def get_country(self): + def get_country(self) -> str: x = (self.locale & 0xffff0000) >> 16 return chr(x & 0x00ff) + chr((x & 0xff00) >> 8) - def get_density(self): + def get_density(self) -> str: x = ((self.screenType >> 16) & 0xffff) return x - def is_default(self): + def is_default(self) -> bool: """ Test if this is a default resource, which matches all @@ -2871,7 +2877,7 @@ class ARSCResTableEntry: # linking with other resource tables. FLAG_WEAK = 4 - def __init__(self, buff, mResId, parent=None): + def __init__(self, buff:BinaryIO, mResId:int, parent:Union[PackageContext, None]=None) -> None: self.start = buff.tell() self.mResId = mResId self.parent = parent @@ -2890,22 +2896,22 @@ class ARSCResTableEntry: if self.is_weak(): logger.debug("Parsed {}".format(self)) - def get_index(self): + def get_index(self) -> int: return self.index - def get_value(self): + def get_value(self) -> str: return self.parent.mKeyStrings.getString(self.index) - def get_key_data(self): + def get_key_data(self) -> str: return self.key.get_data_value() - def is_public(self): + def is_public(self) -> bool: return (self.flags & self.FLAG_PUBLIC) != 0 - def is_complex(self): + def is_complex(self) -> bool: return (self.flags & self.FLAG_COMPLEX) != 0 - def is_weak(self): + def is_weak(self) -> bool: return (self.flags & self.FLAG_WEAK) != 0 def __repr__(self): @@ -2928,7 +2934,7 @@ class ARSCComplex: See http://androidxref.com/9.0.0_r3/xref/frameworks/base/libs/androidfw/include/androidfw/ResourceTypes.h#1485 for `ResTable_map_entry` and http://androidxref.com/9.0.0_r3/xref/frameworks/base/libs/androidfw/include/androidfw/ResourceTypes.h#1498 for `ResTable_map` """ - def __init__(self, buff, parent=None): + def __init__(self, buff:BinaryIO, parent:Union[PackageContext,None]=None) -> None: self.start = buff.tell() self.parent = parent @@ -2953,7 +2959,7 @@ class ARSCResStringPoolRef: See: http://androidxref.com/9.0.0_r3/xref/frameworks/base/libs/androidfw/include/androidfw/ResourceTypes.h#262 """ - def __init__(self, buff, parent=None): + def __init__(self, buff:BinaryIO, parent:Union[PackageContext, None]=None) -> None: self.start = buff.tell() self.parent = parent @@ -2968,19 +2974,19 @@ class ARSCResStringPoolRef: except Exception as e: logger.error(e) - def get_data_value(self): + def get_data_value(self) -> str: return self.parent.stringpool_main.getString(self.data) - def get_data(self): + def get_data(self) -> int: return self.data - def get_data_type(self): + def get_data_type(self) -> bytes: return self.data_type - def get_data_type_string(self): + def get_data_type_string(self) -> str: return TYPE_TABLE[self.data_type] - def format_value(self): + def format_value(self) -> str: """ Return the formatted (interpreted) data according to `data_type`. """ @@ -2990,7 +2996,7 @@ class ARSCResStringPoolRef: self.parent.stringpool_main.getString ) - def is_reference(self): + def is_reference(self) -> bool: """ Returns True if the Res_value is actually a reference to another resource """ @@ -3004,7 +3010,7 @@ class ARSCResStringPoolRef: self.data) -def get_arsc_info(arscobj): +def get_arsc_info(arscobj:ARSCParser) -> str: """ Return a string containing all resources packages ordered by packagename, locale and type. diff --git a/androguard/core/bytecode.py b/androguard/core/bytecode.py index a96aefbc..0661d85b 100644 --- a/androguard/core/bytecode.py +++ b/androguard/core/bytecode.py @@ -1,13 +1,23 @@ +# Allows type hinting of types not-yet-declared +# in Python >= 3.7 +# see https://peps.python.org/pep-0563/ +from __future__ import annotations + import hashlib -from xml.sax.saxutils import escape +import json from struct import pack import textwrap -import json -from loguru import logger +from typing import TYPE_CHECKING, Union + from androguard.core.androconf import CONF, color_range from androguard.core.dex.dex_types import Kind, Operand +if TYPE_CHECKING: + from androguard.core.analysis import DEXBasicBlock, MethodAnalysis + from androguard.core.dex import DEX +from loguru import logger +from xml.sax.saxutils import escape def _PrintBanner(): print_fct = CONF["PRINT_FCT"] @@ -106,7 +116,7 @@ def _colorize_operands(operands, colors): yield "{}".format(repr(operands[1])) -def PrettyShow(basic_blocks, notes={}): +def PrettyShow(basic_blocks: list[DEXBasicBlock], notes:list=[]) -> None: idx = 0 offset_color = CONF["COLORS"]["OFFSET"] @@ -126,6 +136,8 @@ def PrettyShow(basic_blocks, notes={}): print_fct("{}{}{} : \n".format(bb_color, i.get_name(), normal_color)) instructions = list(i.get_instructions()) for ins in instructions: + + # TODO: this seems wrong, notes is a list[str], but maybe it used to be a dict? if nb in notes: for note in notes[nb]: _PrintNote(note, 1) @@ -185,7 +197,7 @@ def _get_operand_html(operand, registers_colors, colors): The HTML should be compatible with pydot/graphviz to be used inside a node label. - This is solely used in :func:`~androguard.core.bytecodes.method2dot` + This is solely used in :func:`~androguard.core.bytecode.method2dot` :param operand: tuple containing the operand type and operands :param dict register_colors: key: register number, value: register color @@ -222,7 +234,7 @@ def _get_operand_html(operand, registers_colors, colors): return escape(str(operand[1])) -def method2dot(mx, colors=None): +def method2dot(mx: MethodAnalysis, colors:Union[dict[str,str],None]=None) -> str: """ Export analysis method to dot format. @@ -394,7 +406,11 @@ def method2dot(mx, colors=None): return {'name': method_label, 'nodes': blocks_html, 'edges': edges_html} -def method2format(output, _format="png", mx=None, raw=None): +def method2format( + output:str, + _format:str="png", + mx:Union[MethodAnalysis,None]=None, + raw:Union[str,None]=None): """ Export method structure as a graph to a specific file format using dot from the graphviz package. The result is written to the file specified via :code:`output`. @@ -472,7 +488,7 @@ def method2format(output, _format="png", mx=None, raw=None): logger.error("Could not write graph image, ensure graphviz is installed!") raise -def method2png(output, mx, raw=False): +def method2png(output:str, mx:MethodAnalysis, raw:Union[str,None]=None) -> None: """ Export method to a png file format @@ -490,7 +506,7 @@ def method2png(output, mx, raw=False): method2format(output, "png", mx, buff) -def method2jpg(output, mx, raw=False): +def method2jpg(output:str, mx:MethodAnalysis, raw:Union[str,None]=None) -> None: """ Export method to a jpg file format @@ -508,12 +524,12 @@ def method2jpg(output, mx, raw=False): method2format(output, "jpg", mx, buff) -def vm2json(vm): +def vm2json(vm:DEX) -> str: """ Get a JSON representation of a DEX file - :param vm: :class:`~androguard.core.bytecodes.dvm.DEX` - :return: + :param vm: :class:`~androguard.core.dex.DEX` + :return: str """ d = {"name": "root", "children": []} @@ -531,31 +547,31 @@ def vm2json(vm): class TmpBlock: - def __init__(self, name): + def __init__(self, name:str) -> None: self.name = name - def get_name(self): + def get_name(self) -> str: return self.name -def method2json(mx, directed_graph=False): +def method2json(mx: MethodAnalysis, directed_graph:bool=False) -> str: """ Create directed or undirected graph in the json format. :param mx: :class:`~androguard.core.analysis.analysis.MethodAnalysis` :param directed_graph: True if a directed graph should be created (default: False) - :return: + :return: str """ if directed_graph: return method2json_direct(mx) return method2json_undirect(mx) -def method2json_undirect(mx): +def method2json_undirect(mx: MethodAnalysis) -> str: """ :param mx: :class:`~androguard.core.analysis.analysis.MethodAnalysis` - :return: + :return: str """ d = {} reports = [] @@ -583,7 +599,7 @@ def method2json_undirect(mx): return json.dumps(d) -def method2json_direct(mx): +def method2json_direct(mx: MethodAnalysis) -> str: """ :param mx: :class:`~androguard.core.analysis.analysis.MethodAnalysis` @@ -677,7 +693,7 @@ def method2json_direct(mx): return json.dumps(d) -def object_to_bytes(obj): +def object_to_bytes(obj:Union[str,bool,int,bytearray]) -> bytearray: """ Convert a object to a bytearray or call get_raw() of the object if no useful type was found. @@ -696,7 +712,7 @@ def object_to_bytes(obj): return obj.get_raw() -def FormatClassToJava(i): +def FormatClassToJava(i:str) -> str: """ Transform a java class name into the typed variant found in DEX files. @@ -711,7 +727,7 @@ def FormatClassToJava(i): return "L" + i.replace(".", "/") + ";" -def FormatClassToPython(i): +def FormatClassToPython(i:str) -> str: """ Transform a typed class name into a form which can be used as a python attribute @@ -731,7 +747,7 @@ def FormatClassToPython(i): return i -def get_package_class_name(name): +def get_package_class_name(name:str)->tuple[str, str]: """ Return package and class name in a java variant from a typed variant name. @@ -773,7 +789,7 @@ def get_package_class_name(name): return package, clsname -def FormatNameToPython(i): +def FormatNameToPython(i:str) -> str: """ Transform a (method) name into a form which can be used as a python attribute @@ -794,7 +810,7 @@ def FormatNameToPython(i): return i -def FormatDescriptorToPython(i): +def FormatDescriptorToPython(i:str) -> str: """ Format a descriptor into a form which can be used as a python attribute diff --git a/androguard/core/dex/__init__.py b/androguard/core/dex/__init__.py index c3ed3b76..e9fbc0f7 100644 --- a/androguard/core/dex/__init__.py +++ b/androguard/core/dex/__init__.py @@ -1,23 +1,30 @@ +# Allows type hinting of types not-yet-declared +# in Python >= 3.7 +# see https://peps.python.org/pep-0563/ +from __future__ import annotations + +import binascii +from enum import IntEnum +import hashlib +import io +import re +import struct +from struct import pack, unpack, calcsize +import sys +import time +from typing import Union, IO, BinaryIO, Iterator +from typing import TYPE_CHECKING +import zlib + +if TYPE_CHECKING: + from androguard.core.analysis.analysis import Analysis, MethodAnalysis + from androguard.decompiler.decompiler import DecompilerDAD + from androguard.decompiler.node import Node + from androguard.core import bytecode, apk from androguard.core.androconf import CONF from androguard.util import read_at - -import sys -import io -import re -import struct -import binascii -import time -from struct import pack, unpack, calcsize -import logging -import warnings -import zlib -import hashlib -from enum import IntEnum -from loguru import logger - - from androguard.core import mutf8 from .dex_types import ( TypeMapItem, @@ -27,6 +34,7 @@ from .dex_types import ( Operand, ) +from loguru import logger # TODO: have some more generic magic... DEX_FILE_MAGIC_35 = b'dex\n035\x00' @@ -98,7 +106,7 @@ class InvalidInstruction(Exception): pass -def read_null_terminated_string(f): +def read_null_terminated_string(f: IO) -> bytearray: """ Read a null terminated string from a file-like object. :param f: file-like object @@ -118,7 +126,7 @@ def read_null_terminated_string(f): return b''.join(x) -def get_access_flags_string(value): +def get_access_flags_string(value: int) -> str: """ Transform an access flag field to the corresponding string @@ -135,7 +143,7 @@ def get_access_flags_string(value): return " ".join(flags) -def get_type(atype, size=None): +def get_type(atype:str, size:Union[int,None]=None) -> str: """ Retrieve the type of a descriptor (e.g : I) """ @@ -176,7 +184,7 @@ BRANCH_DEX_OPCODES = ["throw", "throw.", "if.", "goto", "goto.", "return", "return.", "packed-switch$", "sparse-switch$"] -def clean_name_instruction(instruction): +def clean_name_instruction(instruction: Instruction) -> str: """USED IN ELSIM""" op_value = instruction.get_op_value() @@ -187,7 +195,7 @@ def clean_name_instruction(instruction): return instruction.get_name() -def static_operand_instruction(instruction): +def static_operand_instruction(instruction: Instruction) -> str: """USED IN ELSIM""" buff = "" @@ -203,15 +211,15 @@ def static_operand_instruction(instruction): return buff -def get_sbyte(cm, buff): +def get_sbyte(cm: ClassManager, buff: BinaryIO) -> int: return cm.packer["b"].unpack(buff.read(1))[0] -def get_byte(cm, buff): +def get_byte(cm: ClassManager, buff: BinaryIO) -> int: return cm.packer["B"].unpack(buff.read(1))[0] -def readuleb128(cm, buff): +def readuleb128(cm: ClassManager, buff: BinaryIO) -> int: """ Read an unsigned LEB128 at the current position of the buffer @@ -237,7 +245,7 @@ def readuleb128(cm, buff): return result -def readuleb128p1(cm, buff): +def readuleb128p1(cm: ClassManager, buff: BinaryIO) -> int: """ Read an unsigned LEB128p1 at the current position of the buffer. This format is the same as uLEB128 but has the ability to store the value -1. @@ -248,7 +256,7 @@ def readuleb128p1(cm, buff): return readuleb128(cm, buff) - 1 -def readsleb128(cm, buff): +def readsleb128(cm: ClassManager, buff: BinaryIO) -> int: """ Read a signed LEB128 at the current position of the buffer. @@ -274,7 +282,7 @@ def readsleb128(cm, buff): return result -def writeuleb128(cm, value): +def writeuleb128(cm: ClassManager, value: int) -> bytearray: """ Convert an integer value to the corresponding unsigned LEB128. @@ -299,7 +307,7 @@ def writeuleb128(cm, value): return buff -def writesleb128(cm, value): +def writesleb128(cm: ClassManager, value: int) -> bytearray: """ Convert an integer value to the corresponding signed LEB128 @@ -328,7 +336,7 @@ def writesleb128(cm, value): return buff -def determineNext(i, cur_idx, m): +def determineNext(i: Instruction, cur_idx: int, m: EncodedMethod) -> list: """ Determine the next offsets inside the bytecode of an :class:`EncodedMethod`. The offsets are calculated in number of bytes from the start of the method. @@ -396,7 +404,7 @@ def determineNext(i, cur_idx, m): return [] -def determineException(vm, m): +def determineException(vm: DEX, m:EncodedMethod) -> list[list]: """ Returns try-catch handler inside the method. @@ -435,12 +443,14 @@ def determineException(vm, m): for value in h_off[i]: try_value = value[0] + # start,end z = [try_value.get_start_addr() * 2, (try_value.get_start_addr() * 2) + (try_value.get_insn_count() * 2) - 1] handler_catch = value[1] + # exceptions for handler in handler_catch.get_handlers(): z.append([vm.get_cm_type(handler.get_type_idx()), handler.get_addr() * 2]) @@ -467,7 +477,7 @@ class HeaderItem: :type cm: :class:`ClassManager` """ - def __init__(self, size, buff, cm): + def __init__(self, size, buff: BinaryIO, cm:ClassManager) -> None: logger.debug("HeaderItem") self.CM = cm @@ -547,7 +557,7 @@ class HeaderItem: self.class_off_obj = None self.data_off_obj = None - def get_obj(self): + def get_obj(self) -> bytes: if self.map_off_obj is None: self.map_off_obj = self.CM.get_item_by_offset(self.map_off) @@ -622,13 +632,13 @@ class HeaderItem: pack(" bytes: return self.get_obj() - def get_length(self): + def get_length(self) -> int: return 112 - def show(self): + def show(self) -> None: bytecode._PrintSubBanner("Header Item") bytecode._PrintDefault("magic=%s, checksum=%s, signature=%s\n" % (self.magic, self.checksum, @@ -675,10 +685,10 @@ class HeaderItem: self.class_defs_size, self.class_defs_off, self.data_size, self.data_off) - def set_off(self, off): + def set_off(self, off:int) -> None: self.offset = off - def get_off(self): + def get_off(self) -> int: return self.offset @@ -687,36 +697,36 @@ class AnnotationOffItem: This class can parse an annotation_off_item of a dex file :param buff: a string which represents a Buff object of the annotation_off_item - :type buff: Buff object + :type buff: BinaryIO bytes object :param cm: a ClassManager object :type cm: :class:`ClassManager` """ - def __init__(self, buff, cm): + def __init__(self, buff: BinaryIO, cm: ClassManager) -> None: self.CM = cm self.annotation_off, = cm.packer["I"].unpack(buff.read(4)) - def get_annotation_off(self): + def get_annotation_off(self) -> int: return self.annotation_off def show(self): bytecode._PrintSubBanner("Annotation Off Item") bytecode._PrintDefault("annotation_off=0x%x\n" % self.annotation_off) - def get_obj(self): + def get_obj(self) -> bytes: if self.annotation_off != 0: self.annotation_off = self.CM.get_obj_by_offset( self.annotation_off).get_off() return self.CM.packer["I"].pack(self.annotation_off) - def get_raw(self): + def get_raw(self) -> bytes: return self.get_obj() - def get_length(self): + def get_length(self) -> int: return len(self.get_obj()) - def get_annotation_item(self): + def get_annotation_item(self) -> list[AnnotationItem]: return self.CM.get_annotation_item(self.get_annotation_off()) @@ -730,14 +740,14 @@ class AnnotationSetItem: :type cm: :class:`ClassManager` """ - def __init__(self, buff, cm): + def __init__(self, buff: BinaryIO, cm:ClassManager) -> None: self.CM = cm self.offset = buff.tell() self.size, = cm.packer["I"].unpack(buff.read(4)) self.annotation_off_item = [AnnotationOffItem(buff, cm) for _ in range(self.size)] - def get_annotation_off_item(self): + def get_annotation_off_item(self) -> list[AnnotationOffItem]: """ Return the offset from the start of the file to an annotation @@ -745,25 +755,25 @@ class AnnotationSetItem: """ return self.annotation_off_item - def set_off(self, off): + def set_off(self, off:int) -> None: self.offset = off - def get_off(self): + def get_off(self) -> int: return self.offset - def show(self): + def show(self) -> None: bytecode._PrintSubBanner("Annotation Set Item") for i in self.annotation_off_item: i.show() - def get_obj(self): + def get_obj(self) -> bytes: return self.CM.packer["I"].pack(self.size) - def get_raw(self): + def get_raw(self) -> bytes: return self.get_obj() + b''.join(i.get_raw() for i in self.annotation_off_item) - def get_length(self): + def get_length(self) -> int: length = len(self.get_obj()) for i in self.annotation_off_item: @@ -782,11 +792,11 @@ class AnnotationSetRefItem: :type cm: :class:`ClassManager` """ - def __init__(self, buff, cm): + def __init__(self, buff: BinaryIO, cm:ClassManager) -> None: self.CM = cm self.annotations_off, = cm.packer["I"].unpack(buff.read(4)) - def get_annotations_off(self): + def get_annotations_off(self) -> int: """ Return the offset from the start of the file to the referenced annotation set or 0 if there are no annotations for this element. @@ -795,18 +805,18 @@ class AnnotationSetRefItem: """ return self.annotations_off - def show(self): + def show(self) -> str: bytecode._PrintSubBanner("Annotation Set Ref Item") bytecode._PrintDefault("annotation_off=0x%x\n" % self.annotations_off) - def get_obj(self): + def get_obj(self) -> bytes: if self.annotations_off != 0: self.annotations_off = self.CM.get_obj_by_offset( self.annotations_off).get_off() return self.CM.packer["I"].pack(self.annotations_off) - def get_raw(self): + def get_raw(self) -> bytes: return self.get_obj() @@ -820,7 +830,7 @@ class AnnotationSetRefList: :type cm: :class:`ClassManager` """ - def __init__(self, buff, cm): + def __init__(self, buff: BinaryIO, cm:ClassManager) -> None: self.offset = buff.tell() self.CM = cm @@ -828,32 +838,32 @@ class AnnotationSetRefList: self.list = [AnnotationSetRefItem(buff, cm) for _ in range(self.size)] - def get_list(self): + def get_list(self) -> list[AnnotationSetRefItem]: """ - Return elements of the list + Return list of AnnotationSetRefItem - :rtype: :class:`AnnotationSetRefItem` + :rtype: list """ return self.list - def get_off(self): + def get_off(self) -> int: return self.offset - def set_off(self, off): + def set_off(self, off:int) -> None: self.offset = off - def show(self): + def show(self) -> None: bytecode._PrintSubBanner("Annotation Set Ref List Item") for i in self.list: i.show() - def get_obj(self): + def get_obj(self) -> list[AnnotationSetRefItem]: return [i for i in self.list] - def get_raw(self): + def get_raw(self) -> bytes: return self.CM.packer["I"].pack(self.size) + b''.join(i.get_raw() for i in self.list) - def get_length(self): + def get_length(self) -> int: return len(self.get_raw()) @@ -867,13 +877,13 @@ class FieldAnnotation: :type cm: :class:`ClassManager` """ - def __init__(self, buff, cm): + def __init__(self, buff: BinaryIO, cm:ClassManager) -> None: self.offset = buff.tell() self.CM = cm self.field_idx, self.annotations_off = cm.packer["2I"].unpack(buff.read(8)) - def get_field_idx(self): + def get_field_idx(self) -> int: """ Return the index into the field_ids list for the identity of the field being annotated @@ -881,7 +891,7 @@ class FieldAnnotation: """ return self.field_idx - def get_annotations_off(self): + def get_annotations_off(self) -> int: """ Return the offset from the start of the file to the list of annotations for the field @@ -889,28 +899,28 @@ class FieldAnnotation: """ return self.annotations_off - def set_off(self, off): + def set_off(self, off:int) -> None: self.offset = off - def get_off(self): + def get_off(self) -> int: return self.offset - def show(self): + def show(self) -> None: bytecode._PrintSubBanner("Field Annotation") bytecode._PrintDefault("field_idx=0x%x annotations_off=0x%x\n" % (self.field_idx, self.annotations_off)) - def get_obj(self): + def get_obj(self) -> bytes: if self.annotations_off != 0: self.annotations_off = self.CM.get_obj_by_offset( self.annotations_off).get_off() return self.CM.packer["2I"].pack(self.field_idx, self.annotations_off) - def get_raw(self): + def get_raw(self) -> bytes: return self.get_obj() - def get_length(self): + def get_length(self) -> int: return len(self.get_raw()) @@ -924,14 +934,14 @@ class MethodAnnotation: :type cm: :class:`ClassManager` """ - def __init__(self, buff, cm): + def __init__(self, buff: BinaryIO, cm:ClassManager) -> None: self.offset = buff.tell() self.CM = cm self.method_idx, \ self.annotations_off = cm.packer["2I"].unpack(buff.read(8)) - def get_method_idx(self): + def get_method_idx(self) -> int: """ Return the index into the method_ids list for the identity of the method being annotated @@ -939,7 +949,7 @@ class MethodAnnotation: """ return self.method_idx - def get_annotations_off(self): + def get_annotations_off(self) -> int: """ Return the offset from the start of the file to the list of annotations for the method @@ -947,28 +957,28 @@ class MethodAnnotation: """ return self.annotations_off - def set_off(self, off): + def set_off(self, off:int) -> None: self.offset = off - def get_off(self): + def get_off(self) -> int: return self.offset - def show(self): + def show(self) -> None: bytecode._PrintSubBanner("Method Annotation") bytecode._PrintDefault("method_idx=0x%x annotations_off=0x%x\n" % (self.method_idx, self.annotations_off)) - def get_obj(self): + def get_obj(self) -> bytes: if self.annotations_off != 0: self.annotations_off = self.CM.get_obj_by_offset( self.annotations_off).get_off() return self.CM.packer["2I"].pack(self.method_idx, self.annotations_off) - def get_raw(self): + def get_raw(self) -> bytes: return self.get_obj() - def get_length(self): + def get_length(self) -> int: return len(self.get_raw()) @@ -982,14 +992,14 @@ class ParameterAnnotation: :type cm: :class:`ClassManager` """ - def __init__(self, buff, cm): + def __init__(self, buff: BinaryIO, cm:ClassManager) -> None: self.offset = buff.tell() self.CM = cm self.method_idx, \ self.annotations_off = cm.packer["2I"].unpack(buff.read(8)) - def get_method_idx(self): + def get_method_idx(self) -> int: """ Return the index into the method_ids list for the identity of the method whose parameters are being annotated @@ -997,7 +1007,7 @@ class ParameterAnnotation: """ return self.method_idx - def get_annotations_off(self): + def get_annotations_off(self) -> int: """ Return the offset from the start of the file to the list of annotations for the method parameters @@ -1005,28 +1015,28 @@ class ParameterAnnotation: """ return self.annotations_off - def set_off(self, off): + def set_off(self, off:int) -> None: self.offset = off - def get_off(self): + def get_off(self) -> int: return self.offset - def show(self): + def show(self) -> None: bytecode._PrintSubBanner("Parameter Annotation") bytecode._PrintDefault("method_idx=0x%x annotations_off=0x%x\n" % (self.method_idx, self.annotations_off)) - def get_obj(self): + def get_obj(self) -> bytes: if self.annotations_off != 0: self.annotations_off = self.CM.get_obj_by_offset( self.annotations_off).get_off() return self.CM.packer["2I"].pack(self.method_idx, self.annotations_off) - def get_raw(self): + def get_raw(self) -> bytes: return self.get_obj() - def get_length(self): + def get_length(self) -> int: return len(self.get_raw()) @@ -1040,7 +1050,7 @@ class AnnotationsDirectoryItem: :type cm: :class:`ClassManager` """ - def __init__(self, buff, cm): + def __init__(self, buff: BinaryIO, cm:ClassManager) -> None: self.CM = cm self.offset = buff.tell() @@ -1056,7 +1066,7 @@ class AnnotationsDirectoryItem: self.parameter_annotations = [ParameterAnnotation(buff, cm) for i in range(0, self.annotated_parameters_size)] - def get_class_annotations_off(self): + def get_class_annotations_off(self) -> int: """ Return the offset from the start of the file to the annotations made directly on the class, or 0 if the class has no direct annotations @@ -1065,10 +1075,10 @@ class AnnotationsDirectoryItem: """ return self.class_annotations_off - def get_annotation_set_item(self): + def get_annotation_set_item(self) -> list[AnnotationSetItem]: return self.CM.get_annotation_set_item(self.class_annotations_off) - def get_annotated_fields_size(self): + def get_annotated_fields_size(self) -> int: """ Return the count of fields annotated by this item @@ -1076,7 +1086,7 @@ class AnnotationsDirectoryItem: """ return self.annotated_fields_size - def get_annotated_methods_size(self): + def get_annotated_methods_size(self) -> int: """ Return the count of methods annotated by this item @@ -1084,7 +1094,7 @@ class AnnotationsDirectoryItem: """ return self.annotated_methods_size - def get_annotated_parameters_size(self): + def get_annotated_parameters_size(self) -> int: """ Return the count of method parameter lists annotated by this item @@ -1092,7 +1102,7 @@ class AnnotationsDirectoryItem: """ return self.annotated_parameters_size - def get_field_annotations(self): + def get_field_annotations(self) -> list[FieldAnnotation]: """ Return the list of associated field annotations @@ -1100,7 +1110,7 @@ class AnnotationsDirectoryItem: """ return self.field_annotations - def get_method_annotations(self): + def get_method_annotations(self) -> list[MethodAnnotation]: """ Return the list of associated method annotations @@ -1108,7 +1118,7 @@ class AnnotationsDirectoryItem: """ return self.method_annotations - def get_parameter_annotations(self): + def get_parameter_annotations(self) -> list[ParameterAnnotation]: """ Return the list of associated method parameter annotations @@ -1116,13 +1126,13 @@ class AnnotationsDirectoryItem: """ return self.parameter_annotations - def set_off(self, off): + def set_off(self, off:int) -> None: self.offset = off - def get_off(self): + def get_off(self) -> int: return self.offset - def show(self): + def show(self) -> None: bytecode._PrintSubBanner("Annotations Directory Item") bytecode._PrintDefault( "class_annotations_off=0x%x annotated_fields_size=%d annotated_methods_size=%d annotated_parameters_size=%d\n" @@ -1138,7 +1148,7 @@ class AnnotationsDirectoryItem: for i in self.parameter_annotations: i.show() - def get_obj(self): + def get_obj(self) -> bytes: if self.class_annotations_off != 0: self.class_annotations_off = self.CM.get_obj_by_offset( self.class_annotations_off).get_off() @@ -1148,13 +1158,13 @@ class AnnotationsDirectoryItem: self.annotated_methods_size, self.annotated_parameters_size) - def get_raw(self): + def get_raw(self) -> bytes: return self.get_obj() + \ b''.join(i.get_raw() for i in self.field_annotations) + \ b''.join(i.get_raw() for i in self.method_annotations) + \ b''.join(i.get_raw() for i in self.parameter_annotations) - def get_length(self): + def get_length(self) -> int: length = len(self.get_obj()) for i in self.field_annotations: length += i.get_length() @@ -1192,7 +1202,7 @@ class HiddenApiClassDataItem: CORE_PLATFORM_API = 1 TEST_API = 2 - def __init__(self, buff, cm): + def __init__(self, buff: BinaryIO, cm:ClassManager) -> None: self.CM = cm self.offset = buff.tell() @@ -1236,7 +1246,7 @@ class HiddenApiClassDataItem: """ return self.flags[idx] - def set_off(self, off): + def set_off(self, off:int): self.offset = off def get_off(self): @@ -1281,7 +1291,7 @@ class TypeItem: :type cm: :class:`ClassManager` """ - def __init__(self, buff, cm): + def __init__(self, buff: BinaryIO, cm:ClassManager) -> None: self.CM = cm self.type_idx, = cm.packer["H"].unpack(buff.read(2)) @@ -1325,7 +1335,7 @@ class TypeList: :type cm: :class:`ClassManager` """ - def __init__(self, buff, cm): + def __init__(self, buff: BinaryIO, cm:ClassManager) -> None: self.CM = cm self.offset = buff.tell() self.size, = cm.packer["I"].unpack(buff.read(4)) @@ -1378,7 +1388,7 @@ class TypeList: """ return self.list - def set_off(self, off): + def set_off(self, off:int): self.offset = off def get_off(self): @@ -1445,7 +1455,7 @@ class DBGBytecode: class DebugInfoItem: - def __init__(self, buff, cm): + def __init__(self, buff: BinaryIO, cm:ClassManager) -> None: self.CM = cm self.offset = buff.tell() @@ -1541,7 +1551,7 @@ class DebugInfoItem: class DebugInfoItemEmpty: - def __init__(self, buff, cm): + def __init__(self, buff: BinaryIO, cm:ClassManager) -> None: self.CM = cm self.offset = buff.tell() @@ -1550,7 +1560,7 @@ class DebugInfoItemEmpty: self.reload() - def set_off(self, off): + def set_off(self, off:int): self.offset = off def get_off(self): @@ -1589,7 +1599,7 @@ class EncodedArray: :type cm: :class:`ClassManager` """ - def __init__(self, buff, cm): + def __init__(self, buff: BinaryIO, cm:ClassManager) -> None: self.CM = cm self.offset = buff.tell() @@ -1624,7 +1634,7 @@ class EncodedArray: def get_obj(self): return writeuleb128(self.CM, self.size) - def get_raw(self): + def get_raw(self) -> bytes: return self.get_obj() + b''.join(i.get_raw() for i in self.values) def get_length(self): @@ -1645,7 +1655,7 @@ class EncodedValue: :type cm: :class:`ClassManager` """ - def __init__(self, buff, cm): + def __init__(self, buff: BinaryIO, cm:ClassManager) -> None: self.CM = cm self.val = get_byte(cm, buff) @@ -1749,7 +1759,7 @@ class AnnotationElement: :type cm: :class:`ClassManager` """ - def __init__(self, buff, cm): + def __init__(self, buff: BinaryIO, cm:ClassManager) -> None: self.CM = cm self.offset = buff.tell() @@ -1797,7 +1807,7 @@ class EncodedAnnotation: :type cm: :class:`ClassManager` """ - def __init__(self, buff, cm): + def __init__(self, buff: BinaryIO, cm:ClassManager) -> None: self.CM = cm self.offset = buff.tell() @@ -1859,12 +1869,12 @@ class AnnotationItem: This class can parse an annotation_item of a dex file :param buff: a string which represents a Buff object of the annotation_item - :type buff: Buff object + :type buff: BinaryIO bytes object :param cm: a ClassManager object :type cm: :class:`ClassManager` """ - def __init__(self, buff, cm): + def __init__(self, buff: BinaryIO, cm:ClassManager) -> None: self.CM = cm self.offset = buff.tell() @@ -1872,7 +1882,7 @@ class AnnotationItem: self.visibility = get_byte(cm, buff) self.annotation = EncodedAnnotation(buff, cm) - def get_visibility(self): + def get_visibility(self) -> int: """ Return the intended visibility of this annotation @@ -1880,7 +1890,7 @@ class AnnotationItem: """ return self.visibility - def get_annotation(self): + def get_annotation(self) -> EncodedAnnotation: """ Return the encoded annotation contents @@ -1888,24 +1898,24 @@ class AnnotationItem: """ return self.annotation - def set_off(self, off): + def set_off(self, off:int) -> None: self.offset = off - def get_off(self): + def get_off(self) -> int: return self.offset - def show(self): + def show(self) -> None: bytecode._PrintSubBanner("Annotation Item") bytecode._PrintDefault("visibility=%d\n" % self.visibility) self.annotation.show() - def get_obj(self): + def get_obj(self) -> list[EncodedAnnotation]: return [self.annotation] - def get_raw(self): + def get_raw(self) -> bytes: return self.CM.packer["B"].pack(self.visibility) + self.annotation.get_raw() - def get_length(self): + def get_length(self) -> int: return len(self.get_raw()) @@ -1919,13 +1929,13 @@ class EncodedArrayItem: :type cm: :class:`ClassManager` """ - def __init__(self, buff, cm): + def __init__(self, buff: BinaryIO, cm: ClassManager) -> None: self.CM = cm self.offset = buff.tell() self.value = EncodedArray(buff, cm) - def get_value(self): + def get_value(self) -> EncodedArray: """ Return the bytes representing the encoded array value @@ -1933,23 +1943,23 @@ class EncodedArrayItem: """ return self.value - def set_off(self, off): + def set_off(self, off:int) -> None: self.offset = off - def show(self): + def show(self) -> None: bytecode._PrintSubBanner("Encoded Array Item") self.value.show() - def get_obj(self): + def get_obj(self) -> list[EncodedArray]: return [self.value] - def get_raw(self): + def get_raw(self) -> bytes: return self.value.get_raw() - def get_length(self): + def get_length(self) -> int: return self.value.get_length() - def get_off(self): + def get_off(self) -> int: return self.offset @@ -1976,12 +1986,12 @@ class StringDataItem: python you ca use :meth:`get` which escapes invalid characters. :param buff: a string which represents a Buff object of the string_data_item - :type buff: io.io.BufferedReader + :type buff: BinaryIO.io.BufferedReader :param cm: a ClassManager object :type cm: :class:`ClassManager` """ - def __init__(self, buff, cm): + def __init__(self, buff: BinaryIO, cm: ClassManager) -> None: self.CM = cm self.offset = buff.tell() @@ -1990,7 +2000,7 @@ class StringDataItem: self.utf16_size = readuleb128(cm, buff) self.data = read_null_terminated_string(buff) - def get_utf16_size(self): + def get_utf16_size(self) -> int: """ Return the size of this string, in UTF-16 code units @@ -1998,7 +2008,7 @@ class StringDataItem: """ return self.utf16_size - def get_data(self): + def get_data(self) -> str: """ Return a series of MUTF-8 code units (a.k.a. octets, a.k.a. bytes) followed by a byte of value 0 @@ -2006,13 +2016,13 @@ class StringDataItem: """ return self.data + b"\x00" - def set_off(self, off): + def set_off(self, off: int) -> None: self.offset = off - def get_off(self): + def get_off(self) -> int: return self.offset - def get(self): + def get(self) -> str: """ Returns a str object """ @@ -2022,15 +2032,15 @@ class StringDataItem: logger.error("Impossible to decode {}".format(self.data)) return "ANDROGUARD[INVALID_STRING] {}".format(self.data) - def show(self): + def show(self) -> None: bytecode._PrintSubBanner("String Data Item") bytecode._PrintDefault("utf16_size=%d data=%s\n" % (self.utf16_size, repr(self.get()))) - def get_obj(self): + def get_obj(self) -> list: return [] - def get_raw(self): + def get_raw(self) -> bytes: """ Returns the raw string including the ULEB128 coded length and null byte string terminator @@ -2039,7 +2049,7 @@ class StringDataItem: """ return writeuleb128(self.CM, self.utf16_size) + self.data + b"\x00" - def get_length(self): + def get_length(self) -> int: """ Get the length of the raw string including the ULEB128 coded length and the null byte terminator @@ -2059,13 +2069,13 @@ class StringIdItem: :type cm: :class:`ClassManager` """ - def __init__(self, buff, cm): + def __init__(self, buff: BinaryIO, cm: ClassManager): self.CM = cm self.offset = buff.tell() self.string_data_off, = cm.packer["I"].unpack(buff.read(4)) - def get_string_data_off(self): + def get_string_data_off(self) -> int: """ Return the offset from the start of the file to the string data for this item @@ -2073,27 +2083,27 @@ class StringIdItem: """ return self.string_data_off - def set_off(self, off): + def set_off(self, off:int) -> None: self.offset = off - def get_off(self): + def get_off(self) -> int: return self.offset - def show(self): + def show(self) -> None: bytecode._PrintSubBanner("String Id Item") bytecode._PrintDefault("string_data_off=%x\n" % self.string_data_off) - def get_obj(self): + def get_obj(self) -> bytes: if self.string_data_off != 0: self.string_data_off = self.CM.get_string_by_offset( self.string_data_off).get_off() return self.CM.packer["I"].pack(self.string_data_off) - def get_raw(self): + def get_raw(self) -> bytes: return self.get_obj() - def get_length(self): + def get_length(self) -> int: return len(self.get_obj()) @@ -2107,14 +2117,14 @@ class TypeIdItem: :type cm: :class:`ClassManager` """ - def __init__(self, buff, cm): + def __init__(self, buff: BinaryIO, cm: ClassManager): self.CM = cm self.offset = buff.tell() self.descriptor_idx, = cm.packer["I"].unpack(buff.read(4)) self.descriptor_idx_value = self.CM.get_string(self.descriptor_idx) - def get_descriptor_idx(self): + def get_descriptor_idx(self) -> int: """ Return the index into the string_ids list for the descriptor string of this type @@ -2122,7 +2132,7 @@ class TypeIdItem: """ return self.descriptor_idx - def get_descriptor_idx_value(self): + def get_descriptor_idx_value(self) -> str: """ Return the string associated to the descriptor @@ -2130,18 +2140,18 @@ class TypeIdItem: """ return self.descriptor_idx_value - def show(self): + def show(self) -> None: bytecode._PrintSubBanner("Type Id Item") bytecode._PrintDefault("descriptor_idx=%d descriptor_idx_value=%s\n" % (self.descriptor_idx, self.descriptor_idx_value)) - def get_obj(self): + def get_obj(self) -> bytes: return self.CM.packer["I"].pack(self.descriptor_idx) - def get_raw(self): + def get_raw(self) -> bytes: return self.get_obj() - def get_length(self): + def get_length(self) -> int: return len(self.get_obj()) @@ -2155,14 +2165,14 @@ class TypeHIdItem: :type cm: :class:`ClassManager` """ - def __init__(self, size, buff, cm): + def __init__(self, size: int, buff: BinaryIO, cm: ClassManager) -> None: self.CM = cm self.offset = buff.tell() self.type = [TypeIdItem(buff, cm) for i in range(0,size)] - def get_type(self): + def get_type(self) -> list[TypeIdItem]: """ Return the list of type_id_item @@ -2170,30 +2180,30 @@ class TypeHIdItem: """ return self.type - def get(self, idx): + def get(self, idx: int) -> int: try: return self.type[idx].get_descriptor_idx() except IndexError: return -1 - def set_off(self, off): + def set_off(self, off:int) -> None: self.offset = off - def get_off(self): + def get_off(self) -> int: return self.offset - def show(self): + def show(self) -> None: bytecode._PrintSubBanner("Type List Item") for i in self.type: i.show() - def get_obj(self): + def get_obj(self) -> list[TypeIdItem]: return [i for i in self.type] - def get_raw(self): + def get_raw(self) -> bytes: return b''.join(i.get_raw() for i in self.type) - def get_length(self): + def get_length(self) -> int: length = 0 for i in self.type: length += i.get_length() @@ -2210,7 +2220,7 @@ class ProtoIdItem: :type cm: :class:`ClassManager` """ - def __init__(self, buff, cm): + def __init__(self, buff: BinaryIO, cm: ClassManager): self.CM = cm self.offset = buff.tell() @@ -2222,7 +2232,7 @@ class ProtoIdItem: self.return_type_idx_value = self.CM.get_type(self.return_type_idx) self.parameters_off_value = None - def get_shorty_idx(self): + def get_shorty_idx(self) -> int: """ Return the index into the string_ids list for the short-form descriptor string of this prototype @@ -2230,7 +2240,7 @@ class ProtoIdItem: """ return self.shorty_idx - def get_return_type_idx(self): + def get_return_type_idx(self) -> int: """ Return the index into the type_ids list for the return type of this prototype @@ -2238,7 +2248,7 @@ class ProtoIdItem: """ return self.return_type_idx - def get_parameters_off(self): + def get_parameters_off(self) -> int: """ Return the offset from the start of the file to the list of parameter types for this prototype, or 0 if this prototype has no parameters @@ -2246,7 +2256,7 @@ class ProtoIdItem: """ return self.parameters_off - def get_shorty_idx_value(self): + def get_shorty_idx_value(self) -> str: """ Return the string associated to the shorty_idx @@ -2256,7 +2266,7 @@ class ProtoIdItem: self.shorty_idx_value = self.CM.get_string(self.shorty_idx) return self.shorty_idx_value - def get_return_type_idx_value(self): + def get_return_type_idx_value(self) -> str: """ Return the string associated to the return_type_idx @@ -2264,10 +2274,9 @@ class ProtoIdItem: """ if self.return_type_idx_value is None: self.return_type_idx_value = self.CM.get_type(self.return_type_idx) - return self.return_type_idx_value - def get_parameters_off_value(self): + def get_parameters_off_value(self) -> str: """ Return the string associated to the parameters_off @@ -2278,7 +2287,7 @@ class ProtoIdItem: self.parameters_off_value = '(' + ' '.join(params) + ')' return self.parameters_off_value - def show(self): + def show(self) -> None: bytecode._PrintSubBanner("Proto Item") bytecode._PrintDefault( "shorty_idx=%d return_type_idx=%d parameters_off=%d\n" % @@ -2288,7 +2297,7 @@ class ProtoIdItem: % (self.shorty_idx_value, self.return_type_idx_value, self.parameters_off_value)) - def get_obj(self): + def get_obj(self) -> bytes: if self.parameters_off != 0: self.parameters_off = self.CM.get_obj_by_offset( self.parameters_off).get_off() @@ -2297,10 +2306,10 @@ class ProtoIdItem: self.return_type_idx, self.parameters_off) - def get_raw(self): + def get_raw(self) -> bytes: return self.get_obj() - def get_length(self): + def get_length(self) -> int: return len(self.get_obj()) @@ -2314,37 +2323,37 @@ class ProtoHIdItem: :type cm: :class:`ClassManager` """ - def __init__(self, size, buff, cm): + def __init__(self, size:int, buff: BinaryIO, cm:ClassManager) -> None: self.CM = cm self.offset = buff.tell() self.proto = [ProtoIdItem(buff, cm) for i in range(0, size)] - def set_off(self, off): + def set_off(self, off:int) -> None: self.offset = off - def get_off(self): + def get_off(self) -> int: return self.offset - def get(self, idx): + def get(self, idx:int) -> ProtoIdItem: try: return self.proto[idx] except IndexError: return ProtoIdItemInvalid() - def show(self): + def show(self) -> None: bytecode._PrintSubBanner("Proto List Item") for i in self.proto: i.show() - def get_obj(self): + def get_obj(self) -> list[ProtoIdItem]: return [i for i in self.proto] - def get_raw(self): + def get_raw(self) -> bytes: return b''.join(i.get_raw() for i in self.proto) - def get_length(self): + def get_length(self) -> int: length = 0 for i in self.proto: length += i.get_length() @@ -2361,7 +2370,7 @@ class FieldIdItem: :type cm: :class:`ClassManager` """ - def __init__(self, buff, cm): + def __init__(self, buff: BinaryIO, cm: ClassManager): self.CM = cm self.offset = buff.tell() @@ -2371,12 +2380,12 @@ class FieldIdItem: self.reload() - def reload(self): + def reload(self) -> None: self.class_idx_value = self.CM.get_type(self.class_idx) self.type_idx_value = self.CM.get_type(self.type_idx) self.name_idx_value = self.CM.get_string(self.name_idx) - def get_class_idx(self): + def get_class_idx(self) -> int: """ Return the index into the type_ids list for the definer of this field @@ -2384,7 +2393,7 @@ class FieldIdItem: """ return self.class_idx - def get_type_idx(self): + def get_type_idx(self) -> int: """ Return the index into the type_ids list for the type of this field @@ -2392,7 +2401,7 @@ class FieldIdItem: """ return self.type_idx - def get_name_idx(self): + def get_name_idx(self) -> int: """ Return the index into the string_ids list for the name of this field @@ -2400,7 +2409,7 @@ class FieldIdItem: """ return self.name_idx - def get_class_name(self): + def get_class_name(self) -> str: """ Return the class name of the field @@ -2408,10 +2417,9 @@ class FieldIdItem: """ if self.class_idx_value is None: self.class_idx_value = self.CM.get_type(self.class_idx) - return self.class_idx_value - def get_type(self): + def get_type(self) -> str: """ Return the type of the field @@ -2419,10 +2427,9 @@ class FieldIdItem: """ if self.type_idx_value is None: self.type_idx_value = self.CM.get_type(self.type_idx) - return self.type_idx_value - def get_descriptor(self): + def get_descriptor(self) -> str: """ Return the descriptor of the field @@ -2430,10 +2437,9 @@ class FieldIdItem: """ if self.type_idx_value is None: self.type_idx_value = self.CM.get_type(self.type_idx) - return self.type_idx_value - def get_name(self): + def get_name(self) -> str: """ Return the name of the field @@ -2441,13 +2447,12 @@ class FieldIdItem: """ if self.name_idx_value is None: self.name_idx_value = self.CM.get_string(self.name_idx) - return self.name_idx_value - def get_list(self): + def get_list(self) -> list[str]: return [self.get_class_name(), self.get_type(), self.get_name()] - def show(self): + def show(self) -> None: bytecode._PrintSubBanner("Field Id Item") bytecode._PrintDefault("class_idx=%d type_idx=%d name_idx=%d\n" % (self.class_idx, self.type_idx, self.name_idx)) @@ -2455,15 +2460,15 @@ class FieldIdItem: "class_idx_value=%s type_idx_value=%s name_idx_value=%s\n" % (self.class_idx_value, self.type_idx_value, self.name_idx_value)) - def get_obj(self): + def get_obj(self) -> bytes: return self.CM.packer["2HI"].pack(self.class_idx, self.type_idx, self.name_idx) - def get_raw(self): + def get_raw(self) -> bytes: return self.get_obj() - def get_length(self): + def get_length(self) -> int: return len(self.get_obj()) @@ -2477,40 +2482,40 @@ class FieldHIdItem: :type cm: :class:`ClassManager` """ - def __init__(self, size, buff, cm): + def __init__(self, size:int, buff: BinaryIO, cm:ClassManager) -> None: self.offset = buff.tell() self.field_id_items = [FieldIdItem(buff, cm) for i in range(0, size)] - def set_off(self, off): + def set_off(self, off:int) -> None: self.offset = off - def get_off(self): + def get_off(self) -> int: return self.offset - def gets(self): + def gets(self) -> list[FieldIdItem]: return self.field_id_items - def get(self, idx): + def get(self, idx:int) -> Union[FieldIdItem,FieldIdItemInvalid]: try: return self.field_id_items[idx] except IndexError: return FieldIdItemInvalid() - def show(self): + def show(self) -> None: nb = 0 for i in self.field_id_items: print(nb, end=' ') i.show() nb = nb + 1 - def get_obj(self): + def get_obj(self) -> list[FieldIdItem]: return [i for i in self.field_id_items] - def get_raw(self): + def get_raw(self) -> bytes: return b''.join(i.get_raw() for i in self.field_id_items) - def get_length(self): + def get_length(self) -> int: length = 0 for i in self.field_id_items: length += i.get_length() @@ -2527,7 +2532,7 @@ class MethodIdItem: :type cm: :class:`ClassManager` """ - def __init__(self, buff, cm): + def __init__(self, buff: BinaryIO, cm:ClassManager) -> None: self.CM = cm self.offset = buff.tell() @@ -2537,12 +2542,12 @@ class MethodIdItem: self.reload() - def reload(self): + def reload(self) -> None: self.class_idx_value = self.CM.get_type(self.class_idx) self.proto_idx_value = self.CM.get_proto(self.proto_idx) self.name_idx_value = self.CM.get_string(self.name_idx) - def get_class_idx(self): + def get_class_idx(self) -> int: """ Return the index into the type_ids list for the definer of this method @@ -2550,7 +2555,7 @@ class MethodIdItem: """ return self.class_idx - def get_proto_idx(self): + def get_proto_idx(self) -> int: """ Return the index into the proto_ids list for the prototype of this method @@ -2558,7 +2563,7 @@ class MethodIdItem: """ return self.proto_idx - def get_name_idx(self): + def get_name_idx(self) -> int: """ Return the index into the string_ids list for the name of this method @@ -2566,7 +2571,7 @@ class MethodIdItem: """ return self.name_idx - def get_class_name(self): + def get_class_name(self) -> str: """ Return the class name of the method @@ -2577,7 +2582,7 @@ class MethodIdItem: return self.class_idx_value - def get_proto(self): + def get_proto(self) -> str: """ Return the prototype of the method @@ -2588,7 +2593,7 @@ class MethodIdItem: return self.proto_idx_value - def get_descriptor(self): + def get_descriptor(self) -> str: """ Return the descriptor @@ -2597,7 +2602,7 @@ class MethodIdItem: proto = self.get_proto() return proto[0] + proto[1] - def get_real_descriptor(self): + def get_real_descriptor(self) -> str: """ Return the real descriptor (i.e. without extra spaces) @@ -2606,7 +2611,7 @@ class MethodIdItem: proto = self.get_proto() return proto[0].replace(' ', '') + proto[1] - def get_name(self): + def get_name(self) -> str: """ Return the name of the method @@ -2614,17 +2619,16 @@ class MethodIdItem: """ if self.name_idx_value is None: self.name_idx_value = self.CM.get_string(self.name_idx) - return self.name_idx_value - def get_list(self): + def get_list(self) -> list[str]: return [self.get_class_name(), self.get_name(), self.get_proto()] - def get_triple(self): + def get_triple(self) -> tuple[str,str,str]: return self.get_class_name()[1:-1], self.get_name( ), self.get_real_descriptor() - def show(self): + def show(self) -> None: bytecode._PrintSubBanner("Method Id Item") bytecode._PrintDefault("class_idx=%d proto_idx=%d name_idx=%d\n" % (self.class_idx, self.proto_idx, self.name_idx)) @@ -2632,15 +2636,15 @@ class MethodIdItem: "class_idx_value=%s proto_idx_value=%s name_idx_value=%s\n" % (self.class_idx_value, self.proto_idx_value, self.name_idx_value)) - def get_obj(self): + def get_obj(self) -> bytes: return self.CM.packer["2HI"].pack(self.class_idx, self.proto_idx, self.name_idx) - def get_raw(self): + def get_raw(self) -> bytes: return self.get_obj() - def get_length(self): + def get_length(self) -> int: return len(self.get_obj()) @@ -2654,33 +2658,33 @@ class MethodHIdItem: :type cm: :class:`ClassManager` """ - def __init__(self, size, buff, cm): + def __init__(self, size:int, buff: BinaryIO, cm:ClassManager) -> None: self.CM = cm self.offset = buff.tell() self.method_id_items = [MethodIdItem(buff, cm) for i in range(0, size)] - def set_off(self, off): + def set_off(self, off: int): self.offset = off - def get_off(self): + def get_off(self) -> int: return self.offset - def gets(self): + def gets(self) -> list[MethodIdItem]: return self.method_id_items - def get(self, idx): + def get(self, idx) -> Union[MethodIdItem,MethodIdItemInvalid]: try: return self.method_id_items[idx] except IndexError: return MethodIdItemInvalid() - def reload(self): + def reload(self) -> None: for i in self.method_id_items: i.reload() - def show(self): + def show(self) -> None: print("METHOD_ID_ITEM") nb = 0 for i in self.method_id_items: @@ -2688,13 +2692,13 @@ class MethodHIdItem: i.show() nb = nb + 1 - def get_obj(self): + def get_obj(self) -> list[MethodIdItem]: return [i for i in self.method_id_items] - def get_raw(self): + def get_raw(self) -> bytes: return b''.join(i.get_raw() for i in self.method_id_items) - def get_length(self): + def get_length(self) -> int: length = 0 for i in self.method_id_items: length += i.get_length() @@ -2702,57 +2706,57 @@ class MethodHIdItem: class ProtoIdItemInvalid: - def get_params(self): + def get_params(self) -> str: return "AG:IPI:invalid_params;" - def get_shorty(self): + def get_shorty(self) -> str: return "(AG:IPI:invalid_shorty)" - def get_return_type(self): + def get_return_type(self) -> str: return "(AG:IPI:invalid_return_type)" - def show(self): + def show(self) -> None: print("AG:IPI:invalid_proto_item", self.get_shorty( ), self.get_return_type(), self.get_params()) class FieldIdItemInvalid: - def get_class_name(self): + def get_class_name(self) -> str: return "AG:IFI:invalid_class_name;" - def get_type(self): + def get_type(self) -> str: return "(AG:IFI:invalid_type)" - def get_descriptor(self): + def get_descriptor(self) -> str: return "(AG:IFI:invalid_descriptor)" - def get_name(self): + def get_name(self) -> str: return "AG:IFI:invalid_name" - def get_list(self): + def get_list(self) -> list[str]: return [self.get_class_name(), self.get_type(), self.get_name()] - def show(self): + def show(self) -> None: print("AG:IFI:invalid_field_item") class MethodIdItemInvalid: - def get_class_name(self): + def get_class_name(self) -> str: return "AG:IMI:invalid_class_name;" - def get_descriptor(self): + def get_descriptor(self) -> str: return "(AG:IMI:invalid_descriptor)" - def get_proto(self): + def get_proto(self) -> str: return "()AG:IMI:invalid_proto" - def get_name(self): + def get_name(self) -> str: return "AG:IMI:invalid_name" - def get_list(self): + def get_list(self) -> list[str]: return [self.get_class_name(), self.get_name(), self.get_proto()] - def show(self): + def show(self) -> None: print("AG:IMI:invalid_method_item") @@ -2766,7 +2770,7 @@ class EncodedField: :type cm: :class:`ClassManager` """ - def __init__(self, buff, cm): + def __init__(self, buff: BinaryIO, cm:ClassManager) -> None: self.CM = cm self.offset = buff.tell() @@ -2783,19 +2787,19 @@ class EncodedField: self.access_flags_string = None self.loaded = False - def load(self): + def load(self) -> None: if self.loaded: return self.reload() self.loaded = True - def reload(self): + def reload(self) -> None: name = self.CM.get_field(self.field_idx) self.class_name = name[0] self.name = name[2] self.proto = name[1] - def set_init_value(self, value): + def set_init_value(self, value: EncodedValue) -> None: """ Setup the init value object of the field @@ -2804,7 +2808,7 @@ class EncodedField: """ self.init_value = value - def get_init_value(self): + def get_init_value(self) -> EncodedValue: """ Return the init value object of the field @@ -2812,10 +2816,10 @@ class EncodedField: """ return self.init_value - def adjust_idx(self, val): + def adjust_idx(self, val: int) -> None: self.field_idx = self.field_idx_diff + val - def get_field_idx_diff(self): + def get_field_idx_diff(self) -> int: """ Return the index into the field_ids list for the identity of this field (includes the name and descriptor), represented as a difference from the index of previous element in the list @@ -2824,7 +2828,7 @@ class EncodedField: """ return self.field_idx_diff - def get_field_idx(self): + def get_field_idx(self) -> int: """ Return the real index of the method @@ -2832,7 +2836,7 @@ class EncodedField: """ return self.field_idx - def get_access_flags(self): + def get_access_flags(self) -> int: """ Return the access flags of the field @@ -2840,7 +2844,7 @@ class EncodedField: """ return self.access_flags - def get_class_name(self): + def get_class_name(self) -> str: """ Return the class name of the field @@ -2850,7 +2854,7 @@ class EncodedField: self.load() return self.class_name - def get_descriptor(self): + def get_descriptor(self) -> str: """ Return the descriptor of the field @@ -2862,7 +2866,7 @@ class EncodedField: self.load() return self.proto - def get_name(self): + def get_name(self) -> str: """ Return the name of the field @@ -2872,7 +2876,7 @@ class EncodedField: self.load() return self.name - def get_access_flags_string(self): + def get_access_flags_string(self) -> str: """ Return the access flags string of the field @@ -2892,21 +2896,21 @@ class EncodedField: self.access_flags_string = "0x{:06x}".format(self.get_access_flags()) return self.access_flags_string - def set_name(self, value): + def set_name(self, value: str) -> None: self.CM.set_hook_field_name(self, value) self.reload() - def get_obj(self): + def get_obj(self) -> list: return [] - def get_raw(self): + def get_raw(self) -> bytes: return writeuleb128(self.CM, self.field_idx_diff) + writeuleb128(self.CM, self.access_flags) - def get_size(self): + def get_size(self) -> bytes: return len(self.get_raw()) - def show(self): + def show(self) -> None: """ Display the information (with a pretty print) about the field """ @@ -2936,7 +2940,7 @@ class EncodedMethod: :type cm: :class:`ClassManager` """ - def __init__(self, buff, cm): + def __init__(self, buff: BinaryIO, cm:ClassManager) -> None: self.CM = cm self.offset = buff.tell() @@ -2957,10 +2961,10 @@ class EncodedMethod: self.notes = [] self.loaded = False - def adjust_idx(self, val): + def adjust_idx(self, val: int) -> None: self.method_idx = self.method_idx_diff + val - def get_method_idx(self): + def get_method_idx(self) -> int: """ Return the real index of the method @@ -2968,7 +2972,7 @@ class EncodedMethod: """ return self.method_idx - def get_method_idx_diff(self): + def get_method_idx_diff(self) -> int: """ Return index into the method_ids list for the identity of this method (includes the name and descriptor), represented as a difference from the index of previous element in the lis @@ -2977,7 +2981,7 @@ class EncodedMethod: """ return self.method_idx_diff - def get_access_flags(self): + def get_access_flags(self) -> int: """ Return the access flags of the method @@ -2985,7 +2989,7 @@ class EncodedMethod: """ return self.access_flags - def get_code_off(self): + def get_code_off(self) -> int: """ Return the offset from the start of the file to the code structure for this method, or 0 if this method is either abstract or native @@ -2994,7 +2998,7 @@ class EncodedMethod: """ return self.code_off - def get_address(self): + def get_address(self) -> int: """ Return the offset from the start of the file to the code structure for this method, or 0 if this method is either abstract or native @@ -3003,7 +3007,7 @@ class EncodedMethod: """ return self.code_off + 0x10 - def get_access_flags_string(self): + def get_access_flags_string(self) -> str: """ Return the access flags string of the method @@ -3019,13 +3023,13 @@ class EncodedMethod: self.access_flags_string = "0x%x" % self.get_access_flags() return self.access_flags_string - def load(self): + def load(self) -> None: if self.loaded: return self.reload() self.loaded = True - def reload(self): + def reload(self) -> None: v = self.CM.get_method(self.method_idx) # TODO this can probably be more elegant: # get_method returns an array with the already resolved types. @@ -3043,7 +3047,7 @@ class EncodedMethod: self.code = self.CM.get_code(self.code_off) - def get_locals(self): + def get_locals(self) -> int: """ Get the number of local registers used by the method @@ -3057,7 +3061,7 @@ class EncodedMethod: return self.code.get_registers_size() - len(params) - 1 - def get_information(self): + def get_information(self) -> dict[str, Union[str,tuple[int,int],list]]: """ Get brief information about the method's register use, parameters and return type. @@ -3103,7 +3107,7 @@ class EncodedMethod: return info - def each_params_by_register(self, nb, proto): + def each_params_by_register(self, nb: int, proto: str) -> None: """ From the Dalvik Bytecode documentation: @@ -3140,16 +3144,16 @@ class EncodedMethod: self.get_access_flags_string(), self.get_code_off()) @property - def full_name(self): + def full_name(self) -> str: """Return class_name + name + descriptor, separated by spaces (no access flags""" return ' '.join([self.class_name, self.name, self.get_descriptor()]) @property - def descriptor(self): + def descriptor(self) -> str: """Get the descriptor of the method""" return self.get_descriptor() - def get_short_string(self): + def get_short_string(self) -> str: """ Return a shorter formatted String which encodes this method. The returned name has the form: @@ -3163,7 +3167,7 @@ class EncodedMethod: :return: str """ - def _fmt_classname(cls): + def _fmt_classname(cls) -> str: arr = "" # Test for arrays while cls.startswith("["): @@ -3185,7 +3189,7 @@ class EncodedMethod: desc = "({}){}".format(''.join(params), _fmt_classname(ret)) return "{cls} {meth} {desc}".format(cls=clsname, meth=self.get_name(), desc=desc) - def show_info(self): + def show_info(self) -> None: """ Display the basic information about the method """ @@ -3194,7 +3198,7 @@ class EncodedMethod: self.get_class_name(), self.get_name(), self.get_descriptor(), self.get_access_flags_string())) - def show(self): + def show(self) -> None: """ Display the information (with a pretty print) about the method """ @@ -3210,7 +3214,7 @@ class EncodedMethod: - def show_notes(self): + def show_notes(self) -> None: """ Display the notes about the method """ @@ -3220,7 +3224,7 @@ class EncodedMethod: bytecode._PrintNote(i) bytecode._PrintSubBanner() - def source(self): + def source(self) -> str: """ Return the source code of this method @@ -3228,10 +3232,10 @@ class EncodedMethod: """ self.CM.decompiler_ob.display_source(self) - def get_source(self): + def get_source(self) -> str: return self.CM.decompiler_ob.get_source_method(self) - def get_length(self): + def get_length(self) -> int: """ Return the length of the associated code of the method @@ -3241,7 +3245,7 @@ class EncodedMethod: return self.code.get_length() return 0 - def get_code(self): + def get_code(self) -> Union[DalvikCode,None]: """ Return the code object associated to the method @@ -3252,13 +3256,13 @@ class EncodedMethod: self.load() return self.code - def is_cached_instructions(self): + def is_cached_instructions(self) -> bool: if self.code is None: return False return self.code.get_bc().is_cached_instructions() - def get_instructions(self): + def get_instructions(self) -> Iterator[Instruction]: """ Get the instructions @@ -3268,7 +3272,7 @@ class EncodedMethod: return [] return self.get_code().get_bc().get_instructions() - def get_instructions_idx(self): + def get_instructions_idx(self) -> Iterator[tuple[int,Instruction]]: """ Iterate over all instructions of the method, but also return the current index. This is the same as using :meth:`get_instructions` and adding the instruction length @@ -3284,7 +3288,7 @@ class EncodedMethod: yield idx, ins idx += ins.get_length() - def set_instructions(self, instructions): + def set_instructions(self, instructions: list[Instruction]) -> None: """ Set the instructions @@ -3295,7 +3299,7 @@ class EncodedMethod: return [] return self.code.get_bc().set_instructions(instructions) - def get_instruction(self, idx, off=None): + def get_instruction(self, idx, off:Union[int,None]=None) -> Iterator[Instruction]: """ Get a particular instruction by using (default) the index of the address if specified @@ -3310,7 +3314,7 @@ class EncodedMethod: return self.get_code().get_bc().get_instruction(idx, off) return None - def get_debug(self): + def get_debug(self) -> DebugInfoItem: """ Return the debug object associated to this method @@ -3320,7 +3324,7 @@ class EncodedMethod: return None return self.get_code().get_debug() - def get_descriptor(self): + def get_descriptor(self) -> str: """ Return the descriptor of the method A method descriptor will have the form (A A A ...)R @@ -3346,7 +3350,7 @@ class EncodedMethod: self.load() return self.proto - def get_class_name(self): + def get_class_name(self) -> str: """ Return the class name of the method @@ -3356,7 +3360,7 @@ class EncodedMethod: self.load() return self.class_name - def get_name(self): + def get_name(self) -> str: """ Return the name of the method @@ -3366,10 +3370,10 @@ class EncodedMethod: self.load() return self.name - def get_triple(self): + def get_triple(self) -> tuple[str,str,str]: return self.CM.get_method_ref(self.method_idx).get_triple() - def add_inote(self, msg, idx, off=None): + def add_inote(self, msg:str, idx:int, off:Union[int,None]=None) -> None: """ Add a message to a specific instruction by using (default) the index of the address if specified @@ -3383,7 +3387,7 @@ class EncodedMethod: if self.code is not None: self.code.add_inote(msg, idx, off) - def add_note(self, msg): + def add_note(self, msg:str) -> None: """ Add a message to this method @@ -3427,7 +3431,7 @@ class ClassDataItem: :type cm: :class:`ClassManager` """ - def __init__(self, buff, cm): + def __init__(self, buff: BinaryIO, cm:ClassManager) -> None: self.CM = cm self.offset = buff.tell() @@ -3536,7 +3540,7 @@ class ClassDataItem: return [x for x in self.static_fields] + [x for x in self.instance_fields] - def set_off(self, off): + def set_off(self, off:int): self.offset = off def set_static_fields(self, value): @@ -3633,7 +3637,7 @@ class ClassDefItem: :type cm: :class:`ClassManager` """ - def __init__(self, buff, cm): + def __init__(self, buff: BinaryIO, cm:ClassManager) -> None: self.CM = cm self.offset = buff.tell() @@ -3657,7 +3661,7 @@ class ClassDefItem: self.reload() - def reload(self): + def reload(self) -> None: self.name = self.CM.get_type(self.class_idx) self.sname = self.CM.get_type(self.superclass_idx) self.interfaces = self.CM.get_type_list(self.interfaces_off) @@ -3680,7 +3684,7 @@ class ClassDefItem: def __repr__(self): return "".format(self.__str__()) - def get_methods(self): + def get_methods(self) -> list[EncodedMethod]: """ Return all EncodedMethods of this class @@ -3690,7 +3694,7 @@ class ClassDefItem: return self.class_data_item.get_methods() return [] - def get_fields(self): + def get_fields(self) -> list[EncodedField]: """ Return all EncodedFields of this class @@ -3700,11 +3704,11 @@ class ClassDefItem: return self.class_data_item.get_fields() return [] - def _get_annotation_type_ids(self): + def _get_annotation_type_ids(self) -> list[EncodedAnnotation]: """ Get the EncodedAnnotations from this class - :rtype: Iterator[EncodedAnnotation] + :rtype: list[EncodedAnnotation] """ if self.annotations_directory_item is None: return [] @@ -3719,18 +3723,18 @@ class ClassDefItem: return [annotation.get_annotation_item().annotation for annotation in annotation_off_item] - def get_annotations(self): + def get_annotations(self) -> list[str]: """ Returns the class names of the annotations of this class. For example, if the class is marked as :code:`@Deprecated`, this will return :code:`['Ljava/lang/Deprecated;']`. - :rtype: Iterator[str] + :rtype: list[str] """ return [self.CM.get_type(x.get_type_idx()) for x in self._get_annotation_type_ids()] - def get_class_idx(self): + def get_class_idx(self) -> int: """ Return the index into the type_ids list for this class @@ -3738,7 +3742,7 @@ class ClassDefItem: """ return self.class_idx - def get_access_flags(self): + def get_access_flags(self) -> int: """ Return the access flags for the class (public, final, etc.) @@ -3746,7 +3750,7 @@ class ClassDefItem: """ return self.access_flags - def get_superclass_idx(self): + def get_superclass_idx(self) -> int: """ Return the index into the type_ids list for the superclass @@ -3754,7 +3758,7 @@ class ClassDefItem: """ return self.superclass_idx - def get_interfaces_off(self): + def get_interfaces_off(self) -> int: """ Return the offset from the start of the file to the list of interfaces, or 0 if there are none @@ -3762,7 +3766,7 @@ class ClassDefItem: """ return self.interfaces_off - def get_source_file_idx(self): + def get_source_file_idx(self) -> int: """ Return the index into the string_ids list for the name of the file containing the original source for (at least most of) this class, or the special value NO_INDEX to represent a lack of this information @@ -3771,7 +3775,7 @@ class ClassDefItem: """ return self.source_file_idx - def get_annotations_off(self): + def get_annotations_off(self) -> int: """ Return the offset from the start of the file to the annotations structure for this class, or 0 if there are no annotations on this class. @@ -3780,7 +3784,7 @@ class ClassDefItem: """ return self.annotations_off - def get_class_data_off(self): + def get_class_data_off(self) -> int: """ Return the offset from the start of the file to the associated class data for this item, or 0 if there is no class data for this class @@ -3789,7 +3793,7 @@ class ClassDefItem: """ return self.class_data_off - def get_static_values_off(self): + def get_static_values_off(self) -> int: """ Return the offset from the start of the file to the list of initial values for static fields, or 0 if there are none (and all static fields are to be initialized with 0 or null) @@ -3798,7 +3802,7 @@ class ClassDefItem: """ return self.static_values_off - def get_class_data(self): + def get_class_data(self) -> ClassDataItem: """ Return the associated class_data_item @@ -3806,7 +3810,7 @@ class ClassDefItem: """ return self.class_data_item - def get_name(self): + def get_name(self) -> str: """ Return the name of this class @@ -3814,7 +3818,7 @@ class ClassDefItem: """ return self.name - def get_superclassname(self): + def get_superclassname(self) -> str: """ Return the name of the super class @@ -3822,7 +3826,7 @@ class ClassDefItem: """ return self.sname - def get_interfaces(self): + def get_interfaces(self) -> list[str]: """ Return the names of the interfaces @@ -3830,7 +3834,7 @@ class ClassDefItem: """ return self.interfaces - def get_access_flags_string(self): + def get_access_flags_string(self) -> str: """ Return the access flags string of the class @@ -3844,7 +3848,7 @@ class ClassDefItem: self.access_flags_string = "0x%x" % self.get_access_flags() return self.access_flags_string - def show(self): + def show(self) -> None: bytecode._PrintSubBanner("Class Def Item") bytecode._PrintDefault( "name=%s, sname=%s, interfaces=%s, access_flags=%s\n" % @@ -3859,18 +3863,18 @@ class ClassDefItem: for method in self.get_methods(): method.show() - def source(self): + def source(self) -> None: """ - Return the source code of the entire class + Print the source code of the entire class :rtype: string """ self.CM.decompiler_ob.display_all(self) - def get_source(self): + def get_source(self) -> str: return self.CM.decompiler_ob.get_source_class(self) - def get_source_ext(self): + def get_source_ext(self) -> list[tuple[str, list]]: return self.CM.decompiler_ob.get_source_class_ext(self) def get_ast(self): @@ -3922,7 +3926,7 @@ class ClassHDefItem: :type cm: :class:`ClassManager` """ - def __init__(self, size, buff, cm): + def __init__(self, size:int, buff: BinaryIO, cm:ClassManager) -> None: self.CM = cm self.offset = buff.tell() @@ -3937,19 +3941,19 @@ class ClassHDefItem: buff.seek(idx + calcsize("8I")) - def set_off(self, off): + def set_off(self, off:int) -> None: self.offset = off - def get_off(self): + def get_off(self) -> int: return self.offset - def get_class_idx(self, idx): + def get_class_idx(self, idx: int) -> ClassDefItem: for i in self.class_def: if i.get_class_idx() == idx: return i return None - def get_method(self, name_class, name_method): + def get_method(self, name_class: str, name_method: str) -> list[EncodedMethod]: l = [] for i in self.class_def: @@ -3957,23 +3961,22 @@ class ClassHDefItem: for j in i.get_methods(): if j.get_name() == name_method: l.append(j) - return l - def get_names(self): + def get_names(self) -> list[str]: return [x.get_name() for x in self.class_def] - def show(self): + def show(self) -> None: for i in self.class_def: i.show() - def get_obj(self): + def get_obj(self) -> list[ClassDefItem]: return [i for i in self.class_def] - def get_raw(self): + def get_raw(self) -> bytes: return b''.join(i.get_raw() for i in self.class_def) - def get_length(self): + def get_length(self) -> int: length = 0 for i in self.class_def: length += i.get_length() @@ -3990,12 +3993,12 @@ class EncodedTypeAddrPair: :type cm: :class:`ClassManager` """ - def __init__(self, cm, buff): + def __init__(self, cm: ClassManager, buff: BinaryIO) -> None: self.CM = cm self.type_idx = readuleb128(cm, buff) self.addr = readuleb128(cm, buff) - def get_type_idx(self): + def get_type_idx(self) -> int: """ Return the index into the type_ids list for the type of the exception to catch @@ -4003,7 +4006,7 @@ class EncodedTypeAddrPair: """ return self.type_idx - def get_addr(self): + def get_addr(self) -> int: """ Return the bytecode address of the associated exception handler @@ -4011,18 +4014,18 @@ class EncodedTypeAddrPair: """ return self.addr - def get_obj(self): + def get_obj(self) -> list: return [] - def show(self): + def show(self) -> None: bytecode._PrintSubBanner("Encoded Type Addr Pair") bytecode._PrintDefault("type_idx=%d addr=%x\n" % (self.type_idx, self.addr)) - def get_raw(self): + def get_raw(self) -> bytearray: return writeuleb128(self.CM, self.type_idx) + writeuleb128(self.CM, self.addr) - def get_length(self): + def get_length(self) -> int: return len(self.get_raw()) @@ -4036,7 +4039,7 @@ class EncodedCatchHandler: :type cm: :class:`ClassManager` """ - def __init__(self, buff, cm): + def __init__(self, buff: BinaryIO, cm:ClassManager) -> None: self.CM = cm self.offset = buff.tell() @@ -4050,7 +4053,7 @@ class EncodedCatchHandler: if self.size <= 0: self.catch_all_addr = readuleb128(cm, buff) - def get_size(self): + def get_size(self) -> int: """ Return the number of catch types in this list @@ -4058,7 +4061,7 @@ class EncodedCatchHandler: """ return self.size - def get_handlers(self): + def get_handlers(self) -> list[EncodedTypeAddrPair]: """ Return the stream of abs(size) encoded items, one for each caught type, in the order that the types should be tested. @@ -4066,7 +4069,7 @@ class EncodedCatchHandler: """ return self.handlers - def get_catch_all_addr(self): + def get_catch_all_addr(self) -> int: """ Return the bytecode address of the catch-all handler. This element is only present if size is non-positive. @@ -4074,13 +4077,13 @@ class EncodedCatchHandler: """ return self.catch_all_addr - def get_off(self): + def get_off(self) -> int: return self.offset - def set_off(self, off): + def set_off(self, off:int) -> None: self.offset = off - def show(self): + def show(self) -> None: bytecode._PrintSubBanner("Encoded Catch Handler") bytecode._PrintDefault("size=%d\n" % self.size) @@ -4090,7 +4093,7 @@ class EncodedCatchHandler: if self.size <= 0: bytecode._PrintDefault("catch_all_addr=%x\n" % self.catch_all_addr) - def get_raw(self): + def get_raw(self) -> bytearray: """ :rtype: bytearray """ @@ -4104,7 +4107,7 @@ class EncodedCatchHandler: return buff - def get_length(self): + def get_length(self) -> int: length = len(writesleb128(self.CM, self.size)) for i in self.handlers: @@ -4126,14 +4129,14 @@ class EncodedCatchHandlerList: :type cm: :class:`ClassManager` """ - def __init__(self, buff, cm): + def __init__(self, buff: BinaryIO, cm:ClassManager) -> None: self.CM = cm self.offset = buff.tell() self.size = readuleb128(cm, buff) self.list = [EncodedCatchHandler(buff, cm) for _ in range(self.size)] - def get_size(self): + def get_size(self) -> int: """ Return the size of this list, in entries @@ -4141,7 +4144,7 @@ class EncodedCatchHandlerList: """ return self.size - def get_list(self): + def get_list(self) -> list[EncodedCatchHandler]: """ Return the actual list of handler lists, represented directly (not as offsets), and concatenated sequentially @@ -4149,23 +4152,23 @@ class EncodedCatchHandlerList: """ return self.list - def show(self): + def show(self) -> None: bytecode._PrintSubBanner("Encoded Catch Handler List") bytecode._PrintDefault("size=%d\n" % self.size) for i in self.list: i.show() - def get_off(self): + def get_off(self) -> int: return self.offset - def set_off(self, off): + def set_off(self, off:int) -> None: self.offset = off - def get_obj(self): + def get_obj(self) -> bytearray: return writeuleb128(self.CM, self.size) - def get_raw(self): + def get_raw(self) -> bytearray: """ :rtype: bytearray """ @@ -4175,7 +4178,7 @@ class EncodedCatchHandlerList: buff += i.get_raw() return buff - def get_length(self): + def get_length(self) -> int: length = len(self.get_obj()) for i in self.list: @@ -4183,7 +4186,7 @@ class EncodedCatchHandlerList: return length -def get_kind(cm, kind, value): +def get_kind(cm:ClassManager, kind: int, value: int) -> str: """ Return the value of the 'kind' argument @@ -4271,7 +4274,7 @@ class Instruction: length = 0 OP = 0 - def get_kind(self): + def get_kind(self) -> int: """ Return the 'kind' argument of the instruction @@ -4284,7 +4287,7 @@ class Instruction: return DALVIK_OPCODES_OPTIMIZED[self.OP][1][1] return DALVIK_OPCODES_FORMAT[self.OP][1][1] - def get_name(self): + def get_name(self) -> str: """ Return the mnemonic of the instruction @@ -4294,7 +4297,7 @@ class Instruction: return DALVIK_OPCODES_OPTIMIZED[self.OP][1][0] return DALVIK_OPCODES_FORMAT[self.OP][1][0] - def get_op_value(self): + def get_op_value(self) -> int: """ Return the numerical value of the opcode @@ -4302,7 +4305,7 @@ class Instruction: """ return self.OP - def get_literals(self): + def get_literals(self) -> list: """ Return the associated literals @@ -4310,7 +4313,7 @@ class Instruction: """ return [] - def show(self, idx): + def show(self, idx: int) -> None: """ Print the instruction @@ -4318,7 +4321,7 @@ class Instruction: """ print(self.get_name() + " " + self.get_output(idx), end=' ') - def show_buff(self, idx): + def show_buff(self, idx:int) -> str: """ Return the display of the instruction @@ -4326,7 +4329,7 @@ class Instruction: """ return self.get_output(idx) - def get_translated_kind(self): + def get_translated_kind(self) -> str: """ Return the translated value of the 'kind' argument @@ -4334,7 +4337,7 @@ class Instruction: """ return get_kind(self.cm, self.get_kind(), self.get_ref_kind()) - def get_output(self, idx=-1): + def get_output(self, idx:int=-1) -> str: """ Return an additional output of the instruction @@ -4342,7 +4345,7 @@ class Instruction: """ return "" - def get_operands(self, idx=-1): + def get_operands(self, idx:int=-1) -> list: """ Return all operands @@ -4353,7 +4356,7 @@ class Instruction: """ return [] - def get_length(self): + def get_length(self) -> int: """ Return the length of the instruction in bytes @@ -4377,7 +4380,7 @@ class Instruction: """ raise Exception("not implemented") - def get_hex(self): + def get_hex(self) -> str: """ Returns a HEX String, separated by spaces every byte @@ -4393,7 +4396,7 @@ class Instruction: return "{} {}".format(self.get_name(), self.get_output()) # FIXME Better name - def disasm(self): + def disasm(self) -> str: """Some small line for disassembly view""" s = binascii.hexlify(self.get_raw()).decode('ascii') byteview = " ".join(s[i:i + 4] for i in range(0, len(s), 4)) @@ -4408,7 +4411,7 @@ class FillArrayData: """ # FIXME: why is this not a subclass of Instruction? - def __init__(self, cm, buff): + def __init__(self, cm: ClassManager, buff: BinaryIO) -> None: self.OP = 0x0 self.notes = [] self.CM = cm @@ -4424,24 +4427,24 @@ class FillArrayData: self.data = buff[self.format_general_size:self.format_general_size + buf_len] - def add_note(self, msg): + def add_note(self, msg: str) -> None: """ Add a note to this instruction :param msg: the message - :type msg: objects (string) + :type msg: string """ self.notes.append(msg) - def get_notes(self): + def get_notes(self) -> list[str]: """ Get all notes from this instruction - :rtype: a list of objects + :rtype: a list of string notes """ return self.notes - def get_op_value(self): + def get_op_value(self) -> int: """ Get the value of the opcode @@ -4449,7 +4452,7 @@ class FillArrayData: """ return self.ident - def get_data(self): + def get_data(self) -> bytes: """ Return the data of this instruction (the payload) @@ -4457,7 +4460,7 @@ class FillArrayData: """ return self.data - def get_output(self, idx=-1): + def get_output(self, idx:int=-1) -> str: """ Return an additional output of the instruction @@ -4473,7 +4476,7 @@ class FillArrayData: return buff - def get_operands(self, idx=-1): + def get_operands(self, idx: int=-1) -> tuple[Operand, str]: # FIXME: not sure of binascii is the right choise here, # but before it was repr(), which lead to weird outputs of bytearrays if isinstance(self.get_data(), bytearray): @@ -4481,10 +4484,10 @@ class FillArrayData: else: return [(Operand.RAW, repr(self.get_data()))] - def get_formatted_operands(self): + def get_formatted_operands(self) -> None: return None - def get_name(self): + def get_name(self) -> str: """ Return the name of the instruction @@ -4492,7 +4495,7 @@ class FillArrayData: """ return "fill-array-data-payload" - def show_buff(self, pos): + def show_buff(self, pos:int) -> str: """ Return the display of the instruction @@ -4504,13 +4507,13 @@ class FillArrayData: buff += "\\x%02x" % self.data[i] return buff - def show(self, pos): + def show(self, pos) -> None: """ Print the instruction """ print(self.show_buff(pos), end=' ') - def get_length(self): + def get_length(self) -> int: """ Return the length of the instruction @@ -4518,17 +4521,17 @@ class FillArrayData: """ return ((self.size * self.element_width + 1) // 2 + 4) * 2 - def get_raw(self): + def get_raw(self) -> bytes: return self.CM.packer["2HI"].pack(self.ident, self.element_width, self.size) + self.data - def get_hex(self): + def get_hex(self) -> str: """ Returns a HEX String, separated by spaces every byte """ s = binascii.hexlify(self.get_raw()).decode("ascii") return " ".join(s[i:i + 2] for i in range(0, len(s), 2)) - def disasm(self): + def disasm(self) -> str: # FIXME: return self.show_buff(None) @@ -4541,7 +4544,7 @@ class SparseSwitch: """ # FIXME: why is this not a subclass of Instruction? - def __init__(self, cm, buff): + def __init__(self, cm: ClassManager, buff: bytes) -> None: self.OP = 0x0 self.notes = [] self.CM = cm @@ -4562,16 +4565,16 @@ class SparseSwitch: self.targets.append(cm.packer["l"].unpack(buff[idx:idx + 4])[0]) idx += 4 - def add_note(self, msg): + def add_note(self, msg:str) -> None: """ Add a note to this instruction :param msg: the message - :type msg: objects (string) + :type msg: string """ self.notes.append(msg) - def get_notes(self): + def get_notes(self) -> list[str]: """ Get all notes from this instruction @@ -4579,7 +4582,7 @@ class SparseSwitch: """ return self.notes - def get_op_value(self): + def get_op_value(self) -> int: """ Get the value of the opcode @@ -4587,26 +4590,26 @@ class SparseSwitch: """ return self.ident - def get_keys(self): + def get_keys(self) -> list[int]: """ Return the keys of the instruction - :rtype: a list of long + :rtype: a list of long (integer) """ return self.keys - def get_values(self): + def get_values(self) -> list[int]: return self.get_keys() - def get_targets(self): + def get_targets(self) -> list[int]: """ Return the targets (address) of the instruction - :rtype: a list of long + :rtype: a list of long (integer) """ return self.targets - def get_output(self, idx=-1): + def get_output(self, idx:int=-1) -> str: """ Return an additional output of the instruction @@ -4614,7 +4617,7 @@ class SparseSwitch: """ return " ".join("%x" % i for i in self.keys) - def get_operands(self, idx=-1): + def get_operands(self, idx:int=-1) -> str: """ Return an additional output of the instruction @@ -4622,10 +4625,10 @@ class SparseSwitch: """ return [] - def get_formatted_operands(self): + def get_formatted_operands(self) -> None: return None - def get_name(self): + def get_name(self) -> str: """ Return the name of the instruction @@ -4633,7 +4636,7 @@ class SparseSwitch: """ return "sparse-switch-payload" - def show_buff(self, pos): + def show_buff(self, pos:int) -> str: """ Return the display of the instruction @@ -4645,26 +4648,26 @@ class SparseSwitch: return buff - def show(self, pos): + def show(self, pos) -> None: """ Print the instruction """ print(self.show_buff(pos), end=' ') - def get_length(self): + def get_length(self) -> int: return self.format_general_size + (self.size * calcsize(' bytes: return self.CM.packer["2H"].pack(self.ident, self.size) + b''.join(self.CM.packer["l"].pack(i) for i in self.keys) + b''.join(self.CM.packer["l"].pack(i) for i in self.targets) - def get_hex(self): + def get_hex(self) -> str: """ Returns a HEX String, separated by spaces every byte """ s = binascii.hexlify(self.get_raw()).decode('ascii') return " ".join(s[i:i + 2] for i in range(0, len(s), 2)) - def disasm(self): + def disasm(self) -> str: # FIXME: return self.show_buff(None) @@ -4677,7 +4680,7 @@ class PackedSwitch: """ # FIXME: why is this not a subclass of Instruction? - def __init__(self, cm, buff): + def __init__(self, cm: ClassManager, buff: bytes) -> None: self.OP = 0x0 self.notes = [] self.CM = cm @@ -4700,16 +4703,16 @@ class PackedSwitch: self.targets.append(cm.packer["l"].unpack(buff[idx:idx + 4])[0]) idx += 4 - def add_note(self, msg): + def add_note(self, msg: str) -> None: """ Add a note to this instruction :param msg: the message - :type msg: objects (string) + :type msg: string """ self.notes.append(msg) - def get_notes(self): + def get_notes(self) -> list[str]: """ Get all notes from this instruction @@ -4717,7 +4720,7 @@ class PackedSwitch: """ return self.notes - def get_op_value(self): + def get_op_value(self) -> int: """ Get the value of the opcode @@ -4725,26 +4728,26 @@ class PackedSwitch: """ return self.ident - def get_keys(self): + def get_keys(self) -> list[int]: """ Return the keys of the instruction - :rtype: a list of long + :rtype: a list of long (integer) """ return [(self.first_key + i) for i in range(0, len(self.targets))] - def get_values(self): + def get_values(self) -> list[int]: return self.get_keys() - def get_targets(self): + def get_targets(self) -> list[int]: """ Return the targets (address) of the instruction - :rtype: a list of long + :rtype: a list of long (integer) """ return self.targets - def get_output(self, idx=-1): + def get_output(self, idx:int=-1) -> str: """ Return an additional output of the instruction @@ -4754,18 +4757,18 @@ class PackedSwitch: return " ".join("%x" % (self.first_key + i) for i in range(0, len(self.targets))) - def get_operands(self, idx=-1): + def get_operands(self, idx:int=-1) -> list: """ Return an additional output of the instruction - :rtype: string + :rtype: list """ return [] - def get_formatted_operands(self): + def get_formatted_operands(self) -> None: return None - def get_name(self): + def get_name(self) -> str: """ Return the name of the instruction @@ -4773,7 +4776,7 @@ class PackedSwitch: """ return "packed-switch-payload" - def show_buff(self, pos): + def show_buff(self, pos:int) -> str: """ Return the display of the instruction @@ -4787,26 +4790,26 @@ class PackedSwitch: return buff - def show(self, pos): + def show(self, pos: int) -> None: """ Print the instruction """ print(self.show_buff(pos), end=' ') - def get_length(self): + def get_length(self) -> int: return self.format_general_size + (self.size * calcsize(' bytes: return self.CM.packer["2Hi"].pack(self.ident, self.size, self.first_key) + b''.join(self.CM.packer["l"].pack(i) for i in self.targets) - def get_hex(self): + def get_hex(self) -> bytes: """ Returns a HEX String, separated by spaces every byte """ s = binascii.hexlify(self.get_raw()).decode('ascii') return " ".join(s[i:i + 2] for i in range(0, len(s), 2)) - def disasm(self): + def disasm(self) -> str: # FIXME: return self.show_buff(None) @@ -4817,7 +4820,7 @@ class Instruction35c(Instruction): """ length = 6 - def __init__(self, cm, buff): + def __init__(self, cm: ClassManager, buff: bytes) -> None: super().__init__() self.cm = cm @@ -4831,7 +4834,7 @@ class Instruction35c(Instruction): self.E = (i16b >> 8) & 0xf self.F = (i16b >> 12) & 0xf - def get_output(self, idx=-1): + def get_output(self, idx:int=-1) -> str: kind = get_kind(self.cm, self.get_kind(), self.BBBB) if self.A == 0: @@ -4851,7 +4854,7 @@ class Instruction35c(Instruction): return '' - def get_operands(self, idx=-1): + def get_operands(self, idx:int=-1) -> list[tuple]: l = [] kind = get_kind(self.cm, self.get_kind(), self.BBBB) @@ -4879,10 +4882,10 @@ class Instruction35c(Instruction): return l - def get_ref_kind(self): + def get_ref_kind(self) -> int: return self.BBBB - def get_raw(self): + def get_raw(self) -> bytes: return self.cm.packer["3H"].pack((self.A << 12) | (self.G << 8) | self.OP, self.BBBB, (self.F << 12) | (self.E << 8) | (self.D << 4) | self.C) @@ -4894,7 +4897,7 @@ class Instruction10x(Instruction): length = 2 - def __init__(self, cm, buff): + def __init__(self, cm: ClassManager, buff: bytes) -> None: super().__init__() self.cm = cm @@ -4902,7 +4905,7 @@ class Instruction10x(Instruction): if padding != 0: raise InvalidInstruction('High byte of opcode with format 10x must be zero!') - def get_raw(self): + def get_raw(self) -> bytes: return self.cm.packer["H"].pack(self.OP) @@ -4912,7 +4915,7 @@ class Instruction21h(Instruction): """ length = 4 - def __init__(self, cm, buff): + def __init__(self, cm:ClassManager, buff: bytes) -> None: super().__init__() self.cm = cm @@ -4928,16 +4931,16 @@ class Instruction21h(Instruction): # Unknown opcode? self.BBBB = self.__BBBB - def get_output(self, idx=-1): + def get_output(self, idx:int=-1) -> str: return "v{}, {}".format(self.AA, self.BBBB) - def get_operands(self, idx=-1): + def get_operands(self, idx:int=-1) -> list[tuple[Operand, int]]: return [(Operand.REGISTER, self.AA), (Operand.LITERAL, self.BBBB)] - def get_literals(self): + def get_literals(self) -> list[int]: return [self.BBBB] - def get_raw(self): + def get_raw(self) -> bytes: return self.cm.packer["Hh"].pack((self.AA << 8) | self.OP, self.__BBBB) @@ -4947,7 +4950,7 @@ class Instruction11n(Instruction): """ length = 2 - def __init__(self, cm, buff): + def __init__(self, cm: ClassManager, buff: bytes) -> None: super().__init__() self.cm = cm @@ -4956,16 +4959,16 @@ class Instruction11n(Instruction): # Sign extension not required self.B = i8 >> 4 - def get_output(self, idx=-1): + def get_output(self, idx:int=-1) -> str: return "v{}, {}".format(self.A, self.B) - def get_operands(self, idx=-1): + def get_operands(self, idx:int=-1) -> list[tuple[Operand, int]]: return [(Operand.REGISTER, self.A), (Operand.LITERAL, self.B)] - def get_literals(self): + def get_literals(self) -> list[int]: return [self.B] - def get_raw(self): + def get_raw(self) -> bytes: return self.cm.packer["h"].pack((self.B << 12) | (self.A << 8) | self.OP) @@ -4976,32 +4979,32 @@ class Instruction21c(Instruction): length = 4 - def __init__(self, cm, buff): + def __init__(self, cm: ClassManager, buff: bytes) -> None: super().__init__() self.cm = cm self.OP, self.AA, self.BBBB = cm.packer["BBH"].unpack(buff[:self.length]) - def get_output(self, idx=-1): + def get_output(self, idx:int=-1) -> str: kind = get_kind(self.cm, self.get_kind(), self.BBBB) if self.get_kind() == Kind.STRING: kind = '"{}"'.format(kind) return "v{}, {}".format(self.AA, kind) - def get_operands(self, idx=-1): + def get_operands(self, idx:int=-1) -> list[tuple[Operand, int, int]]: kind = get_kind(self.cm, self.get_kind(), self.BBBB) return [(Operand.REGISTER, self.AA), (self.get_kind() + Operand.KIND, self.BBBB, kind)] - def get_ref_kind(self): + def get_ref_kind(self) -> int: return self.BBBB - def get_string(self): + def get_string(self) -> str: return get_kind(self.cm, self.get_kind(), self.BBBB) - def get_raw_string(self): + def get_raw_string(self) -> str: return get_kind(self.cm, Kind.RAW_STRING, self.BBBB) - def get_raw(self): + def get_raw(self) -> bytes: return self.cm.packer["2H"].pack((self.AA << 8) | self.OP, self.BBBB) @@ -5012,23 +5015,23 @@ class Instruction21s(Instruction): length = 4 - def __init__(self, cm, buff): + def __init__(self, cm: ClassManager, buff: bytes) -> bytes: super().__init__() self.cm = cm # BBBB is a signed int (16bit) self.OP, self.AA, self.BBBB = self.cm.packer["BBh"].unpack(buff[:self.length]) - def get_output(self, idx=-1): + def get_output(self, idx:int=-1) -> str: return "v{}, {}".format(self.AA, self.BBBB) - def get_operands(self, idx=-1): + def get_operands(self, idx:int=-1) -> list[tuple[Operand, int]]: return [(Operand.REGISTER, self.AA), (Operand.LITERAL, self.BBBB)] - def get_literals(self): + def get_literals(self) -> list[int]: return [self.BBBB] - def get_raw(self): + def get_raw(self) -> bytes: return self.cm.packer["BBh"].pack(self.OP, self.AA, self.BBBB) @@ -5039,7 +5042,7 @@ class Instruction22c(Instruction): length = 4 - def __init__(self, cm, buff): + def __init__(self, cm: ClassManager, buff: bytes) -> None: super().__init__() self.cm = cm @@ -5048,19 +5051,19 @@ class Instruction22c(Instruction): self.A = (i16 >> 8) & 0xf self.B = (i16 >> 12) & 0xf - def get_output(self, idx=-1): + def get_output(self, idx:int=-1): kind = get_kind(self.cm, self.get_kind(), self.CCCC) return "v{}, v{}, {}".format(self.A, self.B, kind) - def get_operands(self, idx=-1): + def get_operands(self, idx:int=-1) -> list[tuple[Operand, int]]: kind = get_kind(self.cm, self.get_kind(), self.CCCC) return [(Operand.REGISTER, self.A), (Operand.REGISTER, self.B), (self.get_kind() + Operand.KIND, self.CCCC, kind)] - def get_ref_kind(self): + def get_ref_kind(self) -> int: return self.CCCC - def get_raw(self): + def get_raw(self) -> int: return self.cm.packer["2H"].pack((self.B << 12) | (self.A << 8) | self.OP, self.CCCC) @@ -5071,7 +5074,7 @@ class Instruction22cs(Instruction): length = 4 - def __init__(self, cm, buff): + def __init__(self, cm: ClassManager, buff: bytes) -> str: super().__init__() self.cm = cm @@ -5080,19 +5083,19 @@ class Instruction22cs(Instruction): self.A = (i16 >> 8) & 0xf self.B = (i16 >> 12) & 0xf - def get_output(self, idx=-1): + def get_output(self, idx:int=-1) -> str: kind = get_kind(self.cm, self.get_kind(), self.CCCC) return "v{}, v{}, {}".format(self.A, self.B, kind) - def get_operands(self, idx=-1): + def get_operands(self, idx:int=-1) -> list[tuple[Operand, int]]: kind = get_kind(self.cm, self.get_kind(), self.CCCC) return [(Operand.REGISTER, self.A), (Operand.REGISTER, self.B), (self.get_kind() + Operand.KIND, self.CCCC, kind)] - def get_ref_kind(self): + def get_ref_kind(self) -> int: return self.CCCC - def get_raw(self): + def get_raw(self) -> bytes: return self.cm.packer["2H"].pack((self.B << 12) | (self.A << 8) | self.OP, self.CCCC) @@ -5102,22 +5105,22 @@ class Instruction31t(Instruction): """ length = 6 - def __init__(self, cm, buff): + def __init__(self, cm: ClassManager, buff: bytes) -> None: super().__init__() self.cm = cm self.OP, self.AA, self.BBBBBBBB = cm.packer["BBi"].unpack(buff[:self.length]) - def get_output(self, idx=-1): + def get_output(self, idx:int=-1) -> str: return "v{}, {:+08x}h".format(self.AA, self.BBBBBBBB) - def get_operands(self, idx=-1): + def get_operands(self, idx:int=-1) -> list[tuple[Operand, int]]: return [(Operand.REGISTER, self.AA), (Operand.OFFSET, self.BBBBBBBB)] - def get_ref_off(self): + def get_ref_off(self) -> int: return self.BBBBBBBB - def get_raw(self): + def get_raw(self) -> bytes: return self.cm.packer["Hi"].pack((self.AA << 8) | self.OP, self.BBBBBBBB) @@ -5128,24 +5131,24 @@ class Instruction31c(Instruction): length = 6 - def __init__(self, cm, buff): + def __init__(self, cm: ClassManager, buff: bytes) -> None: super().__init__() self.cm = cm self.OP, self.AA, self.BBBBBBBB = cm.packer["BBi"].unpack(buff[:self.length]) - def get_output(self, idx=-1): + def get_output(self, idx:int=-1) -> str: kind = get_kind(self.cm, self.get_kind(), self.BBBBBBBB) return "v{}, {}".format(self.AA, kind) - def get_operands(self, idx=-1): + def get_operands(self, idx:int=-1) -> list[tuple[Operand, int]]: kind = get_kind(self.cm, self.get_kind(), self.BBBBBBBB) return [(Operand.REGISTER, self.AA), (self.get_kind() + Operand.KIND, self.BBBBBBBB, kind)] - def get_ref_kind(self): + def get_ref_kind(self) -> int: return self.BBBBBBBB - def get_string(self): + def get_string(self) -> str: """ Return the string associated to the 'kind' argument @@ -5153,10 +5156,10 @@ class Instruction31c(Instruction): """ return get_kind(self.cm, self.get_kind(), self.BBBBBBBB) - def get_raw_string(self): + def get_raw_string(self) -> str: return get_kind(self.cm, Kind.RAW_STRING, self.BBBBBBBB) - def get_raw(self): + def get_raw(self) -> bytes: return self.cm.packer["HI"].pack((self.AA << 8) | self.OP, self.BBBBBBBB) @@ -5167,7 +5170,7 @@ class Instruction12x(Instruction): length = 2 - def __init__(self, cm, buff): + def __init__(self, cm: ClassManager, buff: bytes) -> None: super().__init__() self.cm = cm @@ -5176,13 +5179,13 @@ class Instruction12x(Instruction): self.A = (i16 >> 8) & 0xf self.B = (i16 >> 12) & 0xf - def get_output(self, idx=-1): + def get_output(self, idx:int=-1) -> str: return "v{}, v{}".format(self.A, self.B) - def get_operands(self, idx=-1): + def get_operands(self, idx:int=-1) -> list[tuple[Operand, int]]: return [(Operand.REGISTER, self.A), (Operand.REGISTER, self.B)] - def get_raw(self): + def get_raw(self) -> bytes: return self.cm.packer["H"].pack((self.B << 12) | (self.A << 8) | self.OP) @@ -5193,19 +5196,19 @@ class Instruction11x(Instruction): length = 2 - def __init__(self, cm, buff): + def __init__(self, cm: ClassManager, buff: bytes) -> None: super().__init__() self.cm = cm self.OP, self.AA = cm.packer["BB"].unpack(buff[:self.length]) - def get_output(self, idx=-1): + def get_output(self, idx:int=-1) -> str: return "v{}".format(self.AA) - def get_operands(self, idx=-1): + def get_operands(self, idx:int=-1) -> list[tuple[Operand, int]]: return [(Operand.REGISTER, self.AA)] - def get_raw(self): + def get_raw(self) -> bytes: return self.cm.packer["H"].pack((self.AA << 8) | self.OP) @@ -5216,23 +5219,23 @@ class Instruction51l(Instruction): length = 10 - def __init__(self, cm, buff): + def __init__(self, cm: ClassManager, buff: bytes) -> None: super().__init__() self.cm = cm # arbitrary double-width (64-bit) constant self.OP, self.AA, self.BBBBBBBBBBBBBBBB = cm.packer["BBq"].unpack(buff[:self.length]) - def get_output(self, idx=-1): + def get_output(self, idx:int=-1) -> str: return "v{}, {}".format(self.AA, self.BBBBBBBBBBBBBBBB) - def get_operands(self, idx=-1): + def get_operands(self, idx:int=-1) -> list[tuple[Operand, int]]: return [(Operand.REGISTER, self.AA), (Operand.LITERAL, self.BBBBBBBBBBBBBBBB)] - def get_literals(self): + def get_literals(self) -> list[int]: return [self.BBBBBBBBBBBBBBBB] - def get_raw(self): + def get_raw(self) -> bytes: return self.cm.packer["BBq"].pack(self.OP, self.AA, self.BBBBBBBBBBBBBBBB) @@ -5242,7 +5245,7 @@ class Instruction31i(Instruction): """ length = 6 - def __init__(self, cm, buff): + def __init__(self, cm: ClassManager, buff: bytes) -> None: super().__init__() self.cm = cm @@ -5251,17 +5254,17 @@ class Instruction31i(Instruction): # 0x14 // const vAA, #+BBBBBBBB: arbitrary 32-bit constant # 0x17 // const-wide/32 vAA, #+BBBBBBBB: signed int (32 bits) - def get_output(self, idx=-1): + def get_output(self, idx:int=-1) -> str: #FIXME: on const-wide/32: it is actually a register pair vAA:vAA+1! return "v{}, {}".format(self.AA, self.BBBBBBBB) - def get_operands(self, idx=-1): + def get_operands(self, idx:int=-1) -> list[tuple[Operand, int]]: return [(Operand.REGISTER, self.AA), (Operand.LITERAL, self.BBBBBBBB)] - def get_literals(self): + def get_literals(self) -> list[int]: return [self.BBBBBBBB] - def get_raw(self): + def get_raw(self) -> bytes: return self.cm.packer["BBi"].pack(self.OP, self.AA, self.BBBBBBBB) @@ -5272,19 +5275,19 @@ class Instruction22x(Instruction): length = 4 - def __init__(self, cm, buff): + def __init__(self, cm: ClassManager, buff: bytes) -> None: super().__init__() self.cm = cm self.OP, self.AA, self.BBBB = cm.packer["BBH"].unpack(buff[:self.length]) - def get_output(self, idx=-1): + def get_output(self, idx:int=-1) -> str: return "v{}, v{}".format(self.AA, self.BBBB) - def get_operands(self, idx=-1): + def get_operands(self, idx:int=-1) -> list[tuple[Operand, int]]: return [(Operand.REGISTER, self.AA), (Operand.REGISTER, self.BBBB)] - def get_raw(self): + def get_raw(self) -> bytes: return self.cm.packer["2H"].pack((self.AA << 8) | self.OP, self.BBBB) @@ -5295,21 +5298,21 @@ class Instruction23x(Instruction): length = 4 - def __init__(self, cm, buff): + def __init__(self, cm: ClassManager, buff: bytes) -> None: super().__init__() self.cm = cm self.OP, self.AA, self.BB, self.CC = cm.packer["BBBB"].unpack(buff[:self.length]) - def get_output(self, idx=-1): + def get_output(self, idx:int=-1) -> str: return "v{}, v{}, v{}".format(self.AA, self.BB, self.CC) - def get_operands(self, idx=-1): + def get_operands(self, idx:int=-1) -> list[tuple[Operand, int]]: return [(Operand.REGISTER, self.AA), (Operand.REGISTER, self.BB), (Operand.REGISTER, self.CC)] - def get_raw(self): + def get_raw(self) -> bytes: return self.cm.packer["2H"].pack((self.AA << 8) | self.OP, (self.CC << 8) | self.BB) @@ -5320,7 +5323,7 @@ class Instruction20t(Instruction): length = 4 - def __init__(self, cm, buff): + def __init__(self, cm: ClassManager, buff: bytes) -> None: super().__init__() self.cm = cm @@ -5328,17 +5331,17 @@ class Instruction20t(Instruction): if padding != 0: raise InvalidInstruction('High byte of opcode with format 20t must be zero!') - def get_output(self, idx=-1): + def get_output(self, idx:int=-1) -> str: # Offset is in 16bit units return "{:+04x}h".format(self.AAAA) - def get_operands(self, idx=-1): + def get_operands(self, idx:int=-1) -> list[tuple[Operand, int]]: return [(Operand.OFFSET, self.AAAA)] - def get_ref_off(self): + def get_ref_off(self) -> int: return self.AAAA - def get_raw(self): + def get_raw(self) -> bytes: return self.cm.packer["Hh"].pack(self.OP, self.AAAA) @@ -5348,22 +5351,22 @@ class Instruction21t(Instruction): """ length = 4 - def __init__(self, cm, buff): + def __init__(self, cm: ClassManager, buff: bytes) -> None: super().__init__() self.cm = cm self.OP, self.AA, self.BBBB = cm.packer["BBh"].unpack(buff[:self.length]) - def get_output(self, idx=-1): + def get_output(self, idx:int=-1) -> str: return "v{}, {:+04x}h".format(self.AA, self.BBBB) - def get_operands(self, idx=-1): + def get_operands(self, idx:int=-1) -> list[tuple[Operand, int]]: return [(Operand.REGISTER, self.AA), (Operand.OFFSET, self.BBBB)] - def get_ref_off(self): + def get_ref_off(self) -> int: return self.BBBB - def get_raw(self): + def get_raw(self) -> bytes: return self.cm.packer["Hh"].pack((self.AA << 8) | self.OP, self.BBBB) @@ -5374,23 +5377,23 @@ class Instruction10t(Instruction): length = 2 - def __init__(self, cm, buff): + def __init__(self, cm: ClassManager, buff: bytes) -> None: super().__init__() self.cm = cm self.OP, self.AA = cm.packer["Bb"].unpack(buff[:self.length]) - def get_output(self, idx=-1): + def get_output(self, idx:int=-1) -> str: # Offset is given in 16bit units return "{:+02x}h".format(self.AA) - def get_operands(self, idx=-1): + def get_operands(self, idx:int=-1) -> list[tuple[Operand, int]]: return [(Operand.OFFSET, self.AA)] - def get_ref_off(self): + def get_ref_off(self) -> int: return self.AA - def get_raw(self): + def get_raw(self) -> bytes: return self.cm.packer["Bb"].pack(self.OP, self.AA) @@ -5401,7 +5404,7 @@ class Instruction22t(Instruction): length = 4 - def __init__(self, cm, buff): + def __init__(self, cm: ClassManager, buff: bytes) -> None: super().__init__() self.cm = cm @@ -5410,17 +5413,17 @@ class Instruction22t(Instruction): self.A = (i16 >> 8) & 0xf self.B = (i16 >> 12) & 0xf - def get_output(self, idx=-1): + def get_output(self, idx:int=-1) -> str: return "v{}, v{}, {:+04x}h".format(self.A, self.B, self.CCCC) - def get_operands(self, idx=-1): + def get_operands(self, idx:int=-1) -> list[tuple[Operand, int]]: return [(Operand.REGISTER, self.A), (Operand.REGISTER, self.B), (Operand.OFFSET, self.CCCC)] - def get_ref_off(self): + def get_ref_off(self) -> int: return self.CCCC - def get_raw(self): + def get_raw(self) -> bytes: return self.cm.packer["Hh"].pack((self.B << 12) | (self.A << 8) | self.OP, self.CCCC) @@ -5431,7 +5434,7 @@ class Instruction22s(Instruction): length = 4 - def __init__(self, cm, buff): + def __init__(self, cm: ClassManager, buff: bytes) -> None: super().__init__() self.cm = cm @@ -5440,17 +5443,17 @@ class Instruction22s(Instruction): self.A = (i16 >> 8) & 0xf self.B = (i16 >> 12) & 0xf - def get_output(self, idx=-1): + def get_output(self, idx:int=-1) -> str: return "v{}, v{}, {}".format(self.A, self.B, self.CCCC) - def get_operands(self, idx=-1): + def get_operands(self, idx:int=-1) -> list[tuple[Operand, int]]: return [(Operand.REGISTER, self.A), (Operand.REGISTER, self.B), (Operand.LITERAL, self.CCCC)] - def get_literals(self): + def get_literals(self) -> list[int]: return [self.CCCC] - def get_raw(self): + def get_raw(self) -> bytes: return self.cm.packer["Hh"].pack((self.B << 12) | (self.A << 8) | self.OP, self.CCCC) @@ -5461,23 +5464,23 @@ class Instruction22b(Instruction): length = 4 - def __init__(self, cm, buff): + def __init__(self, cm: ClassManager, buff: bytes) -> None: super().__init__() self.cm = cm self.OP, self.AA, self.BB, self.CC = cm.packer["BBBb"].unpack(buff[:self.length]) - def get_output(self, idx=-1): + def get_output(self, idx:int=-1) -> str: return "v{}, v{}, {}".format(self.AA, self.BB, self.CC) - def get_operands(self, idx=-1): + def get_operands(self, idx:int=-1) -> list[tuple[Operand, int]]: return [(Operand.REGISTER, self.AA), (Operand.REGISTER, self.BB), (Operand.LITERAL, self.CC)] - def get_literals(self): + def get_literals(self) -> list[int]: return [self.CC] - def get_raw(self): + def get_raw(self) -> bytes: return self.cm.packer["Hh"].pack((self.AA << 8) | self.OP, (self.CC << 8) | self.BB) @@ -5488,7 +5491,7 @@ class Instruction30t(Instruction): length = 6 - def __init__(self, cm, buff): + def __init__(self, cm: ClassManager, buff: bytes) -> None: super().__init__() self.cm = cm @@ -5496,16 +5499,16 @@ class Instruction30t(Instruction): if padding != 0: raise InvalidInstruction('High byte of opcode with format 30t must be zero!') - def get_output(self, idx=-1): + def get_output(self, idx:int=-1) -> str: return "{:+08x}h".format(self.AAAAAAAA) - def get_operands(self, idx=-1): + def get_operands(self, idx:int=-1) -> list[tuple[Operand, int]]: return [(Operand.OFFSET, self.AAAAAAAA)] - def get_ref_off(self): + def get_ref_off(self) -> int: return self.AAAAAAAA - def get_raw(self): + def get_raw(self) -> bytes: return self.cm.packer["Hi"].pack(self.OP, self.AAAAAAAA) @@ -5516,7 +5519,7 @@ class Instruction3rc(Instruction): length = 6 - def __init__(self, cm, buff): + def __init__(self, cm: ClassManager, buff: bytes) -> None: super().__init__() self.cm = cm @@ -5524,7 +5527,7 @@ class Instruction3rc(Instruction): self.NNNN = self.CCCC + self.AA - 1 - def get_output(self, idx=-1): + def get_output(self, idx:int=-1) -> str: kind = get_kind(self.cm, self.get_kind(), self.BBBB) if self.CCCC == self.NNNN: @@ -5532,16 +5535,16 @@ class Instruction3rc(Instruction): else: return "v{} ... v{}, {}".format(self.CCCC, self.NNNN, kind) - def get_operands(self, idx=-1): + def get_operands(self, idx:int=-1) -> list[tuple[Operand, int]]: kind = get_kind(self.cm, self.get_kind(), self.BBBB) return [(Operand.REGISTER, i) for i in range(self.CCCC, self.NNNN + 1)] + \ [(self.get_kind() + Operand.KIND, self.BBBB, kind)] - def get_ref_kind(self): + def get_ref_kind(self) -> int: return self.BBBB - def get_raw(self): + def get_raw(self) -> bytes: return self.cm.packer["3H"].pack((self.AA << 8) | self.OP, self.BBBB, self.CCCC) @@ -5552,7 +5555,7 @@ class Instruction32x(Instruction): length = 6 - def __init__(self, cm, buff): + def __init__(self, cm: ClassManager, buff: bytes) -> None: super().__init__() self.cm = cm @@ -5560,13 +5563,13 @@ class Instruction32x(Instruction): if padding != 0: raise InvalidInstruction('High byte of opcode with format 32x must be zero!') - def get_output(self, idx=-1): + def get_output(self, idx:int=-1) -> str: return "v{}, v{}".format(self.AAAA, self.BBBB) - def get_operands(self, idx=-1): + def get_operands(self, idx:int=-1) -> list[tuple[Operand, int]]: return [(Operand.REGISTER, self.AAAA), (Operand.REGISTER, self.BBBB)] - def get_raw(self): + def get_raw(self) -> bytes: return self.cm.packer["3H"].pack(self.OP, self.AAAA, self.BBBB) @@ -5577,19 +5580,19 @@ class Instruction20bc(Instruction): length = 4 - def __init__(self, cm, buff): + def __init__(self, cm: ClassManager, buff: bytes) -> None: super().__init__() self.cm = cm self.OP, self.AA, self.BBBB = cm.packer["BBH"].unpack(buff[:self.length]) - def get_output(self, idx=-1): + def get_output(self, idx:int=-1) -> str: return "{}, {}".format(self.AA, self.BBBB) - def get_operands(self, idx=-1): + def get_operands(self, idx:int=-1) -> list[tuple[Operand, int]]: return [(Operand.LITERAL, self.AA), (Operand.LITERAL, self.BBBB)] - def get_raw(self): + def get_raw(self) -> bytes: return self.cm.packer["2H"].pack((self.AA << 8) | self.OP, self.BBBB) @@ -5600,7 +5603,7 @@ class Instruction35mi(Instruction): length = 6 - def __init__(self, cm, buff): + def __init__(self, cm: ClassManager, buff: bytes) -> None: super().__init__() self.cm = cm @@ -5615,7 +5618,7 @@ class Instruction35mi(Instruction): self.E = (i16b >> 8) & 0xf self.F = (i16b >> 12) & 0xf - def get_output(self, idx=-1): + def get_output(self, idx:int=-1) -> str: kind = get_kind(self.cm, self.get_kind(), self.BBBB) if self.A == 1: @@ -5631,7 +5634,7 @@ class Instruction35mi(Instruction): return "v%d, v%d, v%d, v%d, v%d, %s" % (self.C, self.D, self.E, self.F, self.G, kind) - def get_operands(self, idx=-1): + def get_operands(self, idx:int=-1) -> list[tuple[Operand, int]]: l = [] kind = get_kind(self.cm, self.get_kind(), self.BBBB) @@ -5657,10 +5660,10 @@ class Instruction35mi(Instruction): return l - def get_ref_kind(self): + def get_ref_kind(self) -> int: return self.BBBB - def get_raw(self): + def get_raw(self) -> bytes: return self.cm.packer["3H"].pack((self.A << 12) | (self.G << 8) | self.OP, self.BBBB, (self.F << 12) | (self.E << 8) | (self.D << 4) | self.C) @@ -5672,7 +5675,7 @@ class Instruction35ms(Instruction): length = 6 - def __init__(self, cm, buff): + def __init__(self, cm: ClassManager, buff: bytes) -> None: super().__init__() self.cm = cm @@ -5687,7 +5690,7 @@ class Instruction35ms(Instruction): self.E = (i16b >> 8) & 0xf self.F = (i16b >> 12) & 0xf - def get_output(self, idx=-1): + def get_output(self, idx:int=-1) -> str: kind = get_kind(self.cm, self.get_kind(), self.BBBB) if self.A == 1: @@ -5703,7 +5706,7 @@ class Instruction35ms(Instruction): return "v%d, v%d, v%d, v%d, v%d, %s" % (self.C, self.D, self.E, self.F, self.G, kind) - def get_operands(self, idx=-1): + def get_operands(self, idx:int=-1) -> list[tuple[Operand, int]]: l = [] kind = get_kind(self.cm, self.get_kind(), self.BBBB) @@ -5729,10 +5732,10 @@ class Instruction35ms(Instruction): return l - def get_ref_kind(self): + def get_ref_kind(self) -> int: return self.BBBB - def get_raw(self): + def get_raw(self) -> bytes: return self.cm.packer["3H"].pack((self.A << 12) | (self.G << 8) | self.OP, self.BBBB, (self.F << 12) | (self.E << 8) | (self.D << 4) | self.C) @@ -5746,7 +5749,7 @@ class Instruction3rmi(Instruction): length = 6 - def __init__(self, cm, buff): + def __init__(self, cm: ClassManager, buff: bytes) -> None: super().__init__() self.cm = cm @@ -5754,7 +5757,7 @@ class Instruction3rmi(Instruction): self.NNNN = self.CCCC + self.AA - 1 - def get_output(self, idx=-1): + def get_output(self, idx:int=-1) -> str: kind = get_kind(self.cm, self.get_kind(), self.BBBB) if self.CCCC == self.NNNN: @@ -5762,7 +5765,7 @@ class Instruction3rmi(Instruction): else: return "v{} ... v{}, {}".format(self.CCCC, self.NNNN, kind) - def get_operands(self, idx=-1): + def get_operands(self, idx:int=-1) -> list[tuple[Operand, int]]: kind = get_kind(self.cm, self.get_kind(), self.BBBB) if self.CCCC == self.NNNN: @@ -5776,10 +5779,10 @@ class Instruction3rmi(Instruction): l.append((self.get_kind() + Operand.KIND, self.BBBB, kind)) return l - def get_ref_kind(self): + def get_ref_kind(self) -> int: return self.BBBB - def get_raw(self): + def get_raw(self) -> bytes: return self.cm.packer["3H"].pack((self.AA << 8) | self.OP, self.BBBB, self.CCCC) @@ -5792,7 +5795,7 @@ class Instruction3rms(Instruction): length = 6 - def __init__(self, cm, buff): + def __init__(self, cm: ClassManager, buff: bytes) -> None: super().__init__() self.cm = cm @@ -5800,7 +5803,7 @@ class Instruction3rms(Instruction): self.NNNN = self.CCCC + self.AA - 1 - def get_output(self, idx=-1): + def get_output(self, idx:int=-1) -> str: kind = get_kind(self.cm, self.get_kind(), self.BBBB) if self.CCCC == self.NNNN: @@ -5808,7 +5811,7 @@ class Instruction3rms(Instruction): else: return "v{} ... v{}, {}".format(self.CCCC, self.NNNN, kind) - def get_operands(self, idx=-1): + def get_operands(self, idx:int=-1) -> list[tuple[Operand, int]]: kind = get_kind(self.cm, self.get_kind(), self.BBBB) if self.CCCC == self.NNNN: @@ -5822,10 +5825,10 @@ class Instruction3rms(Instruction): l.append((self.get_kind() + Operand.KIND, self.BBBB, kind)) return l - def get_ref_kind(self): + def get_ref_kind(self) -> int: return self.BBBB - def get_raw(self): + def get_raw(self) -> bytes: return self.cm.packer["3H"].pack((self.AA << 8) | self.OP, self.BBBB, self.CCCC) @@ -5838,7 +5841,7 @@ class Instruction41c(Instruction): length = 8 - def __init__(self, cm, buff): + def __init__(self, cm: ClassManager, buff: bytes) -> None: super().__init__() self.cm = cm @@ -5846,19 +5849,19 @@ class Instruction41c(Instruction): self.BBBBBBBB, \ self.AAAA = cm.packer["HIH"].unpack(buff[:self.length]) - def get_output(self, idx=-1): + def get_output(self, idx:int=-1) -> str: kind = get_kind(self.cm, self.get_kind(), self.BBBBBBBB) return "v{}, {}".format(self.AAAA, kind) - def get_operands(self, idx=-1): + def get_operands(self, idx:int=-1) -> list[tuple[Operand, int]]: kind = get_kind(self.cm, self.get_kind(), self.BBBBBBBB) return [(Operand.REGISTER, self.AAAA), (self.get_kind() + Operand.KIND, self.BBBBBBBB, kind)] - def get_ref_kind(self): + def get_ref_kind(self) -> int: return self.BBBBBBBB - def get_raw(self): + def get_raw(self) -> bytes: return self.cm.packer["HIH"].pack(self.OP, self.BBBBBBBB, self.AAAA) @@ -5871,7 +5874,7 @@ class Instruction40sc(Instruction): length = 8 - def __init__(self, cm, buff): + def __init__(self, cm: ClassManager, buff: bytes) -> None: super().__init__() self.cm = cm @@ -5879,19 +5882,19 @@ class Instruction40sc(Instruction): self.BBBBBBBB, \ self.AAAA = cm.packer["HIH"].unpack(buff[:self.length]) - def get_output(self, idx=-1): + def get_output(self, idx:int=-1) -> str: kind = get_kind(self.cm, self.get_kind(), self.BBBBBBBB) return "{}, {}".format(self.AAAA, kind) - def get_operands(self, idx=-1): + def get_operands(self, idx:int=-1) -> list[tuple[Operand, int]]: kind = get_kind(self.cm, self.get_kind(), self.BBBBBBBB) return [(Operand.LITERAL, self.AAAA), (self.get_kind() + Operand.KIND, self.BBBBBBBB, kind)] - def get_ref_kind(self): + def get_ref_kind(self) -> int: return self.BBBBBBBB - def get_raw(self): + def get_raw(self) -> bytes: return self.cm.packer["HIH"].pack(self.OP, self.BBBBBBBB, self.AAAA) @@ -5904,7 +5907,7 @@ class Instruction52c(Instruction): length = 10 - def __init__(self, cm, buff): + def __init__(self, cm: ClassManager, buff: bytes) -> None: super().__init__() self.cm = cm @@ -5915,19 +5918,19 @@ class Instruction52c(Instruction): self.AAAA, \ self.BBBB = cm.packer["HI2H"].unpack(buff[:self.length]) - def get_output(self, idx=-1): + def get_output(self, idx:int=-1) -> str: kind = get_kind(self.cm, self.get_kind(), self.CCCCCCCC) return "v{}, v{}, {}".format(self.AAAA, self.BBBB, kind) - def get_operands(self, idx=-1): + def get_operands(self, idx:int=-1) -> list[tuple[Operand, int]]: kind = get_kind(self.cm, self.get_kind(), self.CCCCCCCC) return [(Operand.LITERAL, self.AAAA), (Operand.LITERAL, self.BBBB), (self.get_kind() + Operand.KIND, self.CCCCCCCC, kind)] - def get_ref_kind(self): + def get_ref_kind(self) -> int: return self.CCCCCCCC - def get_raw(self): + def get_raw(self) -> bytes: return self.cm.packer["HI2H"].pack(self.OP, self.CCCCCCCC, self.AAAA, self.BBBB) @@ -5940,7 +5943,7 @@ class Instruction5rc(Instruction): length = 10 - def __init__(self, cm, buff): + def __init__(self, cm: ClassManager, buff: bytes) -> None: super().__init__() self.cm = cm @@ -5951,7 +5954,7 @@ class Instruction5rc(Instruction): self.NNNN = self.CCCC + self.AAAA - 1 - def get_output(self, idx=-1): + def get_output(self, idx:int=-1) -> str: kind = get_kind(self.cm, self.get_kind(), self.BBBBBBBB) if self.CCCC == self.NNNN: @@ -5959,7 +5962,7 @@ class Instruction5rc(Instruction): else: return "v{} ... v{}, {}".format(self.CCCC, self.NNNN, kind) - def get_operands(self, idx=-1): + def get_operands(self, idx:int=-1) -> list[tuple[Operand, int]]: kind = get_kind(self.cm, self.get_kind(), self.BBBBBBBB) if self.CCCC == self.NNNN: @@ -5973,10 +5976,10 @@ class Instruction5rc(Instruction): l.append((self.get_kind() + Operand.KIND, self.BBBBBBBB, kind)) return l - def get_ref_kind(self): + def get_ref_kind(self) -> int: return self.BBBBBBBB - def get_raw(self): + def get_raw(self) -> bytes: return self.cm.packer["HI2H"].pack(self.OP, self.BBBBBBBB, self.AAAA, self.CCCC) @@ -5984,7 +5987,7 @@ class Instruction45cc(Instruction): length = 8 # FIXME!!! - def __init__(self, cm, buff): + def __init__(self, cm: ClassManager, buff: bytes) -> None: super().__init__() self.cm = cm @@ -6002,7 +6005,7 @@ class Instruction45cc(Instruction): self.F = (reg2 & 0xF000) >> 12 self.E = (reg2 & 0x0F00) >> 8 - def get_raw(self): + def get_raw(self) -> bytes: return self.cm.packer["BBHHH"].pack( self.OP, self.A << 4 | self.G, @@ -6010,7 +6013,7 @@ class Instruction45cc(Instruction): self.F << 12 | self.E << 8 | self.D << 4 | self.C, self.HHHH) - def get_output(self, idx=-1): + def get_output(self, idx:int=-1) -> str: # FIXME get_kind of BBBB (method) and HHHH (proto) if self.A == 1: return 'v{}, {}, {}'.format(self.C, self.BBBB, self.HHHH) @@ -6034,17 +6037,17 @@ class Instruction4rcc(Instruction): length = 8 # FIXME!!! - def __init__(self, cm, buff): + def __init__(self, cm: ClassManager, buff: bytes) -> None: super().__init__() self.cm = cm self.OP, self.AA, self.BBBB, self.CCCC, self.HHHH = self.cm.packer['BBHHH'].unpack(buff[:self.get_length()]) self.NNNN = self.AA + self.CCCC - 1 - def get_raw(self): + def get_raw(self) -> bytes: return self.cm.packer['BBHHH'].pack(self.OP, self.AA, self.BBBB, self.CCCC, self.HHHH) - def get_output(self, idx=-1): + def get_output(self, idx:int=-1) -> str: # FIXME get_kind of BBBB (meth) and HHHH (proto) return 'v{} .. v{} {} {}'.format(self.CCCC, self.NNNN, self.BBBB, self.HHHH) @@ -6057,7 +6060,7 @@ class Instruction00x(Instruction): """A class for unused instructions, has zero length and raises an error on initialization""" length = 0 - def __init__(self, cm, buff): + def __init__(self, cm: ClassManager, buff: bytes) -> None: raise InvalidInstruction("Instruction with opcode '0x{:02x}' is unused! This looks like invalid bytecode.".format(buff[0])) @@ -6396,7 +6399,7 @@ DALVIK_OPCODES_OPTIMIZED = { } -def get_instruction(cm, op_value, buff): +def get_instruction(cm: ClassManager, op_value: int, buff: bytearray) -> Instruction: """ Return the :class:`Instruction` for the given opcode @@ -6413,7 +6416,7 @@ def get_instruction(cm, op_value, buff): raise InvalidInstruction("Invalid Instruction for '0x{:02x}': {}".format(op_value, repr(buff))) -def get_optimized_instruction(cm, op_value, buff): +def get_optimized_instruction(cm: ClassManager, op_value: int, buff: bytearray) -> Instruction: try: return DALVIK_OPCODES_OPTIMIZED[op_value][0](cm, buff) except struct.error: @@ -6421,7 +6424,7 @@ def get_optimized_instruction(cm, op_value, buff): raise InvalidInstruction("Invalid Instruction for '0x{:04x}': {}".format(op_value, repr(buff))) -def get_instruction_payload(op_value, cm, buff): +def get_instruction_payload(op_value: int, cm: ClassManager, buff: bytearray) -> Union[PackedSwitch,SparseSwitch,FillArrayData]: try: return DALVIK_OPCODES_PAYLOAD[op_value][0](cm, buff) except struct.error: @@ -6435,7 +6438,7 @@ class LinearSweepAlgorithm: """ @staticmethod - def get_instructions(cm, size, insn, idx): + def get_instructions(cm: ClassManager, size: int, insn: bytearray, idx: int) -> Iterator[Instruction]: """ Yields all instructions for the given bytecode sequence. If unknown/corrupt/unused instructions are encountered, @@ -6498,10 +6501,10 @@ class DCode: :param size: the total size of the buffer :type size: int :param buff: a raw buffer where are the instructions - :type buff: string + :type buff: bytes """ - def __init__(self, class_manager, offset, size, buff): + def __init__(self, class_manager: ClassManager, offset: int, size: int, buff: bytes) -> None: self.CM = class_manager self.insn = buff self.offset = offset @@ -6512,7 +6515,7 @@ class DCode: self.idx = 0 - def get_insn(self): + def get_insn(self) -> bytes: """ Get the insn buffer @@ -6520,7 +6523,7 @@ class DCode: """ return self.insn - def set_insn(self, insn): + def set_insn(self, insn: bytes) -> None: """ Set a new raw buffer to disassemble @@ -6530,7 +6533,7 @@ class DCode: self.insn = insn self.size = len(self.insn) - def seek(self, idx): + def seek(self, idx: int) -> None: """ Set the start address of the buffer @@ -6539,12 +6542,12 @@ class DCode: """ self.idx = idx - def is_cached_instructions(self): + def is_cached_instructions(self) -> bool: if self.cached_instructions is not None: return True return False - def set_instructions(self, instructions): + def set_instructions(self, instructions:list[Instruction]) -> None: """ Set the instructions @@ -6553,7 +6556,7 @@ class DCode: """ self.cached_instructions = instructions - def get_instructions(self): + def get_instructions(self) -> Iterator[Instruction]: """ Get the instructions @@ -6567,7 +6570,7 @@ class DCode: for i in self.cached_instructions: yield i - def add_inote(self, msg, idx, off=None): + def add_inote(self, msg: str, idx: int, off:Union[int,None]=None) -> None: """ Add a message to a specific instruction by using (default) the index of the address if specified @@ -6586,7 +6589,7 @@ class DCode: self.notes[idx].append(msg) - def get_instruction(self, idx, off=None): + def get_instruction(self, idx: int, off:Union[int,None]=None) -> Instruction: """ Get a particular instruction by using (default) the index of the address if specified @@ -6603,7 +6606,7 @@ class DCode: self.get_instructions() return self.cached_instructions[idx] - def off_to_pos(self, off): + def off_to_pos(self, off:int) -> int: """ Get the position of an instruction by using the address @@ -6621,7 +6624,7 @@ class DCode: idx += i.get_length() return -1 - def get_ins_off(self, off): + def get_ins_off(self, off:int) -> Instruction: """ Get a particular instruction by using the address @@ -6637,7 +6640,7 @@ class DCode: idx += i.get_length() return None - def show(self): + def show(self) -> None: """ Display (with a pretty print) this object """ @@ -6646,7 +6649,7 @@ class DCode: print("{:8d} (0x{:08x}) {:04x} {:30} {}".format(n, off, i.get_op_value(), i.get_name(), i.get_output(self.idx))) off += i.get_length() - def get_raw(self): + def get_raw(self) -> bytearray: """ Return the raw buffer of this object @@ -6657,7 +6660,7 @@ class DCode: buff += i.get_raw() return buff - def get_length(self): + def get_length(self) -> int: """ Return the length of this object @@ -6671,12 +6674,12 @@ class TryItem: This class represents the try_item format :param buff: a raw buffer where are the try_item format - :type buff: io.BufferedReader + :type buff: BinaryIO.BufferedReader :param cm: the ClassManager :type cm: ClassManager """ - def __init__(self, buff, cm): + def __init__(self, buff: BinaryIO, cm:ClassManager) -> None: self.offset = buff.tell() self.CM = cm @@ -6685,13 +6688,13 @@ class TryItem: self.insn_count, \ self.handler_off = cm.packer["I2H"].unpack(buff.read(8)) - def set_off(self, off): + def set_off(self, off:int) -> None: self.offset = off - def get_off(self): + def get_off(self) -> int: return self.offset - def get_start_addr(self): + def get_start_addr(self) -> int: """ Get the start address of the block of code covered by this entry. The address is a count of 16-bit code units to the start of the first covered instruction. @@ -6699,7 +6702,7 @@ class TryItem: """ return self.start_addr - def get_insn_count(self): + def get_insn_count(self) -> int: """ Get the number of 16-bit code units covered by this entry @@ -6707,7 +6710,7 @@ class TryItem: """ return self.insn_count - def get_handler_off(self): + def get_handler_off(self) -> int: """ Get the offset in bytes from the start of the associated :class:`EncodedCatchHandlerList` to the :class:`EncodedCatchHandler` for this entry. @@ -6715,12 +6718,12 @@ class TryItem: """ return self.handler_off - def get_raw(self): + def get_raw(self) -> bytes: return self.CM.packer["I2H"].pack(self.start_addr, self.insn_count, self.handler_off) - def get_length(self): + def get_length(self) -> int: return len(self.get_raw()) @@ -6729,12 +6732,12 @@ class DalvikCode: This class represents the instructions of a method :param buff: a raw buffer where are the instructions - :type buff: io.BufferedReader + :type buff: BinaryIO.BufferedReader :param cm: the ClassManager :type cm: :class:`ClassManager` object """ - def __init__(self, buff, cm): + def __init__(self, buff: BinaryIO, cm:ClassManager) -> None: self.CM = cm self.offset = buff.tell() @@ -6760,7 +6763,7 @@ class DalvikCode: self.handlers = EncodedCatchHandlerList(buff, self.CM) - def get_registers_size(self): + def get_registers_size(self) -> int: """ Get the number of registers used by this code @@ -6768,7 +6771,7 @@ class DalvikCode: """ return self.registers_size - def get_ins_size(self): + def get_ins_size(self) -> int: """ Get the number of words of incoming arguments to the method that this code is for @@ -6776,7 +6779,7 @@ class DalvikCode: """ return self.ins_size - def get_outs_size(self): + def get_outs_size(self) -> int: """ Get the number of words of outgoing argument space required by this code for method invocation @@ -6784,7 +6787,7 @@ class DalvikCode: """ return self.outs_size - def get_tries_size(self): + def get_tries_size(self) -> int: """ Get the number of :class:`TryItem` for this instance @@ -6792,7 +6795,7 @@ class DalvikCode: """ return self.tries_size - def get_debug_info_off(self): + def get_debug_info_off(self) -> int: """ Get the offset from the start of the file to the debug info (line numbers + local variable info) sequence for this code, or 0 if there simply is no information @@ -6800,7 +6803,7 @@ class DalvikCode: """ return self.debug_info_off - def get_insns_size(self): + def get_insns_size(self) -> int: """ Get the size of the instructions list, in 16-bit code units @@ -6808,7 +6811,7 @@ class DalvikCode: """ return self.insns_size - def get_handlers(self): + def get_handlers(self) -> EncodedCatchHandlerList: """ Get the bytes representing a list of lists of catch types and associated handler addresses. @@ -6816,7 +6819,7 @@ class DalvikCode: """ return self.handlers - def get_tries(self): + def get_tries(self) -> list[TryItem]: """ Get the array indicating where in the code exceptions are caught and how to handle them @@ -6824,7 +6827,7 @@ class DalvikCode: """ return self.tries - def get_debug(self): + def get_debug(self) -> DebugInfoItem: """ Return the associated debug object @@ -6832,7 +6835,7 @@ class DalvikCode: """ return self.CM.get_debug_off(self.debug_info_off) - def get_bc(self): + def get_bc(self) -> DCode: """ Return the associated code object @@ -6840,13 +6843,13 @@ class DalvikCode: """ return self.code - def seek(self, idx): + def seek(self, idx: int) -> None: self.code.seek(idx) - def get_length(self): + def get_length(self) -> int: return self.insns_size - def _begin_show(self): + def _begin_show(self) -> None: logger.debug("registers_size: %d" % self.registers_size) logger.debug("ins_size: %d" % self.ins_size) logger.debug("outs_size: %d" % self.outs_size) @@ -6856,18 +6859,18 @@ class DalvikCode: bytecode._PrintBanner() - def show(self): + def show(self) -> None: self._begin_show() self.code.show() self._end_show() - def _end_show(self): + def _end_show(self) -> None: bytecode._PrintBanner() - def get_obj(self): + def get_obj(self) -> tuple[DCode, list[TryItem], EncodedCatchHandlerList]: return [self.code, self.tries, self.handlers] - def get_raw(self): + def get_raw(self) -> bytearray: """ Get the reconstructed code as bytearray @@ -6894,7 +6897,7 @@ class DalvikCode: return buff - def add_inote(self, msg, idx, off=None): + def add_inote(self, msg: str, idx: int, off:int=None) -> None: """ Add a message to a specific instruction by using (default) the index of the address if specified @@ -6908,22 +6911,22 @@ class DalvikCode: if self.code: return self.code.add_inote(msg, idx, off) - def get_instruction(self, idx, off=None): + def get_instruction(self, idx:int, off:Union[int,None]=None) -> Instruction: if self.code: return self.code.get_instruction(idx, off) - def get_size(self): + def get_size(self) -> int: return len(self.get_raw()) - def set_off(self, off): + def set_off(self, off:int) -> None: self.offset = off - def get_off(self): + def get_off(self) -> int: return self.offset class CodeItem: - def __init__(self, size, buff, cm): + def __init__(self, size:int, buff:bytes, cm:ClassManager) -> None: self.CM = cm self.offset = buff.tell() @@ -6943,19 +6946,19 @@ class CodeItem: self.code.append(x) self.__code_off[x.get_off()] = x - def set_off(self, off): + def set_off(self, off:int) -> None: self.offset = off - def get_off(self): + def get_off(self) -> int: return self.offset - def get_code(self, off): + def get_code(self, off:int) -> DalvikCode: try: return self.__code_off[off] except KeyError: return None - def show(self): + def show(self) -> None: # FIXME workaround for showing the MAP_ITEMS # if m_a is none, we use get_raw. # Otherwise the real code is printed... @@ -6963,16 +6966,16 @@ class CodeItem: bytecode._PrintDefault(binascii.hexlify(self.get_raw()).decode("ASCII")) bytecode._PrintDefault("\n") - def get_obj(self): + def get_obj(self) -> list[DalvikCode]: return [i for i in self.code] - def get_raw(self): + def get_raw(self) -> bytearray: buff = bytearray() for c in self.code: buff += c.get_raw() return buff - def get_length(self): + def get_length(self) -> int: length = 0 for i in self.code: length += i.get_size() @@ -6980,7 +6983,7 @@ class CodeItem: class MapItem: - def __init__(self, buff, cm): + def __init__(self, buff:bytes, cm:ClassManager) -> None: """ Implementation of a map_item, which occours in a map_list @@ -6998,25 +7001,25 @@ class MapItem: self.item = None - def get_off(self): + def get_off(self) -> int: """Gets the offset of the map item itself inside the DEX file""" return self.off - def get_offset(self): + def get_offset(self) -> int: """Gets the offset of the item of the map item""" return self.offset - def get_type(self): + def get_type(self) -> TypeMapItem: return self.type - def get_size(self): + def get_size(self) -> int: """ Returns the number of items found at the location indicated by :meth:`get_offset`. """ return self.size - def parse(self): + def parse(self) -> None: logger.debug("Starting parsing map_item '{}'".format(self.type.name)) started_at = time.time() @@ -7132,7 +7135,7 @@ class MapItem: minutes, seconds = diff // 60, diff % 60 logger.debug("End of parsing map_item '{}'. Required time {:.0f}:{:07.4f}".format(self.type.name, minutes, seconds)) - def show(self): + def show(self) -> None: bytecode._Print("\tMAP_TYPE_ITEM", self.type.name) if self.item is not None: @@ -7142,7 +7145,7 @@ class MapItem: else: self.item.show() - def get_obj(self): + def get_obj(self) -> object: """ Return the associated item itself. Might return None, if :meth:`parse` was not called yet. @@ -7154,7 +7157,7 @@ class MapItem: # alias get_item = get_obj - def get_raw(self): + def get_raw(self) -> bytes: # FIXME why is it necessary to get the offset here agin? We have this # stored?! if isinstance(self.item, list): @@ -7167,10 +7170,10 @@ class MapItem: self.size, self.offset) - def get_length(self): + def get_length(self) -> int: return calcsize("HHII") - def set_item(self, item): + def set_item(self, item: object) -> None: self.item = item @@ -7180,7 +7183,7 @@ class ClassManager: based on their offset or index. """ - def __init__(self, vm): + def __init__(self, vm: DEX) -> None: """ :param DEX vm: the VM to create a ClassManager for """ @@ -7219,7 +7222,7 @@ class ClassManager: def packer(self, p): self.__packer = p - def get_ascii_string(self, s): + def get_ascii_string(self, s: str) -> str: # TODO Remove method try: return s.decode("ascii") @@ -7232,32 +7235,32 @@ class ClassManager: d += "%x" % i return d - def get_odex_format(self): + def get_odex_format(self) -> bool: """Returns True if the underlying VM is ODEX""" return self.odex_format - def get_obj_by_offset(self, offset): + def get_obj_by_offset(self, offset: int) -> object: """ Returnes a object from as given offset inside the DEX file """ return self.__obj_offset[offset] - def get_item_by_offset(self, offset): + def get_item_by_offset(self, offset: int) -> object: return self.__item_offset[offset] - def get_string_by_offset(self, offset): + def get_string_by_offset(self, offset: int) -> object: return self.__strings_off[offset] - def set_decompiler(self, decompiler): + def set_decompiler(self, decompiler: DecompilerDAD) -> None: self.decompiler_ob = decompiler - def set_analysis(self, analysis_dex): + def set_analysis(self, analysis_dex: Analysis) -> None: self.analysis_dex = analysis_dex - def get_analysis(self): + def get_analysis(self) -> Analysis: return self.analysis_dex - def add_type_item(self, type_item, c_item, item): + def add_type_item(self, type_item: TypeMapItem, c_item: MapItem, item: object) -> None: self.__manage_item[type_item] = item self.__obj_offset[c_item.get_off()] = c_item @@ -7281,50 +7284,49 @@ class ClassManager: else: self.__manage_item_off.append(c_item.get_offset()) - def get_code(self, idx): + def get_code(self, idx: int) -> Union[DalvikCode,None]: try: return self.__manage_item[TypeMapItem.CODE_ITEM].get_code(idx) except KeyError: return None - def get_class_data_item(self, off): + def get_class_data_item(self, off:int) -> ClassDataItem : i = self.__classdata_off.get(off) if i is None: logger.warning("unknown class data item @ 0x%x" % off) - return i - def get_encoded_array_item(self, off): + def get_encoded_array_item(self, off:int) -> EncodedArrayItem: for i in self.__manage_item[TypeMapItem.ENCODED_ARRAY_ITEM]: if i.get_off() == off: return i - def get_annotations_directory_item(self, off): + def get_annotations_directory_item(self, off:int) -> AnnotationsDirectoryItem: for i in self.__manage_item[TypeMapItem.ANNOTATIONS_DIRECTORY_ITEM]: if i.get_off() == off: return i - def get_annotation_set_item(self, off): + def get_annotation_set_item(self, off:int) -> AnnotationSetItem: for i in self.__manage_item[TypeMapItem.ANNOTATION_SET_ITEM]: if i.get_off() == off: return i - def get_annotation_off_item(self, off): + def get_annotation_off_item(self, off:int) -> AnnotationOffItem: for i in self.__manage_item[TypeMapItem.ANNOTATION_OFF_ITEM]: if i.get_off() == off: return i - def get_annotation_item(self, off): + def get_annotation_item(self, off:int) -> AnnotationItem: for i in self.__manage_item[TypeMapItem.ANNOTATION_ITEM]: if i.get_off() == off: return i - def get_hiddenapi_class_data_item(self, off): + def get_hiddenapi_class_data_item(self, off:int) -> HiddenApiClassDataItem: for i in self.__manage_item[TypeMapItem.HIDDENAPI_CLASS_DATA_ITEM]: if i.get_off() == off: return i - def get_string(self, idx): + def get_string(self, idx: int) -> str: """ Return a string from the string table at index `idx` @@ -7337,7 +7339,7 @@ class ClassManager: return self.get_raw_string(idx) - def get_raw_string(self, idx): + def get_raw_string(self, idx: int) -> str: """ Return the (unprocessed) string from the string table at index `idx`. @@ -7355,14 +7357,14 @@ class ClassManager: logger.warning("unknown string item @ 0x%x(%d)" % (off, idx)) return "AG:IS: invalid string" - def get_type_list(self, off): + def get_type_list(self, off:int) -> list[str]: if off == 0: return [] i = self.__typelists_off[off] return [type_.get_string() for type_ in i.get_list()] - def get_type(self, idx): + def get_type(self, idx: int) -> str: """ Return the resolved type name based on the index @@ -7377,7 +7379,7 @@ class ClassManager: return "AG:ITI: invalid type" return self.get_string(_type) - def get_type_ref(self, idx): + def get_type_ref(self, idx: int) -> TypeIdItem: """ Returns the string reference ID for a given type ID. @@ -7388,7 +7390,7 @@ class ClassManager: """ return self.__manage_item[TypeMapItem.TYPE_ID_ITEM].get(idx) - def get_proto(self, idx): + def get_proto(self, idx: int) -> list: proto = self.__cached_proto.get(idx) if not proto: proto = self.__manage_item[TypeMapItem.PROTO_ID_ITEM].get(idx) @@ -7397,20 +7399,21 @@ class ClassManager: return [proto.get_parameters_off_value(), proto.get_return_type_idx_value()] - def get_field(self, idx): + def get_field(self, idx: int) -> list[str]: field = self.get_field_ref(idx) - return [field.get_class_name(), field.get_type(), field.get_name()] - - def get_field_ref(self, idx): + # return [field.get_class_name(), field.get_type(), field.get_name()] + return field.get_list() + + def get_field_ref(self, idx: int) -> FieldIdItem: return self.__manage_item[TypeMapItem.FIELD_ID_ITEM].get(idx) - def get_method(self, idx): + def get_method(self, idx: int) -> list[str]: return self.get_method_ref(idx).get_list() - def get_method_ref(self, idx): + def get_method_ref(self, idx: int) -> MethodIdItem: return self.__manage_item[TypeMapItem.METHOD_ID_ITEM].get(idx) - def set_hook_class_name(self, class_def, value): + def set_hook_class_name(self, class_def: ClassDefItem, value: str) -> None: python_export = True _type = self.__manage_item[TypeMapItem.TYPE_ID_ITEM].get( class_def.get_class_idx()) @@ -7435,7 +7438,7 @@ class ClassManager: if python_export: self.vm._create_python_export_class(class_def) - def set_hook_method_name(self, encoded_method, value): + def set_hook_method_name(self, encoded_method: EncodedMethod, value: str) -> None: python_export = True method = self.__manage_item[TypeMapItem.METHOD_ID_ITEM].get( @@ -7484,7 +7487,7 @@ class ClassManager: method.reload() - def set_hook_field_name(self, encoded_field, value): + def set_hook_field_name(self, encoded_field: EncodedField, value: str) -> None: python_export = True field = self.__manage_item[TypeMapItem.FIELD_ID_ITEM].get( @@ -7511,18 +7514,17 @@ class ClassManager: field.reload() - def set_hook_string(self, idx, value): + def set_hook_string(self, idx: int, value: str) -> None: self.hook_strings[idx] = value - def get_next_offset_item(self, idx): + def get_next_offset_item(self, idx: int) -> int: for i in self.__manage_item_off: if i > idx: return i return idx - def get_debug_off(self, off): + def get_debug_off(self, off:int) -> DebugInfoItem: self.buff.seek(off) - return DebugInfoItem(self.buff, self) @@ -7533,7 +7535,7 @@ class MapList: https://source.android.com/devices/tech/dalvik/dex-format#map-list """ - def __init__(self, cm, off, buff): + def __init__(self, cm: ClassManager, off: int, buff: BinaryIO) -> None: self.CM = cm buff.seek(off) @@ -7564,13 +7566,13 @@ class MapList: self.CM.add_type_item(mi.get_type(), mi, c_item) - def get_off(self): + def get_off(self) -> int: return self.offset - def set_off(self, off): + def set_off(self, off:int) -> None: self.offset = off - def get_item_type(self, ttype): + def get_item_type(self, ttype: TypeMapItem) -> object: """ Get a particular item type @@ -7583,7 +7585,7 @@ class MapList: return i.get_item() return None - def show(self): + def show(self) -> None: """ Print with a pretty display the MapList object """ @@ -7594,16 +7596,16 @@ class MapList: # as we do not have the method analysis here... i.show() - def get_obj(self): + def get_obj(self) -> list[object]: return [x.get_obj() for x in self.map_item] - def get_raw(self): + def get_raw(self) -> bytes: return self.CM.packer["I"].pack(self.size) + b''.join(x.get_raw() for x in self.map_item) - def get_class_manager(self): + def get_class_manager(self) -> ClassManager: return self.CM - def get_length(self): + def get_length(self) -> int: return len(self.get_raw()) @@ -7611,7 +7613,7 @@ class DalvikPacker: """ Generic Packer class to unpack bytes based on different endianness """ - def __init__(self, endian_tag): + def __init__(self, endian_tag: int) -> None: if endian_tag == 0x78563412: logger.error("DEX file with byte swapped endian tag is not supported!") raise NotImplementedError("Byte swapped endian tag encountered!") @@ -7642,7 +7644,7 @@ class DEX: :param buff: a string which represents the classes.dex file :param decompiler: associate a decompiler object to display the java source code - :type buff: bytes + :type buff: BinaryIO :type decompiler: object example:: @@ -7650,7 +7652,7 @@ class DEX: d = DEX( read("classes.dex") ) """ - def __init__(self, buff, decompiler=None, config=None, using_api=None): + def __init__(self, buff, decompiler:Union[DecompilerDAD,None]=None, config=None, using_api:Union[int,None]=None) -> None: logger.debug("DEX {} {} {}".format(decompiler, config, using_api)) # to allow to pass apk object ==> we do not need to pass additionally target version @@ -7675,7 +7677,7 @@ class DEX: def _preload(self, buff): pass - def _load(self, buff): + def _load(self, buff) -> None: self.header = HeaderItem(0, self.raw, self.CM) if self.header.map_off == 0: @@ -7694,7 +7696,7 @@ class DEX: self._flush() - def _flush(self): + def _flush(self) -> None: """ Flush all caches Might be used after classes, methods or fields are added. @@ -7709,22 +7711,22 @@ class DEX: self.__cache_all_fields = None @property - def version(self): + def version(self) -> int: """ Returns the version number of the DEX Format """ return self.header.dex_version - def get_api_version(self): + def get_api_version(self) -> int: """ This method returns api version that should be used for loading api specific resources. - :rtype: int + :rtype: string """ return self.api_version - def get_classes_def_item(self): + def get_classes_def_item(self) -> ClassHDefItem: """ This function returns the class def item @@ -7732,7 +7734,7 @@ class DEX: """ return self.classes - def get_methods_id_item(self): + def get_methods_id_item(self) -> MethodHIdItem: """ This function returns the method id item @@ -7740,7 +7742,7 @@ class DEX: """ return self.methods - def get_fields_id_item(self): + def get_fields_id_item(self) -> FieldHIdItem: """ This function returns the field id item @@ -7748,7 +7750,7 @@ class DEX: """ return self.fields - def get_codes_item(self): + def get_codes_item(self) -> CodeItem: """ This function returns the code item @@ -7756,7 +7758,7 @@ class DEX: """ return self.codes - def get_string_data_item(self): + def get_string_data_item(self) -> StringDataItem: """ This function returns the string data item @@ -7764,15 +7766,15 @@ class DEX: """ return self.strings - def get_debug_info_item(self): + # TODO: this returns DebugInfoItemEmpty, as DebugInfoItem never gets set as a MapItem + def get_debug_info_item(self) -> DebugInfoItemEmpty: """ This function returns the debug info item - - :rtype: :class:`DebugInfoItem` object + :rtype: :class:`DebugInfoItemEmpty` object """ return self.debug - def get_header_item(self): + def get_header_item(self) -> HeaderItem: """ This function returns the header item @@ -7780,7 +7782,7 @@ class DEX: """ return self.header - def get_hidden_api(self): + def get_hidden_api(self) -> HiddenApiClassDataItem: """ This function returns the hidden api item (from Android 10) @@ -7788,7 +7790,7 @@ class DEX: """ return self.hidden_api - def get_class_manager(self): + def get_class_manager(self) -> ClassManager: """ This function returns a ClassManager object which allow you to get access to all index references (strings, methods, fields, ....) @@ -7797,18 +7799,18 @@ class DEX: """ return self.CM - def show(self): + def show(self) -> None: """ Show the all information in the object """ self.map_list.show() - def save(self): + def save(self) -> bytes: """ Return the dex (with the modifications) into raw format (fix checksums) (beta: do not use !) - :rtype: string + :rtype: bytes """ l = [] h = {} @@ -7886,11 +7888,11 @@ class DEX: return self.fix_checksums(buff) - def fix_checksums(self, buff): + def fix_checksums(self, buff: bytes) -> bytes: """ Fix a dex format buffer by setting all checksums - :rtype: string + :rtype: bytes """ signature = hashlib.sha1(buff[32:]).digest() @@ -7904,7 +7906,7 @@ class DEX: return buff - def get_cm_field(self, idx): + def get_cm_field(self, idx: int) -> list[str]: """ Get a specific field by using an index @@ -7913,7 +7915,7 @@ class DEX: """ return self.CM.get_field(idx) - def get_cm_method(self, idx): + def get_cm_method(self, idx: int) -> list[str]: """ Get a specific method by using an index @@ -7922,7 +7924,7 @@ class DEX: """ return self.CM.get_method(idx) - def get_cm_string(self, idx): + def get_cm_string(self, idx: int) -> str: """ Get a specific string by using an index @@ -7931,7 +7933,7 @@ class DEX: """ return self.CM.get_raw_string(idx) - def get_cm_type(self, idx): + def get_cm_type(self, idx: int) -> str: """ Get a specific type by using an index @@ -7940,7 +7942,7 @@ class DEX: """ return self.CM.get_type(idx) - def get_classes_names(self, update=False): + def get_classes_names(self, update:bool=False) -> list[str]: """ Return the names of classes @@ -7952,7 +7954,7 @@ class DEX: self.classes_names = [i.get_name() for i in self.get_classes()] return self.classes_names - def get_classes(self): + def get_classes(self) -> list[ClassDefItem]: """ Return all classes @@ -7964,7 +7966,7 @@ class DEX: # There is a rare case that the DEX has no classes return [] - def get_len_classes(self): + def get_len_classes(self) -> int: """ Return the number of classes @@ -7972,7 +7974,7 @@ class DEX: """ return len(self.get_classes()) - def get_class(self, name): + def get_class(self, name: str) -> Union[ClassDefItem, None]: """ Return a specific class @@ -7985,7 +7987,7 @@ class DEX: return i return None - def get_field(self, name): + def get_field(self, name: str) -> list[FieldIdItem]: """get field id item by name :param name: the name of the field (a python regexp) @@ -8001,7 +8003,7 @@ class DEX: l.append(i) return l - def get_fields(self): + def get_fields(self) -> list[FieldIdItem]: """ Return a list of field items @@ -8012,7 +8014,7 @@ class DEX: except AttributeError: return [] - def get_len_fields(self): + def get_len_fields(self) -> int: """ Return the number of fields @@ -8020,7 +8022,7 @@ class DEX: """ return len(self.get_fields()) - def get_encoded_field(self, name): + def get_encoded_field(self, name:str) -> list[EncodedField]: """ Return a list all fields which corresponds to the regexp @@ -8036,7 +8038,7 @@ class DEX: l.append(i) return l - def get_encoded_fields(self): + def get_encoded_fields(self) -> list[EncodedField]: """ Return all field objects @@ -8049,10 +8051,10 @@ class DEX: self.__cache_all_fields.append(j) return self.__cache_all_fields - def get_len_encoded_fields(self): + def get_len_encoded_fields(self) -> int: return len(self.get_encoded_fields()) - def get_field(self, name): + def get_field(self, name: str) -> list[FieldIdItem]: """get field id item by name :param name: the name of the field (a python regexp) @@ -8067,7 +8069,7 @@ class DEX: l.append(i) return l - def get_method(self, name): + def get_method(self, name: str) -> list[MethodIdItem]: """get method id item by name :param name: the name of the field (a python regexp) @@ -8082,7 +8084,7 @@ class DEX: l.append(i) return l - def get_methods(self): + def get_methods(self) -> list[MethodIdItem]: """ Return a list of method items @@ -8093,7 +8095,7 @@ class DEX: except AttributeError: return [] - def get_len_methods(self): + def get_len_methods(self) -> int: """ Return the number of methods @@ -8101,7 +8103,7 @@ class DEX: """ return len(self.get_methods()) - def get_encoded_method(self, name): + def get_encoded_method(self, name:str) -> list[EncodedMethod]: """ Return a list all encoded methods whose name corresponds to the regexp @@ -8116,7 +8118,7 @@ class DEX: l.append(i) return l - def get_encoded_methods(self): + def get_encoded_methods(self) -> list[EncodedMethod]: """ Return all encoded method objects @@ -8129,7 +8131,7 @@ class DEX: self.__cache_all_methods.append(j) return self.__cache_all_methods - def get_len_encoded_methods(self): + def get_len_encoded_methods(self) -> int: """ Return the number of encoded methods @@ -8137,7 +8139,7 @@ class DEX: """ return len(self.get_encoded_methods()) - def get_encoded_method_by_idx(self, idx): + def get_encoded_method_by_idx(self, idx: int) -> Union[EncodedMethod,None]: """ Return a specific encoded method by using an index :param idx: the index of the method @@ -8156,7 +8158,7 @@ class DEX: except KeyError: return None - def get_encoded_method_descriptor(self, class_name, method_name, descriptor): + def get_encoded_method_descriptor(self, class_name: str, method_name: str, descriptor: str) -> Union[EncodedMethod,None]: """ Return the specific encoded method given a class name, method name, and descriptor @@ -8180,7 +8182,7 @@ class DEX: return self.__cache_methods.get(key) - def get_encoded_methods_class_method(self, class_name, method_name): + def get_encoded_methods_class_method(self, class_name: str, method_name: str) -> Union[EncodedMethod,None]: """ Return the specific encoded methods of the class @@ -8196,7 +8198,7 @@ class DEX: return i return None - def get_encoded_methods_class(self, class_name): + def get_encoded_methods_class(self, class_name: str) -> list[EncodedMethod]: """ Return all encoded methods of a specific class by class name @@ -8211,7 +8213,7 @@ class DEX: l.append(i) return l - def get_encoded_fields_class(self, class_name): + def get_encoded_fields_class(self, class_name: str) -> list[EncodedField]: """ Return all encoded fields of a specific class by class name @@ -8226,7 +8228,7 @@ class DEX: l.append(i) return l - def get_encoded_field_descriptor(self, class_name, field_name, descriptor): + def get_encoded_field_descriptor(self, class_name: str, field_name: str, descriptor: str) -> EncodedField: """ Return the specific encoded field given a class name, field name, and descriptor @@ -8251,7 +8253,7 @@ class DEX: return self.__cache_fields.get(key) - def get_strings(self): + def get_strings(self) -> list[str]: """ Return all strings @@ -8262,7 +8264,7 @@ class DEX: """ return [i.get() for i in self.strings] - def get_len_strings(self): + def get_len_strings(self) -> int: """ Return the number of strings @@ -8270,11 +8272,11 @@ class DEX: """ return len(self.get_strings()) - def get_regex_strings(self, regular_expressions): + def get_regex_strings(self, regular_expressions: str) -> Union[list[str],None]: """ Return all target strings matched the regex - :param regular_expressions: the python regex + :param regular_expressions: the python regex string :type regular_expressions: string :rtype: a list of strings matching the regex expression @@ -8287,7 +8289,7 @@ class DEX: str_list.append(i) return str_list - def get_format_type(self): + def get_format_type(self) -> str: """ Return the type @@ -8295,7 +8297,7 @@ class DEX: """ return "DEX" - def create_python_export(self): + def create_python_export(self) -> None: """ Export classes/methods/fields' names in the python namespace """ @@ -8305,10 +8307,10 @@ class DEX: for _class in self.get_classes(): self._create_python_export_class(_class) - def _delete_python_export_class(self, _class): + def _delete_python_export_class(self, _class: ClassDefItem) -> None: self._create_python_export_class(_class, True) - def _create_python_export_class(self, _class, delete=False): + def _create_python_export_class(self, _class: ClassDefItem, delete:bool=False) -> None: if _class is not None: ### Class name = str(bytecode.FormatClassToPython(_class.get_name())) @@ -8323,7 +8325,7 @@ class DEX: self._create_python_export_methods(_class, delete) self._create_python_export_fields(_class, delete) - def _create_python_export_methods(self, _class, delete): + def _create_python_export_methods(self, _class: ClassDefItem, delete) -> None: m = {} for method in _class.get_methods(): if method.get_name() not in m: @@ -8344,7 +8346,7 @@ class DEX: str(bytecode.FormatDescriptorToPython(j.get_descriptor()))) setattr(_class.M, name, j) - def _create_python_export_fields(self, _class, delete): + def _create_python_export_fields(self, _class: ClassDefItem, delete) -> None: f = {} for field in _class.get_fields(): if field.get_name() not in f: @@ -8365,13 +8367,13 @@ class DEX: j.get_descriptor())) setattr(_class.F, name, j) - def set_decompiler(self, decompiler): + def set_decompiler(self, decompiler: DecompilerDAD) -> None: self.CM.set_decompiler(decompiler) - def set_analysis(self, analysis_dex): + def set_analysis(self, analysis_dex: Analysis) -> None: self.CM.set_analysis(analysis_dex) - def disassemble(self, offset, size): + def disassemble(self, offset: int, size: int) -> Iterator[Instruction]: """ Disassembles a given offset in the DEX file @@ -8385,13 +8387,13 @@ class DEX: read_at(self.raw, offset, size)).get_instructions(): yield i - def _get_class_hierarchy(self): + def _get_class_hierarchy(self) -> Node: """ Constructs a tree out of all the classes. The classes are added to this tree by their superclass. :return: - :rtype: androguard.core.bytecode.Node + :rtype: androguard.decompiler.Node """ # Contains the class names as well as their running number ids = dict() @@ -8438,7 +8440,7 @@ class DEX: return Root - def list_classes_hierarchy(self): + def list_classes_hierarchy(self) -> dict[str, list[dict[str, list]]]: """ Get a tree structure of the classes. The parent is always the superclass. @@ -8475,7 +8477,7 @@ class OdexHeaderItem: :param buff: a Buff object string which represents the odex dependencies """ - def __init__(self, buff): + def __init__(self, buff: BinaryIO) -> None: buff.seek(8) self.dex_offset = unpack("=I", buff.read(4))[0] @@ -8487,12 +8489,12 @@ class OdexHeaderItem: self.flags = unpack("=I", buff.read(4))[0] self.padding = unpack("=I", buff.read(4))[0] - def show(self): + def show(self) -> None: print("dex_offset:{:x} dex_length:{:x} deps_offset:{:x} deps_length:{:x} aux_offset:{:x} aux_length:{:x} flags:{:x}".format( self.dex_offset, self.dex_length, self.deps_offset, self.deps_length, self.aux_offset, self.aux_length, self.flags)) - def get_raw(self): + def get_raw(self) -> bytes: return pack("=I", self.dex_offset) + \ pack("=I", self.dex_length) + \ pack("=I", self.deps_offset) + \ @@ -8510,7 +8512,7 @@ class OdexDependencies: :param buff: a Buff object string which represents the odex dependencies """ - def __init__(self, buff): + def __init__(self, buff: BinaryIO) -> None: self.modification_time = unpack("=I", buff.read(4))[0] self.crc = unpack("=I", buff.read(4))[0] self.dalvik_build = unpack("=I", buff.read(4))[0] @@ -8524,7 +8526,7 @@ class OdexDependencies: self.dependencies.append(name_dependency) self.dependency_checksums.append(buff.read(20)) - def get_dependencies(self): + def get_dependencies(self) -> list[str]: """ Return the list of dependencies @@ -8532,7 +8534,7 @@ class OdexDependencies: """ return self.dependencies - def get_raw(self): + def get_raw(self) -> bytes: dependencies = b"" for idx, value in enumerate(self.dependencies): @@ -8551,16 +8553,16 @@ class ODEX(DEX): """ This class can parse an odex file - :param buff: a string which represents the odex file + :param buff: byteswhich represents the odex file :param decompiler: associate a decompiler object to display the java source code - :type buff: string + :type buff: bytes :type decompiler: object :Example: ODEX( read("classes.odex") ) """ - def _preload(self, buff): + def _preload(self, buff: BinaryIO): self.orig_buff = buff self.magic = buff[:8] if self.magic in (ODEX_FILE_MAGIC_35, ODEX_FILE_MAGIC_36, ODEX_FILE_MAGIC_37): @@ -8576,7 +8578,7 @@ class ODEX(DEX): self.set_buff(self.read(self.odex_header.dex_length)) self.seek(0) - def save(self): + def save(self) -> bytes: """ Do not use ! """ @@ -8584,10 +8586,10 @@ class ODEX(DEX): return self.magic + self.odex_header.get_raw( ) + dex_raw + self.dependencies.get_raw() + self.padding - def get_buff(self): + def get_buff(self) -> bytes: return self.magic + self.odex_header.get_raw() + super().get_buff() + self.dependencies.get_raw() + self.padding - def get_dependencies(self): + def get_dependencies(self) -> OdexDependencies: """ Return the odex dependencies object @@ -8595,7 +8597,7 @@ class ODEX(DEX): """ return self.dependencies - def get_format_type(self): + def get_format_type(self) -> str: """ Return the type @@ -8604,7 +8606,7 @@ class ODEX(DEX): return "ODEX" -def get_params_info(nb, proto): +def get_params_info(nb: int, proto: str) -> str: i_buffer = "# Parameters:\n" ret = proto.split(')') @@ -8624,12 +8626,12 @@ def get_params_info(nb, proto): return i_buffer -def get_bytecodes_method(dex_object, ana_object, method): - mx = ana_object.get_method(method) +def get_bytecodes_method(dex_object, analysis_object: Analysis, method: EncodedMethod) -> str: + mx = analysis_object.get_method(method) return get_bytecodes_methodx(method, mx) -def get_bytecodes_methodx(method, mx): +def get_bytecodes_methodx(method: EncodedMethod, mx: MethodAnalysis) -> str: basic_blocks = mx.basic_blocks.gets() i_buffer = "" diff --git a/androguard/decompiler/basic_blocks.py b/androguard/decompiler/basic_blocks.py index 7aecd7ff..7abe83e3 100644 --- a/androguard/decompiler/basic_blocks.py +++ b/androguard/decompiler/basic_blocks.py @@ -16,6 +16,7 @@ # limitations under the License. from collections import defaultdict + from androguard.decompiler.opcode_ins import INSTRUCTION_SET from androguard.decompiler.instruction import MoveExceptionExpression from androguard.decompiler.node import Node @@ -25,7 +26,7 @@ from loguru import logger class BasicBlock(Node): - def __init__(self, name, block_ins): + def __init__(self, name: str, block_ins: list) -> None: super().__init__(name) self.ins = block_ins self.ins_range = None @@ -33,26 +34,26 @@ class BasicBlock(Node): self.var_to_declare = set() self.catch_type = None - def get_ins(self): + def get_ins(self) -> list: return self.ins - def get_loc_with_ins(self): + def get_loc_with_ins(self) -> list: if self.loc_ins is None: self.loc_ins = list(zip(range(*self.ins_range), self.ins)) return self.loc_ins - def remove_ins(self, loc, ins): + def remove_ins(self, loc, ins) -> None: self.ins.remove(ins) self.loc_ins.remove((loc, ins)) - def add_ins(self, new_ins_list): + def add_ins(self, new_ins_list: list) -> None: for new_ins in new_ins_list: self.ins.append(new_ins) def add_variable_declaration(self, variable): self.var_to_declare.add(variable) - def number_ins(self, num): + def number_ins(self, num: int) -> int: last_ins_num = num + len(self.ins) self.ins_range = [num, last_ins_num] self.loc_ins = None diff --git a/androguard/decompiler/dast.py b/androguard/decompiler/dast.py index 0fe6f7f4..e6c0e868 100644 --- a/androguard/decompiler/dast.py +++ b/androguard/decompiler/dast.py @@ -22,7 +22,7 @@ from androguard.core.dex.dex_types import TYPE_DESCRIPTOR from loguru import logger -def array_access(arr, ind): +def array_access(arr, ind) -> list: return ['ArrayAccess', [arr, ind]] @@ -68,7 +68,7 @@ def parenthesis(expr): return ['Parenthesis', [expr]] -def typen(baset, dim): +def typen(baset: str, dim: int) -> list: return ['TypeName', (baset, dim)] @@ -141,7 +141,7 @@ def _append(sb, stmt): sb[2].append(stmt) -def parse_descriptor(desc): +def parse_descriptor(desc: str) -> list: dim = 0 while desc and desc[0] == '[': desc = desc[1:] diff --git a/androguard/decompiler/decompile.py b/androguard/decompiler/decompile.py index 6b48d153..685eed78 100644 --- a/androguard/decompiler/decompile.py +++ b/androguard/decompiler/decompile.py @@ -15,6 +15,15 @@ # See the License for the specific language governing permissions and # limitations under the License. +# Allows type hinting of types not-yet-declared +# in Python >= 3.7 +# see https://peps.python.org/pep-0563/ +from __future__ import annotations +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from androguard.core.analysis.analysis import Analysis, MethodAnalysis + from androguard.core.dex import EncodedField + import struct import sys from collections import defaultdict @@ -48,7 +57,7 @@ from androguard.util import readFile logger.add(sys.stderr, format="{time} {level} {message}", filter="dad", level="INFO") # No seperate DvField class currently -def get_field_ast(field): +def get_field_ast(field: EncodedField) -> dict: triple = field.get_class_name()[1:-1], field.get_name(), field.get_descriptor() expr = None @@ -73,11 +82,11 @@ def get_field_ast(field): class DvMethod: """ This is a wrapper around :class:`~androguard.core.analysis.analysis.MethodAnalysis` and - :class:`~androguard.core.bytecodes.dvm.EncodedMethod` inside the decompiler. + :class:`~androguard.core.dex.EncodedMethod` inside the decompiler. :param androguard.core.analysis.analysis.MethodAnalysis methanalysis: """ - def __init__(self, methanalysis): + def __init__(self, methanalysis: MethodAnalysis) -> None: method = methanalysis.get_method() self.method = method self.start_block = next(methanalysis.get_basic_blocks().get(), None) @@ -119,7 +128,7 @@ class DvMethod: # TODO: use tempfile to create a correct tempfile (cross platform compatible) bytecode.method2png('/tmp/dad/graphs/{}#{}.png'.format(self.cls_name.split('/')[-1][:-1], self.name), methanalysis) - def process(self, doAST=False): + def process(self, doAST:bool=False) -> None: """ Processes the method and decompile the code. @@ -193,7 +202,7 @@ class DvMethod: self.writer = Writer(graph, self) self.writer.write_method() - def get_ast(self): + def get_ast(self) -> dict: """ Returns the AST, if previously was generated by calling :meth:`process` with argument :code:`doAST=True`. @@ -212,15 +221,15 @@ class DvMethod: """ return self.ast - def show_source(self): + def show_source(self) -> None: print(self.get_source()) - def get_source(self): + def get_source(self) -> str: if self.writer: return str(self.writer) return '' - def get_source_ext(self): + def get_source_ext(self) -> list[tuple]: if self.writer: return self.writer.str_ext() return [] @@ -234,13 +243,13 @@ class DvClass: """ This is a wrapper for :class:`~androguard.core.bytecodes.dvm.ClassDefItem` inside the decompiler. - At first, :py:attr:`methods` contains a list of :class:`~androguard.core.bytecodes.dvm.EncodedMethods`, + At first, :py:attr:`methods` contains a list of :class:`~androguard.core.dex.EncodedMethod`, which are successively replaced by :class:`DvMethod` in the process of decompilation. - :param androguard.core.bytecodes.dvm.ClassDefItem dvclass: the class item + :param androguard.core.dex.ClassDefItem dvclass: the class item :param androguard.core.analysis.analysis.Analysis vma: an Analysis object """ - def __init__(self, dvclass, vma): + def __init__(self, dvclass: dex.ClassDefItem, vma: analysis.Analysis) -> None: name = dvclass.get_name() if name.find('/') > 0: pckg, name = name.rsplit('/', 1) @@ -277,10 +286,10 @@ class DvClass: logger.debug('%s (%s, %s)', meth.get_method_idx(), self.name, meth.name) logger.debug('') - def get_methods(self): + def get_methods(self) -> list[dex.EncodedMethod]: return self.methods - def process_method(self, num, doAST=False): + def process_method(self, num: int, doAST:bool=False) -> None: method = self.methods[num] if not isinstance(method, DvMethod): self.methods[num] = DvMethod(self.vma.get_method(method)) @@ -288,7 +297,7 @@ class DvClass: else: method.process(doAST=doAST) - def process(self, doAST=False): + def process(self, doAST:bool=False) -> None: for i in range(len(self.methods)): try: self.process_method(i, doAST=doAST) @@ -296,7 +305,7 @@ class DvClass: # FIXME: too broad exception? logger.warning('Error decompiling method %s: %s', self.methods[i], e) - def get_ast(self): + def get_ast(self) -> dict: fields = [get_field_ast(f) for f in self.fields] methods = [] for m in self.methods: @@ -314,7 +323,7 @@ class DvClass: 'methods': methods, } - def get_source(self): + def get_source(self) -> str: source = [] if not self.inner and self.package: source.append('package %s;\n' % self.package) @@ -362,7 +371,7 @@ class DvClass: source.append('}\n') return ''.join(source) - def get_source_ext(self): + def get_source_ext(self) -> list[tuple[str, list]]: source = [] if not self.inner and self.package: source.append( @@ -434,7 +443,7 @@ class DvClass: source.append(("CLASS_END", [('CLASS_END', '}\n')])) return source - def show_source(self): + def show_source(self) -> None: print(self.get_source()) def __repr__(self): @@ -449,10 +458,10 @@ class DvMachine: The :class:`~androguard.decompiler.decompile.DvMachine` can take either an APK file directly, where all DEX files from the multidex are used, or a single DEX or ODEX file as an argument. - At first, :py:attr:`classes` contains only :class:`~androguard.core.bytecodes.dvm.ClassDefItem` as values. + At first, :py:attr:`classes` contains only :class:`~androguard.core.dex.ClassDefItem` as values. Then these objects are replaced by :class:`DvClass` items successively. """ - def __init__(self, name): + def __init__(self, name: str) -> None: """ :param name: filename to load @@ -475,7 +484,7 @@ class DvMachine: # TODO why not? # util.merge_inner(self.classes) - def get_classes(self): + def get_classes(self) -> list[str]: """ Return a list of classnames contained in this machine. The format of each name is Lxxx; @@ -484,7 +493,7 @@ class DvMachine: """ return list(self.classes.keys()) - def get_class(self, class_name): + def get_class(self, class_name: str) -> DvClass: """ Return the :class:`DvClass` with the given name @@ -493,7 +502,7 @@ class DvMachine: :param str class_name: :return: the class matching on the name - :rtype: DvClass + :rtype: :class:`DvClass` """ for name, klass in self.classes.items(): # TODO why use the name partially? @@ -503,7 +512,7 @@ class DvMachine: dvclass = self.classes[name] = DvClass(klass, self.vma) return dvclass - def process(self): + def process(self) -> None: """ Process all classes inside the machine. @@ -517,7 +526,7 @@ class DvMachine: dvclass = self.classes[name] = DvClass(klass, self.vma) dvclass.process() - def show_source(self): + def show_source(self) -> None: """ Calls `show_source` on all classes inside the machine. This prints the source to stdout. @@ -527,7 +536,7 @@ class DvMachine: for klass in self.classes.values(): klass.show_source() - def process_and_show(self): + def process_and_show(self) -> None: """ Run :meth:`process` and :meth:`show_source` after each other. """ @@ -538,7 +547,7 @@ class DvMachine: klass.process() klass.show_source() - def get_ast(self): + def get_ast(self) -> dict: """ Processes each class with AST enabled and returns a dictionary with all single ASTs Classnames as keys. diff --git a/androguard/decompiler/decompiler.py b/androguard/decompiler/decompiler.py index f07ec1d7..bbc1e61c 100644 --- a/androguard/decompiler/decompiler.py +++ b/androguard/decompiler/decompiler.py @@ -15,6 +15,16 @@ # See the License for the specific language governing permissions and # limitations under the License. +# Allows type hinting of types not-yet-declared +# in Python >= 3.7 +# see https://peps.python.org/pep-0563/ +from __future__ import annotations + +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from androguard.core.analysis.analysis import Analysis, MethodAnalysis + from androguard.core.dex import DEX, ClassDefItem + from androguard.decompiler import decompile from pygments import highlight @@ -26,7 +36,7 @@ from loguru import logger class DecompilerDAD: - def __init__(self, vm, vmx): + def __init__(self, vm: DEX, vmx: Analysis) -> None: """ Decompiler wrapper for DAD: **D**AD is **A** **D**ecompiler DAD is the androguard internal decompiler. @@ -35,25 +45,25 @@ class DecompilerDAD: creates :class:`~androguard.decompiler.decompile.DvClass` and :class:`~androguard.decompiler.decompile.DvMethod` on demand. - :param androguard.core.bytecodes.dvm.DEX vm: `DEX` object + :param androguard.core.bytecodes.DEX vm: `DEX` object :param androguard.core.analysis.analysis.Analysis vmx: `Analysis` object """ self.vm = vm self.vmx = vmx - def get_source_method(self, m): + def get_source_method(self, m: MethodAnalysis) -> str: mx = self.vmx.get_method(m) z = decompile.DvMethod(mx) z.process() return z.get_source() - def get_ast_method(self, m): + def get_ast_method(self, m: MethodAnalysis) -> dict: mx = self.vmx.get_method(m) z = decompile.DvMethod(mx) z.process(doAST=True) return z.get_ast() - def display_source(self, m): + def display_source(self, m: MethodAnalysis) -> None: result = self.get_source_method(m) lexer = get_lexer_by_name("java", stripall=True) @@ -61,17 +71,17 @@ class DecompilerDAD: result = highlight(result, lexer, formatter) print(result) - def get_source_class(self, _class): + def get_source_class(self, _class: ClassDefItem) -> str: c = decompile.DvClass(_class, self.vmx) c.process() return c.get_source() - def get_ast_class(self, _class): + def get_ast_class(self, _class: ClassDefItem) -> dict: c = decompile.DvClass(_class, self.vmx) c.process(doAST=True) return c.get_ast() - def get_source_class_ext(self, _class): + def get_source_class_ext(self, _class: ClassDefItem) -> list[tuple[str, list]]: c = decompile.DvClass(_class, self.vmx) c.process() @@ -79,7 +89,7 @@ class DecompilerDAD: return result - def display_all(self, _class): + def display_all(self, _class: ClassDefItem) -> None: result = self.get_source_class(_class) lexer = get_lexer_by_name("java", stripall=True) diff --git a/androguard/decompiler/util.py b/androguard/decompiler/util.py index ff53334b..f62e84f1 100644 --- a/androguard/decompiler/util.py +++ b/androguard/decompiler/util.py @@ -15,6 +15,14 @@ # See the License for the specific language governing permissions and # limitations under the License. +# Allows type hinting of types not-yet-declared +# in Python >= 3.7 +# see https://peps.python.org/pep-0563/ +from __future__ import annotations +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from androguard.decompiler.graph import Graph + from loguru import logger TYPE_DESCRIPTOR = { @@ -77,19 +85,19 @@ ACCESS_ORDER = [0x1, 0x4, 0x2, 0x400, 0x8, 0x10, 0x80, 0x40, 0x20, 0x100, 0x800, TYPE_LEN = {'J': 2, 'D': 2, } -def get_access_class(access): +def get_access_class(access: int) -> list[str]: sorted_access = [i for i in ACCESS_ORDER if i & access] return [ACCESS_FLAGS_CLASSES.get(flag, 'unkn_%d' % flag) for flag in sorted_access] -def get_access_method(access): +def get_access_method(access: int) -> list[str]: sorted_access = [i for i in ACCESS_ORDER if i & access] return [ACCESS_FLAGS_METHODS.get(flag, 'unkn_%d' % flag) for flag in sorted_access] -def get_access_field(access): +def get_access_field(access: int) -> list[str]: sorted_access = [i for i in ACCESS_ORDER if i & access] return [ACCESS_FLAGS_FIELDS.get(flag, 'unkn_%d' % flag) for flag in sorted_access] @@ -165,7 +173,7 @@ def get_type_size(param): return TYPE_LEN.get(param, 1) -def get_type(atype, size=None): +def get_type(atype:str, size:int=None) -> str: """ Retrieve the java type of a descriptor (e.g : I) """ @@ -187,7 +195,7 @@ def get_type(atype, size=None): return res -def get_params_type(descriptor): +def get_params_type(descriptor: str) -> list[str]: """ Return the parameters type of a descriptor (e.g (IC)V) """ @@ -197,7 +205,7 @@ def get_params_type(descriptor): return [] -def create_png(cls_name, meth_name, graph, dir_name='graphs2'): +def create_png(cls_name: str, meth_name: str, graph: Graph, dir_name:str='graphs2') -> None: """ Creates a PNG from a given :class:`~androguard.decompiler.graph.Graph`. diff --git a/androguard/decompiler/writer.py b/androguard/decompiler/writer.py index 0ff692ad..c4565e20 100644 --- a/androguard/decompiler/writer.py +++ b/androguard/decompiler/writer.py @@ -50,7 +50,7 @@ class Writer: def __str__(self): return ''.join([str(i) for i in self.buffer]) - def str_ext(self): + def str_ext(self) -> list[tuple]: return self.buffer2 def inc_ind(self, i=1): diff --git a/androguard/misc.py b/androguard/misc.py index 0704d57f..ced3c4b2 100644 --- a/androguard/misc.py +++ b/androguard/misc.py @@ -1,16 +1,21 @@ +# Allows type hinting of types not-yet-declared +# in Python >= 3.7 +# see https://peps.python.org/pep-0563/ +from __future__ import annotations +import hashlib +import os +import re +from typing import Union + +from loguru import logger + from androguard.session import Session from androguard.decompiler import decompiler from androguard.core import androconf from androguard.core import apk, dex from androguard.core.analysis.analysis import Analysis -import hashlib -import re -import os -from loguru import logger - - -def get_default_session(): +def get_default_session() -> Session: """ Return the default Session from the configuration or create a new one, if the session in the configuration is None. @@ -22,7 +27,7 @@ def get_default_session(): return androconf.CONF["SESSION"] -def AnalyzeAPK(_file, session=None, raw=False): +def AnalyzeAPK(_file: Union[str,bytes], session:Union[Session,None]=None, raw:bool=False) -> tuple[apk.APK, list[dex.DEX], Analysis]: """ Analyze an android application and setup all stuff for a more quickly analysis! @@ -36,7 +41,7 @@ def AnalyzeAPK(_file, session=None, raw=False): :type _file: string (for filename) or bytes (for raw) :param session: A session (default: None) :param raw: boolean if raw bytes are supplied instead of a filename - :rtype: return the :class:`~androguard.core.apk.APK`, list of :class:`~androguard.core.dvm.DEX`, and :class:`~androguard.core.analysis.analysis.Analysis` objects + :rtype: return the :class:`~androguard.core.apk.APK`, list of :class:`~androguard.core.dex.DEX`, and :class:`~androguard.core.analysis.analysis.Analysis` objects """ logger.debug("AnalyzeAPK") @@ -71,7 +76,7 @@ def AnalyzeAPK(_file, session=None, raw=False): return a, d, dx -def AnalyzeDex(filename, session=None, raw=False): +def AnalyzeDex(filename: str, session:Session=None, raw:bool=False) -> tuple[str, dex.DEX, Analysis]: """ Analyze an android dex file and setup all stuff for a more quickly analysis ! @@ -96,32 +101,32 @@ def AnalyzeDex(filename, session=None, raw=False): return session.addDEX(filename, data) -def AnalyzeODex(filename, session=None, raw=False): - """ - Analyze an android odex file and setup all stuff for a more quickly analysis ! +# def AnalyzeODex(filename: str, session:Session=None, raw:bool=False): +# """ +# Analyze an android odex file and setup all stuff for a more quickly analysis ! - :param filename: the filename of the android dex file or a buffer which represents the dex file - :type filename: string - :param session: The Androguard Session to add the ODex to (default: None) - :param raw: If set, ``filename`` will be used as the odex's data (bytes). Defaults to ``False`` +# :param filename: the filename of the android dex file or a buffer which represents the dex file +# :type filename: string +# :param session: The Androguard Session to add the ODex to (default: None) +# :param raw: If set, ``filename`` will be used as the odex's data (bytes). Defaults to ``False`` - :rtype: return a tuple of (sha256hash, :class:`DalvikOdexVMFormat`, :class:`Analysis`) - """ - logger.debug("AnalyzeODex") +# :rtype: return a tuple of (sha256hash, :class:`DalvikOdexVMFormat`, :class:`Analysis`) +# """ +# logger.debug("AnalyzeODex") - if not session: - session = get_default_session() +# if not session: +# session = get_default_session() - if raw: - data = filename - else: - with open(filename, "rb") as fd: - data = fd.read() +# if raw: +# data = filename +# else: +# with open(filename, "rb") as fd: +# data = fd.read() - return session.addDEY(filename, data) +# return session.addDEY(filename, data) # <- this function is missing -def clean_file_name(filename, unique=True, replace="_", force_nt=False): +def clean_file_name(filename: str, unique:bool=True, replace:str="_", force_nt:bool=False) -> str: """ Return a filename version, which has no characters in it which are forbidden. On Windows these are for example <, /, ?, ... diff --git a/androguard/session.py b/androguard/session.py index c0a5117c..aed19f02 100644 --- a/androguard/session.py +++ b/androguard/session.py @@ -1,12 +1,13 @@ -from androguard.core.analysis.analysis import Analysis +import collections +import hashlib +from typing import Union, Iterator + +from androguard.core.analysis.analysis import Analysis, StringAnalysis from androguard.core import dex, apk from androguard.decompiler.decompiler import DecompilerDAD from androguard.core import androconf -import hashlib -import collections import dataset - from loguru import logger @@ -22,7 +23,7 @@ class Session: >>> Should we go back to pickling or proceed further with the dataset ?<<< """ - def __init__(self, export_ipython=False): + def __init__(self, export_ipython:bool=False) -> None: """ Create a new Session object @@ -44,7 +45,7 @@ class Session: self.table_session.insert(dict(id=self.session_id)) logger.info("Creating new session [{}]".format(self.session_id)) - def save(self, filename=None): + def save(self, filename:Union[str,None]=None) -> None: """ Save the current session, see also :func:`~androguard.session.Save`. """ @@ -67,13 +68,13 @@ class Session: # files as well, but we do not remove it here for legacy reasons self.analyzed_dex = dict() - def reset(self): + def reset(self) -> None: """ Reset the current session, delete all added files. """ self._setup_objects() - def isOpen(self): + def isOpen(self) -> bool: """ Test if any file was analyzed in this session @@ -81,7 +82,7 @@ class Session: """ return len(self.analyzed_digest) > 0 - def show(self): + def show(self) -> None: """ Print information to stdout about the current session. Gets all APKs, all DEX files and all Analysis objects. @@ -106,7 +107,7 @@ class Session: self.table_system.insert( dict(session_id=str(self.session_id), call=call, callee=callee, information=information, params=params)) - def addAPK(self, filename, data): + def addAPK(self, filename: str, data:bytes) -> tuple[str, apk.APK]: """ Add an APK file to the Session and run analysis on it. @@ -139,7 +140,7 @@ class Session: logger.info("added APK {}:{}".format(filename, digest)) return digest, newapk - def addDEX(self, filename, data, dx=None, postpone_xref=False): + def addDEX(self, filename: str, data: bytes, dx:Union[Analysis,None]=None, postpone_xref:bool=False) -> tuple[str, dex.DEX, Analysis]: """ Add a DEX file to the Session and run analysis. @@ -184,7 +185,7 @@ class Session: return digest, d, dx - def addODEX(self, filename, data, dx=None): + def addODEX(self, filename:str, data:bytes, dx:Union[Analysis,None]=None) -> tuple[str, dex.ODEX, Analysis]: """ Add an ODEX file to the session and run the analysis """ @@ -220,7 +221,7 @@ class Session: return digest, d, dx - def add(self, filename, raw_data=None, dx=None): + def add(self, filename: str, raw_data:Union[bytes,None]=None, dx:Union[Analysis,None]=None) -> Union[str,None]: """ Generic method to add a file to the session. @@ -258,7 +259,7 @@ class Session: return digest - def get_classes(self): + def get_classes(self) -> Iterator[tuple[int, str, str, list[dex.ClassDefItem]]]: """ Returns all Java Classes from the DEX objects as an array of DEX files. """ @@ -268,7 +269,7 @@ class Session: filename = self.analyzed_digest[digest] yield idx, filename, digest, vm.get_classes() - def get_analysis(self, current_class): + def get_analysis(self, current_class: dex.ClassDefItem) -> Analysis: """ Returns the :class:`~androguard.core.analysis.analysis.Analysis` object which contains the `current_class`. @@ -283,7 +284,7 @@ class Session: return dx return None - def get_format(self, current_class): + def get_format(self, current_class: dex.ClassDefItem) -> dex.DEX: """ Returns the :class:`~androguard.core.bytecodes.dvm.DEX` of a given :class:`~androguard.core.bytecodes.dvm.ClassDefItem`. @@ -292,7 +293,7 @@ class Session: """ return current_class.CM.vm - def get_filename_by_class(self, current_class): + def get_filename_by_class(self, current_class: dex.ClassDefItem) -> Union[str,None]: """ Returns the filename of the DEX file where the class is in. @@ -308,7 +309,7 @@ class Session: return self.analyzed_digest[digest] return None - def get_digest_by_class(self, current_class): + def get_digest_by_class(self, current_class: dex.ClassDefItem) -> Union[str,None]: """ Return the SHA256 hash of the object containing the ClassDefItem @@ -321,7 +322,7 @@ class Session: return digest return None - def get_strings(self): + def get_strings(self) -> Iterator[tuple[str, str, dict[str,StringAnalysis]]]: """ Yields all StringAnalysis for all unique Analysis objects """ @@ -332,7 +333,7 @@ class Session: seen.append(dx) yield digest, self.analyzed_digest[digest], dx.get_strings_analysis() - def get_nb_strings(self): + def get_nb_strings(self) -> int: """ Return the total number of strings in all Analysis objects """ @@ -345,7 +346,7 @@ class Session: nb += len(dx.get_strings_analysis()) return nb - def get_all_apks(self): + def get_all_apks(self) -> Iterator[tuple[str, apk.APK]]: """ Yields a list of tuples of SHA256 hash of the APK and APK objects of all analyzed APKs in the Session. @@ -353,7 +354,7 @@ class Session: for digest, a in self.analyzed_apk.items(): yield digest, a - def get_objects_apk(self, filename=None, digest=None): + def get_objects_apk(self, filename:Union[str,None]=None, digest:Union[str,None]=None) -> Iterator[tuple[apk.APK, list[dex.DEX], Analysis]]: """ Returns APK, DEX and Analysis of a specified APK. @@ -391,7 +392,7 @@ class Session: dx = self.analyzed_vms[digest] return a, dx.vms, dx - def get_objects_dex(self): + def get_objects_dex(self) -> Iterator[tuple[str, dex.DEX, Analysis]]: """ Yields all dex objects inclduing their Analysis objects diff --git a/androguard/util.py b/androguard/util.py index 817da72a..716c3e91 100644 --- a/androguard/util.py +++ b/androguard/util.py @@ -1,12 +1,15 @@ -#  External dependecies -import asn1crypto + import sys +from typing import Union, BinaryIO + +#  External dependecies +# import asn1crypto +from asn1crypto.x509 import Name from loguru import logger - class MyFilter: - def __init__(self, level): + def __init__(self, level:int) -> None: self.level = level def __call__(self, record): @@ -14,7 +17,7 @@ class MyFilter: return record["level"].no >= levelno -def set_log(level): +def set_log(level:int) -> None: """ Sets the log for loguru based on the level being passed. The possible values are TRACE, DEBUG, INFO, SUCCESS, WARNING, ERROR, CRITICAL @@ -26,7 +29,7 @@ def set_log(level): # Stuff that might be useful -def read_at(buff, offset, size=-1): +def read_at(buff: BinaryIO, offset:int, size:int=-1) -> bytes: idx = buff.tell() buff.seek(offset) d = buff.read(size) @@ -34,7 +37,7 @@ def read_at(buff, offset, size=-1): return d -def readFile(filename, binary=True): +def readFile(filename: str, binary:bool=True) -> bytes: """ Open and read a file :param filename: filename to open and read @@ -45,7 +48,7 @@ def readFile(filename, binary=True): return f.read() -def get_certificate_name_string(name, short=False, delimiter=', '): +def get_certificate_name_string(name:Union[dict,Name], short:bool=False, delimiter:str=', ') -> str: """ Format the Name type of a X509 Certificate in a human readable form. @@ -59,7 +62,7 @@ def get_certificate_name_string(name, short=False, delimiter=', '): :rtype: str """ - if isinstance(name, asn1crypto.x509.Name): + if isinstance(name, Name):#asn1crypto.x509.Name): name = name.native # For the shortform, we have a lookup table diff --git a/requirements.txt b/requirements.txt index 4cfe51e3..ddb5ccaa 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,4 +14,4 @@ matplotlib networkx PyQt5 pyyaml -oscrypto>=1.3.0 +oscrypto>=1.3.0 \ No newline at end of file diff --git a/tests/test_analysis.py b/tests/test_analysis.py index 2ec4435b..b6d0f28a 100644 --- a/tests/test_analysis.py +++ b/tests/test_analysis.py @@ -299,8 +299,8 @@ class AnalysisTest(unittest.TestCase): sa = dx.strings['Hello world'] self.assertEqual(len(sa.get_xref_from()), 1) - self.assertEqual(len(sa.get_xref_from(withoffset=True)), 1) - self.assertEqual(next(iter(sa.get_xref_from(withoffset=True)))[2], 4) # offset is 4 + self.assertEqual(len(sa.get_xref_from(with_offset=True)), 1) + self.assertEqual(next(iter(sa.get_xref_from(with_offset=True)))[2], 4) # offset is 4 def testXrefOffsetsFields(self): """Tests if Field offsets in bytecode are correctly stored""" @@ -315,26 +315,26 @@ class AnalysisTest(unittest.TestCase): afield = next(dx.find_fields(fieldname='afield')) self.assertEqual(len(afield.get_xref_read()), 1) # always same method - self.assertEqual(len(afield.get_xref_read(withoffset=True)), 2) - self.assertListEqual(list(sorted(map(itemgetter(2), afield.get_xref_read(withoffset=True)))), [4, 40]) + self.assertEqual(len(afield.get_xref_read(with_offset=True)), 2) + self.assertListEqual(list(sorted(map(itemgetter(2), afield.get_xref_read(with_offset=True)))), [4, 40]) self.assertListEqual(list(map(lambda x: x.name, map(itemgetter(1), - afield.get_xref_read(withoffset=True)))), + afield.get_xref_read(with_offset=True)))), ["foonbar", "foonbar"]) self.assertEqual(len(afield.get_xref_write()), 2) - self.assertEqual(len(afield.get_xref_write(withoffset=True)), 2) - self.assertListEqual(list(sorted(map(itemgetter(2), afield.get_xref_write(withoffset=True)))), [10, 32]) + self.assertEqual(len(afield.get_xref_write(with_offset=True)), 2) + self.assertListEqual(list(sorted(map(itemgetter(2), afield.get_xref_write(with_offset=True)))), [10, 32]) self.assertListEqual(list(sorted(map(lambda x: x.name, map(itemgetter(1), - afield.get_xref_write(withoffset=True))))), + afield.get_xref_write(with_offset=True))))), sorted(["", "foonbar"])) cfield = next(dx.find_fields(fieldname='cfield')) # this one is static, hence it must have a write in self.assertListEqual(list(sorted(map(lambda x: x.name, map(itemgetter(1), - cfield.get_xref_write(withoffset=True))))), + cfield.get_xref_write(with_offset=True))))), sorted([""])) self.assertListEqual(list(sorted(map(lambda x: x.name, map(itemgetter(1), - cfield.get_xref_read(withoffset=True))))), + cfield.get_xref_read(with_offset=True))))), sorted(["foonbar"])) def testPermissions(self): From 04e45e2fe06708f473609925d63535b35eea51e5 Mon Sep 17 00:00:00 2001 From: erev0s Date: Mon, 29 Apr 2024 10:23:46 +0300 Subject: [PATCH 6/6] reject decoding strings that are passing the string block in size --- androguard/core/axml/__init__.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/androguard/core/axml/__init__.py b/androguard/core/axml/__init__.py index 95ab6e09..e7979ad1 100644 --- a/androguard/core/axml/__init__.py +++ b/androguard/core/axml/__init__.py @@ -244,6 +244,13 @@ class StringBlock: encoded_bytes, skip = self._decode_length(offset, 1) offset += skip + # Two checks should happen here: + # a) offset + encoded_bytes surpassing the string_pool length and + # b) non-null terminated strings which should be rejected + # platform/frameworks/base/libs/androidfw/ResourceTypes.cpp#789 + if len(self.m_charbuff) < (offset + encoded_bytes): + logger.warning(f"String size: {offset + encoded_bytes} is exceeding string pool size. Returning empty string.") + return "" data = self.m_charbuff[offset: offset + encoded_bytes] if self.m_charbuff[offset + encoded_bytes] != 0: @@ -264,6 +271,14 @@ class StringBlock: # The len is the string len in utf-16 units encoded_bytes = str_len * 2 + # Two checks should happen here: + # a) offset + encoded_bytes surpassing the string_pool length and + # b) non-null terminated strings which should be rejected + # platform/frameworks/base/libs/androidfw/ResourceTypes.cpp#789 + if len(self.m_charbuff) < (offset + encoded_bytes): + logger.warning(f"String size: {offset + encoded_bytes} is exceeding string pool size. Returning empty string.") + return "" + data = self.m_charbuff[offset: offset + encoded_bytes] if self.m_charbuff[offset + encoded_bytes:offset + encoded_bytes + 2] != b"\x00\x00": @@ -835,7 +850,7 @@ class AXMLParser: ":") if res != self.sb[name]: self.packerwarning = True - + if not res or res == ":": # Attach the HEX Number, so for multiple missing attributes we do not run # into problems.