mirror of
https://github.com/androguard/androguard.git
synced 2024-11-23 05:00:11 +00:00
Merge branch 'master' into master
This commit is contained in:
commit
c72e8f8ddc
@ -3,6 +3,7 @@ import os
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
from typing import Union
|
||||
|
||||
# 3rd party modules
|
||||
from lxml import etree
|
||||
@ -10,8 +11,11 @@ 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.axml import ARSCParser
|
||||
from androguard.session import Session
|
||||
from androguard.core import androconf
|
||||
from androguard.core import apk
|
||||
from androguard.core.axml import AXMLPrinter
|
||||
@ -19,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)
|
||||
@ -46,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'
|
||||
@ -74,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
|
||||
@ -187,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
|
||||
|
||||
@ -274,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
|
||||
@ -297,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
|
||||
|
||||
@ -360,14 +375,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()
|
||||
|
||||
@ -380,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:
|
||||
@ -413,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
|
||||
|
||||
@ -458,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
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
||||
@ -508,7 +520,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
|
||||
@ -596,7 +608,7 @@ class APK:
|
||||
|
||||
return app_icon
|
||||
|
||||
def get_package(self):
|
||||
def get_package(self) -> str:
|
||||
"""
|
||||
Return the name of the package
|
||||
|
||||
@ -606,7 +618,7 @@ class APK:
|
||||
"""
|
||||
return self.package
|
||||
|
||||
def get_androidversion_code(self):
|
||||
def get_androidversion_code(self) -> str:
|
||||
"""
|
||||
Return the android version code
|
||||
|
||||
@ -616,7 +628,7 @@ class APK:
|
||||
"""
|
||||
return self.androidversion["Code"]
|
||||
|
||||
def get_androidversion_name(self):
|
||||
def get_androidversion_name(self) -> str:
|
||||
"""
|
||||
Return the android version name
|
||||
|
||||
@ -626,7 +638,7 @@ class APK:
|
||||
"""
|
||||
return self.androidversion["Name"]
|
||||
|
||||
def get_files(self):
|
||||
def get_files(self) -> list[str]:
|
||||
"""
|
||||
Return the file names inside the APK.
|
||||
|
||||
@ -634,7 +646,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
|
||||
@ -686,7 +698,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
|
||||
|
||||
@ -694,7 +706,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)
|
||||
|
||||
@ -742,7 +754,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
|
||||
|
||||
@ -754,7 +766,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
|
||||
|
||||
@ -763,7 +775,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
|
||||
|
||||
@ -777,7 +789,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
|
||||
@ -789,7 +801,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
|
||||
|
||||
@ -804,7 +816,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
|
||||
@ -815,7 +827,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
|
||||
|
||||
@ -824,7 +836,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
|
||||
|
||||
@ -855,8 +867,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
|
||||
|
||||
@ -874,8 +886,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
|
||||
|
||||
@ -889,7 +901,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.
|
||||
|
||||
@ -933,7 +945,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
|
||||
|
||||
@ -948,8 +960,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
|
||||
@ -972,7 +984,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.
|
||||
|
||||
@ -999,7 +1011,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
|
||||
|
||||
@ -1043,7 +1055,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
|
||||
|
||||
@ -1065,7 +1077,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
|
||||
|
||||
@ -1073,7 +1085,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.
|
||||
|
||||
@ -1092,7 +1104,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
|
||||
|
||||
@ -1100,7 +1112,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
|
||||
|
||||
@ -1108,7 +1120,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
|
||||
|
||||
@ -1116,7 +1128,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
|
||||
@ -1137,7 +1149,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.
|
||||
|
||||
@ -1188,7 +1200,7 @@ class APK:
|
||||
|
||||
return d
|
||||
|
||||
def get_permissions(self):
|
||||
def get_permissions(self) -> list[str]:
|
||||
"""
|
||||
Return permissions names declared in the AndroidManifest.xml.
|
||||
|
||||
@ -1205,7 +1217,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.
|
||||
|
||||
@ -1275,7 +1287,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.
|
||||
|
||||
@ -1296,7 +1308,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.
|
||||
|
||||
@ -1311,7 +1323,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.
|
||||
|
||||
@ -1326,7 +1338,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.
|
||||
|
||||
@ -1339,7 +1351,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.
|
||||
|
||||
@ -1347,7 +1359,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.
|
||||
|
||||
@ -1355,7 +1367,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
|
||||
|
||||
@ -1363,7 +1375,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
|
||||
|
||||
@ -1371,7 +1383,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
|
||||
|
||||
@ -1379,7 +1391,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.
|
||||
|
||||
@ -1397,7 +1409,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
|
||||
|
||||
@ -1405,7 +1417,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
|
||||
@ -1414,7 +1426,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'
|
||||
@ -1427,7 +1439,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'
|
||||
@ -1436,7 +1448,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
|
||||
@ -1446,7 +1458,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.
|
||||
|
||||
@ -1459,7 +1471,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
|
||||
|
||||
@ -1471,7 +1483,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
|
||||
|
||||
@ -1512,18 +1524,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
|
||||
|
||||
@ -1534,11 +1546,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"]
|
||||
@ -1550,13 +1562,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.
|
||||
|
||||
@ -1565,7 +1577,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.
|
||||
|
||||
@ -1577,7 +1589,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.
|
||||
|
||||
@ -1589,11 +1601,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('<I', io_stream.read(4))
|
||||
return value
|
||||
|
||||
def parse_signatures_or_digests(self, digest_bytes):
|
||||
def parse_signatures_or_digests(self, digest_bytes) -> list[tuple[int, bytes]]:
|
||||
""" Parse digests """
|
||||
|
||||
if not len(digest_bytes):
|
||||
@ -1613,7 +1625,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
|
||||
@ -1695,7 +1707,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
|
||||
"""
|
||||
@ -1794,7 +1806,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
|
||||
"""
|
||||
@ -1876,7 +1888,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
|
||||
"""
|
||||
@ -1891,7 +1903,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
|
||||
"""
|
||||
@ -1906,7 +1918,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
|
||||
"""
|
||||
@ -1921,7 +1933,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
|
||||
"""
|
||||
@ -1936,21 +1948,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.
|
||||
@ -1959,7 +1971,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.
|
||||
@ -1968,7 +1980,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).
|
||||
@ -1981,7 +1993,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
|
||||
@ -1996,7 +2008,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.
|
||||
"""
|
||||
@ -2006,7 +2018,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)
|
||||
@ -2025,7 +2037,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)
|
||||
@ -2037,7 +2049,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.
|
||||
@ -2053,7 +2065,7 @@ class APK:
|
||||
|
||||
return signature_datas
|
||||
|
||||
def show(self):
|
||||
def show(self) -> None:
|
||||
self.get_files_types()
|
||||
|
||||
print("FILES: ")
|
||||
@ -2106,7 +2118,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.
|
||||
|
||||
@ -2122,7 +2134,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
|
||||
@ -2145,7 +2157,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
|
||||
|
@ -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
|
||||
|
||||
@ -238,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:
|
||||
@ -245,7 +258,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
|
||||
|
||||
@ -258,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":
|
||||
@ -266,7 +287,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 +304,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 +341,7 @@ class StringBlock:
|
||||
|
||||
return length, size
|
||||
|
||||
def show(self):
|
||||
def show(self) -> None:
|
||||
"""
|
||||
Print some information on stdout about the string table
|
||||
"""
|
||||
@ -371,7 +392,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()
|
||||
@ -380,6 +401,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:
|
||||
@ -457,7 +479,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,
|
||||
@ -670,7 +692,7 @@ class AXMLParser:
|
||||
self.buff.seek(h.end)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
def name(self) -> str:
|
||||
"""
|
||||
Return the String assosciated with the tag name
|
||||
"""
|
||||
@ -680,7 +702,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
|
||||
|
||||
@ -693,7 +715,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
|
||||
"""
|
||||
@ -707,7 +729,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
|
||||
|
||||
@ -733,7 +755,7 @@ class AXMLParser:
|
||||
return NSMAP
|
||||
|
||||
@property
|
||||
def text(self):
|
||||
def text(self) -> str:
|
||||
"""
|
||||
Return the String assosicated with the current text
|
||||
"""
|
||||
@ -742,21 +764,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
|
||||
@ -776,7 +798,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
|
||||
@ -786,7 +808,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
|
||||
"""
|
||||
@ -797,7 +819,7 @@ class AXMLParser:
|
||||
|
||||
return uri
|
||||
|
||||
def getAttributeNamespace(self, index):
|
||||
def getAttributeNamespace(self, index:int):
|
||||
"""
|
||||
Return the Namespace URI (if any) for the attribute
|
||||
"""
|
||||
@ -811,7 +833,7 @@ class AXMLParser:
|
||||
|
||||
return self.sb[uri]
|
||||
|
||||
def getAttributeName(self, index):
|
||||
def getAttributeName(self, index:int):
|
||||
"""
|
||||
Returns the String which represents the attribute name
|
||||
"""
|
||||
@ -821,17 +843,21 @@ 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)
|
||||
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
|
||||
# into problems.
|
||||
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
|
||||
|
||||
@ -842,7 +868,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
|
||||
|
||||
@ -872,7 +898,7 @@ class AXMLParser:
|
||||
return ''
|
||||
|
||||
|
||||
def format_value(_type, _data, lookup_string=lambda ix: "<string>"):
|
||||
def format_value(_type: int, _data: int, lookup_string=lambda ix: "<string>") -> str:
|
||||
"""
|
||||
Format a value based on type and data.
|
||||
By default, no strings are looked up and "<string>" is returned.
|
||||
@ -936,7 +962,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)
|
||||
@ -1003,7 +1029,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.
|
||||
|
||||
@ -1011,7 +1037,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
|
||||
|
||||
@ -1019,7 +1045,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
|
||||
|
||||
@ -1027,7 +1053,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
|
||||
@ -1035,7 +1061,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
|
||||
|
||||
@ -1046,9 +1072,9 @@ 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):
|
||||
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
|
||||
@ -1352,7 +1378,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
|
||||
"""
|
||||
@ -1581,10 +1607,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")
|
||||
@ -1592,7 +1618,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")
|
||||
@ -1600,10 +1626,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(),
|
||||
@ -1614,7 +1640,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(
|
||||
@ -1628,17 +1654,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.
|
||||
|
||||
@ -1647,7 +1673,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.
|
||||
@ -1658,7 +1684,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'.
|
||||
|
||||
@ -1684,7 +1710,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'.
|
||||
|
||||
@ -1713,7 +1739,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
|
||||
@ -1746,7 +1772,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'.
|
||||
|
||||
@ -1775,7 +1801,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'.
|
||||
|
||||
@ -1800,7 +1826,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'.
|
||||
|
||||
@ -1825,7 +1851,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'.
|
||||
|
||||
@ -1850,7 +1876,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'.
|
||||
|
||||
@ -1875,7 +1901,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.
|
||||
@ -1900,7 +1926,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.
|
||||
@ -1908,7 +1934,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.
|
||||
|
||||
@ -1927,7 +1953,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
|
||||
@ -1943,7 +1969,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
|
||||
|
||||
@ -1970,7 +1996,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
|
||||
@ -1986,7 +2012,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
|
||||
@ -2021,7 +2047,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.
|
||||
@ -2039,7 +2065,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()
|
||||
|
||||
@ -2064,7 +2090,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:
|
||||
@ -2097,7 +2123,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.
|
||||
@ -2130,7 +2156,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`
|
||||
@ -2168,7 +2194,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:
|
||||
@ -2180,13 +2206,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):
|
||||
@ -2216,9 +2242,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()
|
||||
@ -2263,14 +2289,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
|
||||
@ -2279,7 +2305,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
|
||||
@ -2290,7 +2316,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`.
|
||||
@ -2310,7 +2336,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('<I', buff.read(4))[0]
|
||||
@ -2321,7 +2347,7 @@ class ARSCResTablePackage:
|
||||
self.lastPublicKey = unpack('<I', buff.read(4))[0]
|
||||
self.mResId = self.id << 24
|
||||
|
||||
def get_name(self):
|
||||
def get_name(self) -> None:
|
||||
name = self.name.decode("utf-16", 'replace')
|
||||
name = name[:name.find("\x00")]
|
||||
return name
|
||||
@ -2331,7 +2357,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('<B', buff.read(1))[0]
|
||||
@ -2358,7 +2384,7 @@ class ARSCResType:
|
||||
|
||||
See http://androidxref.com/9.0.0_r3/xref/frameworks/base/libs/androidfw/include/androidfw/ResourceTypes.h#1364
|
||||
"""
|
||||
def __init__(self, buff, parent=None):
|
||||
def __init__(self, buff: BinaryIO, parent:Union[PackageContext,None]=None) -> None:
|
||||
self.start = buff.tell()
|
||||
self.parent = parent
|
||||
|
||||
@ -2378,10 +2404,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):
|
||||
@ -2411,7 +2437,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()
|
||||
|
||||
@ -2599,7 +2625,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.
|
||||
|
||||
@ -2607,7 +2633,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
|
||||
@ -2828,19 +2854,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
|
||||
|
||||
@ -2891,7 +2917,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
|
||||
@ -2910,22 +2936,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):
|
||||
@ -2948,7 +2974,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
|
||||
|
||||
@ -2973,7 +2999,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
|
||||
|
||||
@ -2988,19 +3014,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`.
|
||||
"""
|
||||
@ -3010,7 +3036,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
|
||||
"""
|
||||
@ -3024,7 +3050,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.
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
|
@ -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:]
|
||||
|
@ -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.
|
||||
|
@ -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)
|
||||
|
@ -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`.
|
||||
|
||||
|
@ -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):
|
||||
|
@ -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 <, /, ?, ...
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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"]
|
||||
|
@ -14,3 +14,4 @@ matplotlib
|
||||
networkx
|
||||
PyQt5
|
||||
pyyaml
|
||||
oscrypto>=1.3.0
|
@ -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(["<init>", "foonbar"]))
|
||||
|
||||
cfield = next(dx.find_fields(fieldname='cfield'))
|
||||
# this one is static, hence it must have a write in <clinit>
|
||||
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(["<clinit>"]))
|
||||
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):
|
||||
|
Loading…
Reference in New Issue
Block a user