mirror of
https://github.com/androguard/androguard.git
synced 2024-11-26 14:30:54 +00:00
Format all python files with black + isort
This commit is contained in:
parent
8847ef1be9
commit
f8daa928e3
@ -6,25 +6,32 @@ import json
|
||||
import sys
|
||||
|
||||
import click
|
||||
import networkx as nx
|
||||
from loguru import logger
|
||||
|
||||
import androguard.core.apk
|
||||
from androguard import util
|
||||
from androguard.cli.main import (androarsc_main,
|
||||
androaxml_main,
|
||||
export_apps_to_format,
|
||||
androsign_main,
|
||||
androlyze_main,
|
||||
androdis_main,
|
||||
androtrace_main,
|
||||
androdump_main,
|
||||
)
|
||||
from androguard.cli.main import (
|
||||
androarsc_main,
|
||||
androaxml_main,
|
||||
androdis_main,
|
||||
androdump_main,
|
||||
androlyze_main,
|
||||
androsign_main,
|
||||
androtrace_main,
|
||||
export_apps_to_format,
|
||||
)
|
||||
|
||||
import networkx as nx
|
||||
|
||||
@click.group(help=__doc__)
|
||||
@click.version_option(version=androguard.__version__)
|
||||
@click.option("--verbose", "--debug", 'verbosity', flag_value='verbose', help="Print more")
|
||||
@click.option(
|
||||
"--verbose",
|
||||
"--debug",
|
||||
'verbosity',
|
||||
flag_value='verbose',
|
||||
help="Print more",
|
||||
)
|
||||
def entry_point(verbosity):
|
||||
if verbosity is None:
|
||||
util.set_log("ERROR")
|
||||
@ -35,17 +42,22 @@ def entry_point(verbosity):
|
||||
|
||||
@entry_point.command()
|
||||
@click.option(
|
||||
'--input', '-i', 'input_',
|
||||
'--input',
|
||||
'-i',
|
||||
'input_',
|
||||
type=click.Path(exists=True, file_okay=True, dir_okay=False),
|
||||
help='AndroidManifest.xml or APK to parse (legacy option)',
|
||||
)
|
||||
@click.option(
|
||||
'--output', '-o',
|
||||
'--output',
|
||||
'-o',
|
||||
help='filename to save the decoded AndroidManifest.xml to, default stdout',
|
||||
)
|
||||
@click.option("--resource", "-r",
|
||||
help="Resource (any binary XML file) inside the APK to parse instead of AndroidManifest.xml"
|
||||
)
|
||||
@click.option(
|
||||
"--resource",
|
||||
"-r",
|
||||
help="Resource (any binary XML file) inside the APK to parse instead of AndroidManifest.xml",
|
||||
)
|
||||
@click.argument(
|
||||
'file_',
|
||||
required=False,
|
||||
@ -67,8 +79,10 @@ def axml(input_, output, file_, resource):
|
||||
$ androguard axml AndroidManifest.xml
|
||||
"""
|
||||
if file_ is not None and input_ is not None:
|
||||
print("Can not give --input and positional argument! "
|
||||
"Please use only one of them!")
|
||||
print(
|
||||
"Can not give --input and positional argument! "
|
||||
"Please use only one of them!"
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
if file_ is None and input_ is None:
|
||||
@ -83,7 +97,9 @@ def axml(input_, output, file_, resource):
|
||||
|
||||
@entry_point.command()
|
||||
@click.option(
|
||||
'--input', '-i', 'input_',
|
||||
'--input',
|
||||
'-i',
|
||||
'input_',
|
||||
type=click.Path(exists=True),
|
||||
help='resources.arsc or APK to parse (legacy option)',
|
||||
)
|
||||
@ -92,52 +108,63 @@ def axml(input_, output, file_, resource):
|
||||
required=False,
|
||||
)
|
||||
@click.option(
|
||||
'--output', '-o',
|
||||
'--output',
|
||||
'-o',
|
||||
# required=True, # not required due to --list-types
|
||||
help='filename to save the decoded resources to',
|
||||
)
|
||||
@click.option(
|
||||
'--package', '-p',
|
||||
'--package',
|
||||
'-p',
|
||||
help='Show only resources for the given package name '
|
||||
'(default: the first package name found)',
|
||||
'(default: the first package name found)',
|
||||
)
|
||||
@click.option(
|
||||
'--locale', '-l',
|
||||
'--locale',
|
||||
'-l',
|
||||
help='Show only resources for the given locale (default: \'\\x00\\x00\')',
|
||||
)
|
||||
@click.option(
|
||||
'--type', '-t', 'type_',
|
||||
'--type',
|
||||
'-t',
|
||||
'type_',
|
||||
help='Show only resources of the given type (default: public)',
|
||||
)
|
||||
@click.option(
|
||||
'--id', 'id_',
|
||||
help="Resolve the given ID for the given locale and package. Provide the hex ID!"
|
||||
'--id',
|
||||
'id_',
|
||||
help="Resolve the given ID for the given locale and package. Provide the hex ID!",
|
||||
)
|
||||
@click.option(
|
||||
'--list-packages', is_flag=True,
|
||||
'--list-packages',
|
||||
is_flag=True,
|
||||
default=False,
|
||||
help='List all package names and exit',
|
||||
)
|
||||
@click.option(
|
||||
'--list-locales', is_flag=True,
|
||||
'--list-locales',
|
||||
is_flag=True,
|
||||
default=False,
|
||||
help='List all package names and exit',
|
||||
)
|
||||
@click.option(
|
||||
'--list-types', is_flag=True,
|
||||
'--list-types',
|
||||
is_flag=True,
|
||||
default=False,
|
||||
help='List all types and exit',
|
||||
)
|
||||
def arsc(input_,
|
||||
file_,
|
||||
output,
|
||||
package,
|
||||
locale,
|
||||
type_,
|
||||
id_,
|
||||
list_packages,
|
||||
list_locales,
|
||||
list_types):
|
||||
def arsc(
|
||||
input_,
|
||||
file_,
|
||||
output,
|
||||
package,
|
||||
locale,
|
||||
type_,
|
||||
id_,
|
||||
list_packages,
|
||||
list_locales,
|
||||
list_types,
|
||||
):
|
||||
"""
|
||||
Decode resources.arsc either directly from a given file or from an APK.
|
||||
|
||||
@ -147,11 +174,12 @@ def arsc(input_,
|
||||
$ androguard arsc app.apk
|
||||
"""
|
||||
|
||||
from androguard.core import androconf
|
||||
from androguard.core import axml, apk
|
||||
from androguard.core import androconf, apk, axml
|
||||
|
||||
if file_ and input_:
|
||||
logger.info("Can not give --input and positional argument! Please use only one of them!")
|
||||
logger.info(
|
||||
"Can not give --input and positional argument! Please use only one of them!"
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
if not input_ and not file_:
|
||||
@ -187,7 +215,11 @@ def arsc(input_,
|
||||
try:
|
||||
i_id = int(id_, 16)
|
||||
except ValueError:
|
||||
print("ID '{}' could not be parsed! have you supplied the correct hex ID?".format(id_))
|
||||
print(
|
||||
"ID '{}' could not be parsed! have you supplied the correct hex ID?".format(
|
||||
id_
|
||||
)
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
name = arscobj.get_resource_xml_name(i_id)
|
||||
@ -201,7 +233,16 @@ def arsc(input_,
|
||||
# All the information is in the config.
|
||||
# we simply need to get the actual value of the entry
|
||||
for config, entry in arscobj.get_resolved_res_configs(i_id):
|
||||
print("{} = '{}'".format(config.get_qualifier() if not config.is_default() else "<default>", entry))
|
||||
print(
|
||||
"{} = '{}'".format(
|
||||
(
|
||||
config.get_qualifier()
|
||||
if not config.is_default()
|
||||
else "<default>"
|
||||
),
|
||||
entry,
|
||||
)
|
||||
)
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
@ -212,32 +253,49 @@ def arsc(input_,
|
||||
if list_locales:
|
||||
for p in arscobj.get_packages_names():
|
||||
print("In Package:", p)
|
||||
print("\n".join(map(lambda x: " \\x00\\x00"
|
||||
if x == "\x00\x00"
|
||||
else " {}".format(x),
|
||||
sorted(arscobj.get_locales(p)))))
|
||||
print(
|
||||
"\n".join(
|
||||
map(
|
||||
lambda x: (
|
||||
" \\x00\\x00"
|
||||
if x == "\x00\x00"
|
||||
else " {}".format(x)
|
||||
),
|
||||
sorted(arscobj.get_locales(p)),
|
||||
)
|
||||
)
|
||||
)
|
||||
sys.exit(0)
|
||||
|
||||
if list_types:
|
||||
for p in arscobj.get_packages_names():
|
||||
print("In Package:", p)
|
||||
for locale in sorted(arscobj.get_locales(p)):
|
||||
print(" In Locale: {}".format("\\x00\\x00"
|
||||
if locale == "\x00\x00" else locale))
|
||||
print("\n".join(map(" {}".format,
|
||||
sorted(arscobj.get_types(p, locale)))))
|
||||
print(
|
||||
" In Locale: {}".format(
|
||||
"\\x00\\x00" if locale == "\x00\x00" else locale
|
||||
)
|
||||
)
|
||||
print(
|
||||
"\n".join(
|
||||
map(
|
||||
" {}".format,
|
||||
sorted(arscobj.get_types(p, locale)),
|
||||
)
|
||||
)
|
||||
)
|
||||
sys.exit(0)
|
||||
|
||||
androarsc_main(arscobj,
|
||||
outp=output,
|
||||
package=package,
|
||||
typ=type_,
|
||||
locale=locale)
|
||||
androarsc_main(
|
||||
arscobj, outp=output, package=package, typ=type_, locale=locale
|
||||
)
|
||||
|
||||
|
||||
@entry_point.command()
|
||||
@click.option(
|
||||
'--input', '-i', 'input_',
|
||||
'--input',
|
||||
'-i',
|
||||
'input_',
|
||||
type=click.Path(exists=True, dir_okay=False, file_okay=True),
|
||||
help='APK to parse (legacy option)',
|
||||
)
|
||||
@ -247,29 +305,35 @@ def arsc(input_,
|
||||
required=False,
|
||||
)
|
||||
@click.option(
|
||||
'--output', '-o',
|
||||
'--output',
|
||||
'-o',
|
||||
required=True,
|
||||
help='output directory. If the output folder already exsist, '
|
||||
'it will be overwritten!',
|
||||
'it will be overwritten!',
|
||||
)
|
||||
@click.option(
|
||||
'--format', '-f', 'format_',
|
||||
'--format',
|
||||
'-f',
|
||||
'format_',
|
||||
help='Additionally write control flow graphs for each method, specify '
|
||||
'the format for example png, jpg, raw (write dot file), ...',
|
||||
type=click.Choice(['png', 'jpg', 'raw'])
|
||||
'the format for example png, jpg, raw (write dot file), ...',
|
||||
type=click.Choice(['png', 'jpg', 'raw']),
|
||||
)
|
||||
@click.option(
|
||||
'--jar', '-j',
|
||||
'--jar',
|
||||
'-j',
|
||||
is_flag=True,
|
||||
default=False,
|
||||
help='Use DEX2JAR to create a JAR file',
|
||||
)
|
||||
@click.option(
|
||||
'--limit', '-l',
|
||||
'--limit',
|
||||
'-l',
|
||||
help='Limit to certain methods only by regex (default: \'.*\')',
|
||||
)
|
||||
@click.option(
|
||||
'--decompiler', '-d',
|
||||
'--decompiler',
|
||||
'-d',
|
||||
help='Use a different decompiler (default: DAD)',
|
||||
)
|
||||
def decompile(input_, file_, output, format_, jar, limit, decompiler):
|
||||
@ -282,9 +346,13 @@ def decompile(input_, file_, output, format_, jar, limit, decompiler):
|
||||
$ androguard resources.arsc
|
||||
"""
|
||||
from androguard import session
|
||||
|
||||
if file_ and input_:
|
||||
print("Can not give --input and positional argument! "
|
||||
"Please use only one of them!", file=sys.stderr)
|
||||
print(
|
||||
"Can not give --input and positional argument! "
|
||||
"Please use only one of them!",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
if not input_ and not file_:
|
||||
@ -299,29 +367,35 @@ def decompile(input_, file_, output, format_, jar, limit, decompiler):
|
||||
s = session.Session()
|
||||
with open(fname, "rb") as fd:
|
||||
s.add(fname, fd.read())
|
||||
export_apps_to_format(fname, s, output, limit,
|
||||
jar, decompiler, format_)
|
||||
export_apps_to_format(fname, s, output, limit, jar, decompiler, format_)
|
||||
|
||||
|
||||
@entry_point.command()
|
||||
@click.option(
|
||||
'--hash', 'hash_',
|
||||
'--hash',
|
||||
'hash_',
|
||||
type=click.Choice(['md5', 'sha1', 'sha256', 'sha512']),
|
||||
default='sha1', show_default=True,
|
||||
default='sha1',
|
||||
show_default=True,
|
||||
help='Fingerprint Hash algorithm',
|
||||
)
|
||||
@click.option(
|
||||
'--all', '-a', 'print_all_hashes',
|
||||
'--all',
|
||||
'-a',
|
||||
'print_all_hashes',
|
||||
is_flag=True,
|
||||
default=False, show_default=True,
|
||||
default=False,
|
||||
show_default=True,
|
||||
help='Print all supported hashes',
|
||||
)
|
||||
@click.option(
|
||||
'--show', '-s',
|
||||
'--show',
|
||||
'-s',
|
||||
is_flag=True,
|
||||
default=False, show_default=True,
|
||||
default=False,
|
||||
show_default=True,
|
||||
help='Additionally of printing the fingerprints, show more '
|
||||
'certificate information',
|
||||
'certificate information',
|
||||
)
|
||||
@click.argument(
|
||||
'apk',
|
||||
@ -369,14 +443,20 @@ def analyze(session, apk):
|
||||
|
||||
|
||||
@entry_point.command()
|
||||
@click.option("-o", "--offset",
|
||||
default=0,
|
||||
type=int,
|
||||
help="Offset to start dissassembly inside the file")
|
||||
@click.option("-s", "--size",
|
||||
default=0,
|
||||
type=int,
|
||||
help="Number of bytes from offset to disassemble, 0 for whole file")
|
||||
@click.option(
|
||||
"-o",
|
||||
"--offset",
|
||||
default=0,
|
||||
type=int,
|
||||
help="Offset to start dissassembly inside the file",
|
||||
)
|
||||
@click.option(
|
||||
"-s",
|
||||
"--size",
|
||||
default=0,
|
||||
type=int,
|
||||
help="Number of bytes from offset to disassemble, 0 for whole file",
|
||||
)
|
||||
@click.argument(
|
||||
"DEX",
|
||||
type=click.Path(exists=True, dir_okay=False, file_okay=True),
|
||||
@ -395,11 +475,16 @@ def disassemble(offset, size, dex):
|
||||
required=False,
|
||||
type=click.Path(exists=True, dir_okay=False, file_okay=True),
|
||||
)
|
||||
@click.option("-m", "--modules",
|
||||
multiple=True, default=[],
|
||||
help="A list of modules to load in frida")
|
||||
@click.option(
|
||||
'--enable-ui', is_flag=True,
|
||||
"-m",
|
||||
"--modules",
|
||||
multiple=True,
|
||||
default=[],
|
||||
help="A list of modules to load in frida",
|
||||
)
|
||||
@click.option(
|
||||
'--enable-ui',
|
||||
is_flag=True,
|
||||
default=False,
|
||||
help='Enable UI',
|
||||
)
|
||||
@ -422,9 +507,13 @@ def trace(apk, modules, enable_ui):
|
||||
default=None,
|
||||
required=False,
|
||||
)
|
||||
@click.option("-m", "--modules",
|
||||
multiple=True, default=[],
|
||||
help="A list of modules to load in frida")
|
||||
@click.option(
|
||||
"-m",
|
||||
"--modules",
|
||||
multiple=True,
|
||||
default=[],
|
||||
help="A list of modules to load in frida",
|
||||
)
|
||||
def dtrace(package_name, modules):
|
||||
"""
|
||||
Start dynamically an installed APK on the phone and start to trace all interesting methods from the modules list
|
||||
@ -443,9 +532,13 @@ def dtrace(package_name, modules):
|
||||
default=None,
|
||||
required=False,
|
||||
)
|
||||
@click.option("-m", "--modules",
|
||||
multiple=True, default=["androguard/pentest/modules/helpers/dump/dexdump.js"],
|
||||
help="A list of modules to load in frida")
|
||||
@click.option(
|
||||
"-m",
|
||||
"--modules",
|
||||
multiple=True,
|
||||
default=["androguard/pentest/modules/helpers/dump/dexdump.js"],
|
||||
help="A list of modules to load in frida",
|
||||
)
|
||||
def dump(package_name, modules):
|
||||
"""
|
||||
Start and dump dynamically an installed APK on the phone
|
||||
@ -457,23 +550,29 @@ def dump(package_name, modules):
|
||||
"""
|
||||
androdump_main(package_name, modules)
|
||||
|
||||
|
||||
# callgraph exporting utility functions
|
||||
def _write_gml(G, path):
|
||||
"""Wrapper around nx.write_gml"""
|
||||
return nx.write_gml(G, path, stringizer=str)
|
||||
|
||||
|
||||
def _write_gpickle(G, path):
|
||||
"""Wrapper around pickle dump"""
|
||||
import pickle
|
||||
|
||||
with open(path, 'wb') as f:
|
||||
pickle.dump(G, f, pickle.HIGHEST_PROTOCOL)
|
||||
|
||||
|
||||
def _write_yaml(G, path):
|
||||
"""Wrapper around yaml dump"""
|
||||
import yaml
|
||||
|
||||
with open(path, 'w') as f:
|
||||
yaml.dump(G, f)
|
||||
|
||||
|
||||
# mapping of types to their respective exporting functions
|
||||
write_methods = dict(
|
||||
gml=_write_gml,
|
||||
@ -481,7 +580,9 @@ write_methods = dict(
|
||||
# gpickle=_write_gpickle, # Pickling can't be done due to BufferedReader attributes (e.g. EncodedMethod.buff) not being serializable
|
||||
graphml=nx.write_graphml,
|
||||
# yaml=_write_yaml, # Same limitation as gpickle
|
||||
net=nx.write_pajek)
|
||||
net=nx.write_pajek,
|
||||
)
|
||||
|
||||
|
||||
@entry_point.command()
|
||||
@click.argument(
|
||||
@ -490,20 +591,20 @@ write_methods = dict(
|
||||
required=True,
|
||||
)
|
||||
@click.option(
|
||||
'--output', '-o',
|
||||
'--output',
|
||||
'-o',
|
||||
default='callgraph.gml',
|
||||
help='Filename of the output graph file',
|
||||
)
|
||||
@click.option(
|
||||
'--output-type',
|
||||
type=click.Choice(
|
||||
list(write_methods.keys()),
|
||||
case_sensitive=False),
|
||||
type=click.Choice(list(write_methods.keys()), case_sensitive=False),
|
||||
default='gml',
|
||||
help='Type of the graph to output '
|
||||
help='Type of the graph to output ',
|
||||
)
|
||||
@click.option(
|
||||
'--show', '-s',
|
||||
'--show',
|
||||
'-s',
|
||||
default=False,
|
||||
is_flag=True,
|
||||
help='instead of saving the graph file, render it with matplotlib',
|
||||
@ -543,21 +644,26 @@ def cg(
|
||||
methodname,
|
||||
descriptor,
|
||||
accessflag,
|
||||
no_isolated):
|
||||
no_isolated,
|
||||
):
|
||||
"""
|
||||
Create a call graph based on the data of Analysis and export it into a graph format.
|
||||
"""
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
from androguard.core.analysis.analysis import ExternalMethod
|
||||
from androguard.core.bytecode import FormatClassToJava
|
||||
from androguard.misc import AnalyzeAPK
|
||||
from androguard.core.analysis.analysis import ExternalMethod
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
a, d, dx = AnalyzeAPK(file_)
|
||||
|
||||
entry_points = map(FormatClassToJava,
|
||||
a.get_activities() + a.get_providers() +
|
||||
a.get_services() + a.get_receivers())
|
||||
entry_points = map(
|
||||
FormatClassToJava,
|
||||
a.get_activities()
|
||||
+ a.get_providers()
|
||||
+ a.get_services()
|
||||
+ a.get_receivers(),
|
||||
)
|
||||
entry_points = list(entry_points)
|
||||
|
||||
callgraph = dx.get_call_graph(
|
||||
@ -566,14 +672,16 @@ def cg(
|
||||
descriptor,
|
||||
accessflag,
|
||||
no_isolated,
|
||||
entry_points
|
||||
entry_points,
|
||||
)
|
||||
|
||||
if show:
|
||||
try:
|
||||
import PyQt5
|
||||
except ImportError:
|
||||
print("PyQt5 is not installed. In most OS you can install it by running 'pip install PyQt5'.\n")
|
||||
print(
|
||||
"PyQt5 is not installed. In most OS you can install it by running 'pip install PyQt5'.\n"
|
||||
)
|
||||
exit()
|
||||
pos = nx.spring_layout(callgraph)
|
||||
internal = []
|
||||
@ -586,27 +694,24 @@ def cg(
|
||||
internal.append(n)
|
||||
|
||||
nx.draw_networkx_nodes(
|
||||
callgraph,
|
||||
pos=pos, node_color='r',
|
||||
nodelist=internal)
|
||||
callgraph, pos=pos, node_color='r', nodelist=internal
|
||||
)
|
||||
|
||||
nx.draw_networkx_nodes(
|
||||
callgraph, pos=pos, node_color='b', nodelist=external
|
||||
)
|
||||
|
||||
nx.draw_networkx_edges(callgraph, pos, width=0.5, arrows=True)
|
||||
|
||||
nx.draw_networkx_labels(
|
||||
callgraph,
|
||||
pos=pos,
|
||||
node_color='b',
|
||||
nodelist=external)
|
||||
|
||||
nx.draw_networkx_edges(
|
||||
callgraph,
|
||||
pos,
|
||||
width=0.5,
|
||||
arrows=True)
|
||||
|
||||
nx.draw_networkx_labels(callgraph,
|
||||
pos=pos,
|
||||
font_size=6,
|
||||
labels={n: f"{n.get_class_name()} {n.name} {n.descriptor}"
|
||||
for n in callgraph.nodes})
|
||||
font_size=6,
|
||||
labels={
|
||||
n: f"{n.get_class_name()} {n.name} {n.descriptor}"
|
||||
for n in callgraph.nodes
|
||||
},
|
||||
)
|
||||
|
||||
plt.draw()
|
||||
plt.show()
|
||||
@ -614,7 +719,9 @@ def cg(
|
||||
else:
|
||||
output_type_lower = output_type.lower()
|
||||
if output_type_lower not in write_methods:
|
||||
print(f"Could not find a method to export files to {output_type_lower}!")
|
||||
print(
|
||||
f"Could not find a method to export files to {output_type_lower}!"
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
write_methods[output_type_lower](callgraph, output)
|
||||
|
@ -5,35 +5,39 @@ import shutil
|
||||
import sys
|
||||
from typing import Union
|
||||
|
||||
from loguru import logger
|
||||
|
||||
# 3rd party modules
|
||||
from lxml import etree
|
||||
from loguru import logger
|
||||
from pygments import highlight
|
||||
from pygments.lexers import get_lexer_by_name
|
||||
from pygments.formatters.terminal import TerminalFormatter
|
||||
from pygments.lexers import get_lexer_by_name
|
||||
|
||||
from androguard.core import androconf, apk
|
||||
|
||||
# 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
|
||||
from androguard.core.axml import ARSCParser, AXMLPrinter
|
||||
from androguard.core.dex import get_bytecodes_method
|
||||
from androguard.util import readFile
|
||||
from androguard.session import Session
|
||||
from androguard.ui import DynamicUI
|
||||
from androguard.util import parse_public, calculate_fingerprint
|
||||
from androguard.util import calculate_fingerprint, parse_public, readFile
|
||||
|
||||
|
||||
def androaxml_main(
|
||||
inp:str,
|
||||
outp:Union[str,None]=None,
|
||||
resource:Union[str,None]=None) -> None:
|
||||
|
||||
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)
|
||||
if resource:
|
||||
if resource not in a.files:
|
||||
logger.error("The APK does not contain a file called '{}'".format(resource), file=sys.stderr)
|
||||
logger.error(
|
||||
"The APK does not contain a file called '{}'".format(
|
||||
resource
|
||||
),
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
axml = AXMLPrinter(a.get_file(resource)).get_xml_obj()
|
||||
@ -50,16 +54,23 @@ def androaxml_main(
|
||||
with open(outp, "wb") as fd:
|
||||
fd.write(buff)
|
||||
else:
|
||||
sys.stdout.write(highlight(buff.decode("UTF-8"), get_lexer_by_name("xml"), TerminalFormatter()))
|
||||
sys.stdout.write(
|
||||
highlight(
|
||||
buff.decode("UTF-8"),
|
||||
get_lexer_by_name("xml"),
|
||||
TerminalFormatter(),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
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:
|
||||
|
||||
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'
|
||||
@ -69,36 +80,47 @@ def androarsc_main(
|
||||
# res folder with all the XML files
|
||||
|
||||
if not hasattr(arscobj, "get_{}_resources".format(ttype)):
|
||||
print("No decoder found for type: '{}'! Please open a bug report."
|
||||
.format(ttype),
|
||||
file=sys.stderr)
|
||||
print(
|
||||
"No decoder found for type: '{}'! Please open a bug report.".format(
|
||||
ttype
|
||||
),
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
x = getattr(arscobj, "get_" + ttype + "_resources")(package, locale)
|
||||
|
||||
buff = etree.tostring(etree.fromstring(x),
|
||||
pretty_print=True,
|
||||
encoding="UTF-8")
|
||||
buff = etree.tostring(
|
||||
etree.fromstring(x), pretty_print=True, encoding="UTF-8"
|
||||
)
|
||||
|
||||
if outp:
|
||||
with open(outp, "wb") as fd:
|
||||
fd.write(buff)
|
||||
else:
|
||||
sys.stdout.write(highlight(buff.decode("UTF-8"), get_lexer_by_name("xml"), TerminalFormatter()))
|
||||
sys.stdout.write(
|
||||
highlight(
|
||||
buff.decode("UTF-8"),
|
||||
get_lexer_by_name("xml"),
|
||||
TerminalFormatter(),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
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
|
||||
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.core.bytecode import method2dot, method2format
|
||||
from androguard.decompiler import decompiler
|
||||
from androguard.misc import clean_file_name
|
||||
|
||||
print("Dump information {} in {}".format(filename, output))
|
||||
|
||||
if not os.path.exists(output):
|
||||
@ -106,7 +128,11 @@ def export_apps_to_format(
|
||||
os.makedirs(output)
|
||||
else:
|
||||
while True:
|
||||
user_input = input(f"Do you want to clean the directory {output}? (Y/N): ").strip().lower()
|
||||
user_input = (
|
||||
input(f"Do you want to clean the directory {output}? (Y/N): ")
|
||||
.strip()
|
||||
.lower()
|
||||
)
|
||||
|
||||
if user_input == 'y':
|
||||
print("Deleting...")
|
||||
@ -129,40 +155,61 @@ def export_apps_to_format(
|
||||
sys.stdout.flush()
|
||||
|
||||
if decompiler_type == "dex2jad":
|
||||
vm.set_decompiler(decompiler.DecompilerDex2Jad(vm,
|
||||
androconf.CONF["BIN_DEX2JAR"],
|
||||
androconf.CONF["BIN_JAD"],
|
||||
androconf.CONF["TMP_DIRECTORY"]))
|
||||
vm.set_decompiler(
|
||||
decompiler.DecompilerDex2Jad(
|
||||
vm,
|
||||
androconf.CONF["BIN_DEX2JAR"],
|
||||
androconf.CONF["BIN_JAD"],
|
||||
androconf.CONF["TMP_DIRECTORY"],
|
||||
)
|
||||
)
|
||||
elif decompiler_type == "dex2winejad":
|
||||
vm.set_decompiler(decompiler.DecompilerDex2WineJad(vm,
|
||||
androconf.CONF["BIN_DEX2JAR"],
|
||||
androconf.CONF["BIN_WINEJAD"],
|
||||
androconf.CONF["TMP_DIRECTORY"]))
|
||||
vm.set_decompiler(
|
||||
decompiler.DecompilerDex2WineJad(
|
||||
vm,
|
||||
androconf.CONF["BIN_DEX2JAR"],
|
||||
androconf.CONF["BIN_WINEJAD"],
|
||||
androconf.CONF["TMP_DIRECTORY"],
|
||||
)
|
||||
)
|
||||
elif decompiler_type == "ded":
|
||||
vm.set_decompiler(decompiler.DecompilerDed(vm,
|
||||
androconf.CONF["BIN_DED"],
|
||||
androconf.CONF["TMP_DIRECTORY"]))
|
||||
vm.set_decompiler(
|
||||
decompiler.DecompilerDed(
|
||||
vm,
|
||||
androconf.CONF["BIN_DED"],
|
||||
androconf.CONF["TMP_DIRECTORY"],
|
||||
)
|
||||
)
|
||||
elif decompiler_type == "dex2fernflower":
|
||||
vm.set_decompiler(decompiler.DecompilerDex2Fernflower(vm,
|
||||
androconf.CONF["BIN_DEX2JAR"],
|
||||
androconf.CONF["BIN_FERNFLOWER"],
|
||||
androconf.CONF["OPTIONS_FERNFLOWER"],
|
||||
androconf.CONF["TMP_DIRECTORY"]))
|
||||
vm.set_decompiler(
|
||||
decompiler.DecompilerDex2Fernflower(
|
||||
vm,
|
||||
androconf.CONF["BIN_DEX2JAR"],
|
||||
androconf.CONF["BIN_FERNFLOWER"],
|
||||
androconf.CONF["OPTIONS_FERNFLOWER"],
|
||||
androconf.CONF["TMP_DIRECTORY"],
|
||||
)
|
||||
)
|
||||
|
||||
print("End")
|
||||
|
||||
if jar:
|
||||
print("jar ...", end=' ')
|
||||
filenamejar = decompiler.Dex2Jar(vm,
|
||||
androconf.CONF["BIN_DEX2JAR"],
|
||||
androconf.CONF["TMP_DIRECTORY"]).get_jar()
|
||||
filenamejar = decompiler.Dex2Jar(
|
||||
vm,
|
||||
androconf.CONF["BIN_DEX2JAR"],
|
||||
androconf.CONF["TMP_DIRECTORY"],
|
||||
).get_jar()
|
||||
shutil.move(filenamejar, os.path.join(output, "classes.jar"))
|
||||
print("End")
|
||||
|
||||
for method in vm.get_encoded_methods():
|
||||
if methods_filter_expr:
|
||||
msig = "{}{}{}".format(method.get_class_name(), method.get_name(),
|
||||
method.get_descriptor())
|
||||
msig = "{}{}{}".format(
|
||||
method.get_class_name(),
|
||||
method.get_name(),
|
||||
method.get_descriptor(),
|
||||
)
|
||||
if not methods_filter_expr.search(msig):
|
||||
continue
|
||||
|
||||
@ -171,11 +218,18 @@ def export_apps_to_format(
|
||||
filename_class = os.path.join(output, filename_class)
|
||||
create_directory(filename_class)
|
||||
|
||||
print("Dump {} {} {} ...".format(method.get_class_name(),
|
||||
method.get_name(),
|
||||
method.get_descriptor()), end=' ')
|
||||
print(
|
||||
"Dump {} {} {} ...".format(
|
||||
method.get_class_name(),
|
||||
method.get_name(),
|
||||
method.get_descriptor(),
|
||||
),
|
||||
end=' ',
|
||||
)
|
||||
|
||||
filename = clean_file_name(os.path.join(filename_class, method.get_short_string()))
|
||||
filename = clean_file_name(
|
||||
os.path.join(filename_class, method.get_short_string())
|
||||
)
|
||||
|
||||
buff = method2dot(vmx.get_method(method))
|
||||
# Write Graph of method
|
||||
@ -187,9 +241,13 @@ def export_apps_to_format(
|
||||
if str(method.get_class_name()) not in dump_classes:
|
||||
print("source codes ...", end=' ')
|
||||
current_class = vm.get_class(method.get_class_name())
|
||||
current_filename_class = valid_class_name(str(current_class.get_name()))
|
||||
current_filename_class = valid_class_name(
|
||||
str(current_class.get_name())
|
||||
)
|
||||
|
||||
current_filename_class = os.path.join(output, current_filename_class + ".java")
|
||||
current_filename_class = os.path.join(
|
||||
output, current_filename_class + ".java"
|
||||
)
|
||||
with open(current_filename_class, "w") as fd:
|
||||
fd.write(current_class.get_source())
|
||||
dump_classes.append(method.get_class_name())
|
||||
@ -202,30 +260,29 @@ def export_apps_to_format(
|
||||
print()
|
||||
|
||||
|
||||
def valid_class_name(class_name:str) -> str:
|
||||
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:str) -> None:
|
||||
def create_directory(pathdir: str) -> None:
|
||||
if not os.path.exists(pathdir):
|
||||
os.makedirs(pathdir)
|
||||
|
||||
|
||||
def androlyze_main(session:Session, filename:str) -> None:
|
||||
def androlyze_main(session: Session, filename: str) -> None:
|
||||
"""
|
||||
Start an interactive shell
|
||||
|
||||
:param session: Session file to load
|
||||
:param filename: File to analyze, can be APK or DEX (or ODEX)
|
||||
"""
|
||||
from colorama import Fore
|
||||
import colorama
|
||||
import atexit
|
||||
|
||||
import colorama
|
||||
from colorama import Fore
|
||||
from IPython.terminal.embed import embed
|
||||
|
||||
from traitlets.config import Config
|
||||
|
||||
from androguard.core.androconf import ANDROGUARD_VERSION, CONF
|
||||
@ -251,8 +308,11 @@ def androlyze_main(session:Session, filename:str) -> None:
|
||||
|
||||
if filetype not in ['DEX', 'DEY', 'APK']:
|
||||
logger.error(
|
||||
Fore.RED + "This file type is not supported by androlyze for auto loading right now!" + Fore.RESET,
|
||||
file=sys.stderr)
|
||||
Fore.RED
|
||||
+ "This file type is not supported by androlyze for auto loading right now!"
|
||||
+ Fore.RESET,
|
||||
file=sys.stderr,
|
||||
)
|
||||
logger.error("But your file is still available:")
|
||||
logger.error(">>> filename")
|
||||
logger.error(repr(filename))
|
||||
@ -312,42 +372,59 @@ def androlyze_main(session:Session, filename:str) -> None:
|
||||
ipshell()
|
||||
|
||||
|
||||
def androsign_main(args_apk:list[str], args_hash:str, args_all:bool, show:bool) -> None:
|
||||
def androsign_main(
|
||||
args_apk: list[str], args_hash: str, args_all: bool, show: bool
|
||||
) -> None:
|
||||
import binascii
|
||||
import hashlib
|
||||
import traceback
|
||||
|
||||
from asn1crypto import keys, x509
|
||||
from colorama import Fore, Style
|
||||
|
||||
from androguard.core.apk import APK
|
||||
from androguard.util import get_certificate_name_string
|
||||
|
||||
import hashlib
|
||||
import binascii
|
||||
import traceback
|
||||
from colorama import Fore, Style
|
||||
from asn1crypto import x509, keys
|
||||
|
||||
# Keep the list of hash functions in sync with cli/entry_points.py:sign
|
||||
hashfunctions = dict(md5=hashlib.md5,
|
||||
sha1=hashlib.sha1,
|
||||
sha256=hashlib.sha256,
|
||||
sha512=hashlib.sha512,
|
||||
)
|
||||
hashfunctions = dict(
|
||||
md5=hashlib.md5,
|
||||
sha1=hashlib.sha1,
|
||||
sha256=hashlib.sha256,
|
||||
sha512=hashlib.sha512,
|
||||
)
|
||||
|
||||
if args_hash.lower() not in hashfunctions:
|
||||
print("Hash function {} not supported!"
|
||||
.format(args_hash.lower()), file=sys.stderr)
|
||||
print("Use one of {}"
|
||||
.format(", ".join(hashfunctions.keys())), file=sys.stderr)
|
||||
print(
|
||||
"Hash function {} not supported!".format(args_hash.lower()),
|
||||
file=sys.stderr,
|
||||
)
|
||||
print(
|
||||
"Use one of {}".format(", ".join(hashfunctions.keys())),
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
for path in args_apk:
|
||||
try:
|
||||
a = APK(path)
|
||||
|
||||
print("{}, package: '{}'".format(os.path.basename(path), a.get_package()))
|
||||
print(
|
||||
"{}, package: '{}'".format(
|
||||
os.path.basename(path), a.get_package()
|
||||
)
|
||||
)
|
||||
print("Is signed v1: {}".format(a.is_signed_v1()))
|
||||
print("Is signed v2: {}".format(a.is_signed_v2()))
|
||||
print("Is signed v3: {}".format(a.is_signed_v3()))
|
||||
|
||||
certs = set(a.get_certificates_der_v3() + a.get_certificates_der_v2() + [a.get_certificate_der(x) for x in
|
||||
a.get_signature_names()])
|
||||
pkeys = set(a.get_public_keys_der_v3() + a.get_public_keys_der_v2())
|
||||
certs = set(
|
||||
a.get_certificates_der_v3()
|
||||
+ a.get_certificates_der_v2()
|
||||
+ [a.get_certificate_der(x) for x in a.get_signature_names()]
|
||||
)
|
||||
pkeys = set(
|
||||
a.get_public_keys_der_v3() + a.get_public_keys_der_v2()
|
||||
)
|
||||
|
||||
if len(certs) > 0:
|
||||
print("Found {} unique certificates".format(len(certs)))
|
||||
@ -355,30 +432,61 @@ def androsign_main(args_apk:list[str], args_hash:str, args_all:bool, show:bool)
|
||||
for cert in certs:
|
||||
if show:
|
||||
x509_cert = x509.Certificate.load(cert)
|
||||
print("Issuer:", get_certificate_name_string(x509_cert.issuer, short=True))
|
||||
print("Subject:", get_certificate_name_string(x509_cert.subject, short=True))
|
||||
print(
|
||||
"Issuer:",
|
||||
get_certificate_name_string(
|
||||
x509_cert.issuer, short=True
|
||||
),
|
||||
)
|
||||
print(
|
||||
"Subject:",
|
||||
get_certificate_name_string(
|
||||
x509_cert.subject, short=True
|
||||
),
|
||||
)
|
||||
print("Serial Number:", hex(x509_cert.serial_number))
|
||||
print("Hash Algorithm:", x509_cert.hash_algo)
|
||||
print("Signature Algorithm:", x509_cert.signature_algo)
|
||||
print("Valid not before:", x509_cert['tbs_certificate']['validity']['not_before'].native)
|
||||
print("Valid not after:", x509_cert['tbs_certificate']['validity']['not_after'].native)
|
||||
print(
|
||||
"Valid not before:",
|
||||
x509_cert['tbs_certificate']['validity'][
|
||||
'not_before'
|
||||
].native,
|
||||
)
|
||||
print(
|
||||
"Valid not after:",
|
||||
x509_cert['tbs_certificate']['validity'][
|
||||
'not_after'
|
||||
].native,
|
||||
)
|
||||
|
||||
if not args_all:
|
||||
print("{} {}".format(args_hash.lower(), hashfunctions[args_hash.lower()](cert).hexdigest()))
|
||||
print(
|
||||
"{} {}".format(
|
||||
args_hash.lower(),
|
||||
hashfunctions[args_hash.lower()](cert).hexdigest(),
|
||||
)
|
||||
)
|
||||
else:
|
||||
for k, v in hashfunctions.items():
|
||||
print("{} {}".format(k, v(cert).hexdigest()))
|
||||
print()
|
||||
|
||||
if len(certs) > 0:
|
||||
print("Found {} unique public keys associated with the certs".format(len(pkeys)))
|
||||
print(
|
||||
"Found {} unique public keys associated with the certs".format(
|
||||
len(pkeys)
|
||||
)
|
||||
)
|
||||
|
||||
for public_key in pkeys:
|
||||
if show:
|
||||
parsed_key = parse_public(public_key)
|
||||
print(f"Algorithm: {parsed_key.algorithm}")
|
||||
print(f"Bit size: {parsed_key.bit_size}")
|
||||
print(f"Fingerprint: {calculate_fingerprint(parsed_key).hex()}")
|
||||
print(
|
||||
f"Fingerprint: {calculate_fingerprint(parsed_key).hex()}"
|
||||
)
|
||||
try:
|
||||
print(f"Hash Algorithm: {parsed_key.hash_algo}")
|
||||
except ValueError as ve:
|
||||
@ -386,16 +494,20 @@ def androsign_main(args_apk:list[str], args_hash:str, args_all:bool, show:bool)
|
||||
pass
|
||||
print()
|
||||
|
||||
|
||||
except:
|
||||
print(Fore.RED + "Error in {}".format(os.path.basename(path)) + Style.RESET_ALL, file=sys.stderr)
|
||||
print(
|
||||
Fore.RED
|
||||
+ "Error in {}".format(os.path.basename(path))
|
||||
+ Style.RESET_ALL,
|
||||
file=sys.stderr,
|
||||
)
|
||||
traceback.print_exc(file=sys.stderr)
|
||||
|
||||
if len(args_apk) > 1:
|
||||
print()
|
||||
|
||||
|
||||
def androdis_main(offset:int, size:int, dex_file:str) -> None:
|
||||
def androdis_main(offset: int, size: int, dex_file: str) -> None:
|
||||
from androguard.core.dex import DEX
|
||||
|
||||
with open(dex_file, "rb") as fp:
|
||||
@ -408,7 +520,13 @@ def androdis_main(offset:int, size:int, dex_file:str) -> None:
|
||||
for cls in d.get_classes():
|
||||
print("# CLASS: {}".format(cls.get_name()))
|
||||
for m in cls.get_methods():
|
||||
print("## METHOD: {} {} {}".format(m.get_access_flags_string(), m.get_name(), m.get_descriptor()))
|
||||
print(
|
||||
"## METHOD: {} {} {}".format(
|
||||
m.get_access_flags_string(),
|
||||
m.get_name(),
|
||||
m.get_descriptor(),
|
||||
)
|
||||
)
|
||||
for idx, ins in m.get_instructions_idx():
|
||||
print('{:08x} {}'.format(idx, ins.disasm()))
|
||||
|
||||
@ -428,7 +546,12 @@ def androdis_main(offset:int, size:int, dex_file:str) -> None:
|
||||
idx += i.get_length()
|
||||
|
||||
|
||||
def androtrace_main(apk_file:str, list_modules:list[str], live:bool=False, enable_ui:bool=False) -> None:
|
||||
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
|
||||
|
||||
@ -448,10 +571,14 @@ def androtrace_main(apk_file:str, list_modules:list[str], live:bool=False, enabl
|
||||
|
||||
if enable_ui:
|
||||
logger.remove(1)
|
||||
from prompt_toolkit.eventloop.inputhook import InputHookContext, set_eventloop_with_inputhook
|
||||
from prompt_toolkit.application import get_app
|
||||
import time
|
||||
|
||||
from prompt_toolkit.application import get_app
|
||||
from prompt_toolkit.eventloop.inputhook import (
|
||||
InputHookContext,
|
||||
set_eventloop_with_inputhook,
|
||||
)
|
||||
|
||||
time.sleep(1)
|
||||
|
||||
ui = DynamicUI(p.message_queue)
|
||||
@ -473,7 +600,7 @@ def androtrace_main(apk_file:str, list_modules:list[str], live:bool=False, enabl
|
||||
s = input("Type 'e' to exit:")
|
||||
|
||||
|
||||
def androdump_main(package_name:str, list_modules:list[str]) -> None:
|
||||
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
@ -3,17 +3,20 @@
|
||||
# see https://peps.python.org/pep-0563/
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
from typing import Union
|
||||
|
||||
from androguard import __version__
|
||||
from androguard.core.api_specific_resources import load_permission_mappings, load_permissions
|
||||
from androguard.core.api_specific_resources import (
|
||||
load_permission_mappings,
|
||||
load_permissions,
|
||||
)
|
||||
|
||||
ANDROGUARD_VERSION = __version__
|
||||
|
||||
from colorama import init, Fore
|
||||
from colorama import Fore, init
|
||||
from loguru import logger
|
||||
|
||||
# initialize colorama, only has an effect on windows
|
||||
@ -24,6 +27,7 @@ class InvalidResourceError(Exception):
|
||||
"""
|
||||
Invalid Resource Erorr is thrown by load_api_specific_resource_module
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
@ -46,21 +50,16 @@ def is_ascii_problem(s: str) -> bool:
|
||||
default_conf = {
|
||||
## Configuration for executables used by androguard
|
||||
# Assume the binary is in $PATH, otherwise give full path
|
||||
|
||||
# Runtime variables
|
||||
#
|
||||
# A path to the temporary directory
|
||||
"TMP_DIRECTORY": tempfile.gettempdir(),
|
||||
|
||||
# Function to print stuff
|
||||
"PRINT_FCT": sys.stdout.write,
|
||||
|
||||
# Default API level, if requested API is not available
|
||||
"DEFAULT_API": 16, # this is the minimal API version we have
|
||||
|
||||
# Session, for persistence
|
||||
"SESSION": None,
|
||||
|
||||
# Color output configuration
|
||||
"COLORS": {
|
||||
"OFFSET": Fore.YELLOW,
|
||||
@ -149,7 +148,7 @@ def is_android_raw(raw: bytes) -> str:
|
||||
# probably it would be better to rewrite this and add more sanity checks.
|
||||
if raw[0:2] == b"PK" and b'AndroidManifest.xml' in raw:
|
||||
val = "APK"
|
||||
# check out
|
||||
# check out
|
||||
elif raw[0:3] == b"dex":
|
||||
val = "DEX"
|
||||
elif raw[0:3] == b"dey":
|
||||
@ -161,6 +160,7 @@ def is_android_raw(raw: bytes) -> str:
|
||||
|
||||
return val
|
||||
|
||||
|
||||
def rrmdir(directory: str) -> None:
|
||||
"""
|
||||
Recursivly delete a directory
|
||||
@ -175,7 +175,7 @@ def rrmdir(directory: str) -> None:
|
||||
os.rmdir(directory)
|
||||
|
||||
|
||||
def make_color_tuple(color: str) -> tuple[int,int,int]:
|
||||
def make_color_tuple(color: str) -> tuple[int, int, int]:
|
||||
"""
|
||||
turn something like "#000000" into 0,0,0
|
||||
or "#FFFFFF into "255,255,255"
|
||||
@ -191,7 +191,11 @@ def make_color_tuple(color: str) -> tuple[int,int,int]:
|
||||
return R, G, B
|
||||
|
||||
|
||||
def interpolate_tuple(startcolor: tuple[int,int,int], goalcolor: tuple[int,int,int], steps: int) -> list[str]:
|
||||
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
|
||||
"""
|
||||
@ -234,7 +238,11 @@ def interpolate_tuple(startcolor: tuple[int,int,int], goalcolor: tuple[int,int,i
|
||||
return buffer
|
||||
|
||||
|
||||
def color_range(startcolor: tuple[int,int,int], goalcolor: tuple[int,int,int], steps: int) -> list[str]:
|
||||
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)
|
||||
"""
|
||||
@ -244,7 +252,9 @@ def color_range(startcolor: tuple[int,int,int], goalcolor: tuple[int,int,int], s
|
||||
return interpolate_tuple(start_tuple, goal_tuple, steps)
|
||||
|
||||
|
||||
def load_api_specific_resource_module(resource_name: str, api:Union[str,int,None]=None) -> dict:
|
||||
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.
|
||||
@ -255,11 +265,17 @@ def load_api_specific_resource_module(resource_name: str, api:Union[str,int,None
|
||||
:param api: API version
|
||||
:return: dict
|
||||
"""
|
||||
loader = dict(aosp_permissions=load_permissions,
|
||||
api_permission_mappings=load_permission_mappings)
|
||||
loader = dict(
|
||||
aosp_permissions=load_permissions,
|
||||
api_permission_mappings=load_permission_mappings,
|
||||
)
|
||||
|
||||
if resource_name not in loader:
|
||||
raise InvalidResourceError("Invalid Resource '{}', not in [{}]".format(resource_name, ", ".join(loader.keys())))
|
||||
raise InvalidResourceError(
|
||||
"Invalid Resource '{}', not in [{}]".format(
|
||||
resource_name, ", ".join(loader.keys())
|
||||
)
|
||||
)
|
||||
|
||||
if not api:
|
||||
api = CONF["DEFAULT_API"]
|
||||
@ -268,9 +284,12 @@ def load_api_specific_resource_module(resource_name: str, api:Union[str,int,None
|
||||
|
||||
if ret == {}:
|
||||
# No API mapping found, return default
|
||||
logger.warning("API mapping for API level {} was not found! "
|
||||
"Returning default, which is API level {}".format(api, CONF['DEFAULT_API']))
|
||||
logger.warning(
|
||||
"API mapping for API level {} was not found! "
|
||||
"Returning default, which is API level {}".format(
|
||||
api, CONF['DEFAULT_API']
|
||||
)
|
||||
)
|
||||
ret = loader[resource_name](CONF['DEFAULT_API'])
|
||||
|
||||
return ret
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
@ -10,7 +9,10 @@ from loguru import logger
|
||||
class APILevelNotFoundError(Exception):
|
||||
pass
|
||||
|
||||
def load_permissions(apilevel:Union[str,int], permtype:str='permissions') -> dict[str, dict[str,str]]:
|
||||
|
||||
def load_permissions(
|
||||
apilevel: Union[str, int], permtype: str = 'permissions'
|
||||
) -> dict[str, dict[str, str]]:
|
||||
"""
|
||||
Load the Permissions for the given apilevel.
|
||||
|
||||
@ -37,35 +39,56 @@ def load_permissions(apilevel:Union[str,int], permtype:str='permissions') -> dic
|
||||
apilevel = int(apilevel)
|
||||
|
||||
root = os.path.dirname(os.path.realpath(__file__))
|
||||
permissions_file = os.path.join(root, "aosp_permissions", "permissions_{}.json".format(apilevel))
|
||||
permissions_file = os.path.join(
|
||||
root, "aosp_permissions", "permissions_{}.json".format(apilevel)
|
||||
)
|
||||
|
||||
levels = filter(lambda x: re.match(r'^permissions_\d+\.json$', x), os.listdir(os.path.join(root, "aosp_permissions")))
|
||||
levels = filter(
|
||||
lambda x: re.match(r'^permissions_\d+\.json$', x),
|
||||
os.listdir(os.path.join(root, "aosp_permissions")),
|
||||
)
|
||||
levels = list(map(lambda x: int(x[:-5].split('_')[1]), levels))
|
||||
|
||||
if not levels:
|
||||
logger.error("No Permissions available, can not load!")
|
||||
return {}
|
||||
|
||||
logger.debug("Available API levels: {}".format(", ".join(map(str, sorted(levels)))))
|
||||
logger.debug(
|
||||
"Available API levels: {}".format(", ".join(map(str, sorted(levels))))
|
||||
)
|
||||
|
||||
if not os.path.isfile(permissions_file):
|
||||
if apilevel > max(levels):
|
||||
logger.warning("Requested API level {} is larger than maximum we have, returning API level {} instead.".format(apilevel, max(levels)))
|
||||
logger.warning(
|
||||
"Requested API level {} is larger than maximum we have, returning API level {} instead.".format(
|
||||
apilevel, max(levels)
|
||||
)
|
||||
)
|
||||
return load_permissions(max(levels), permtype)
|
||||
if apilevel < min(levels):
|
||||
logger.warning("Requested API level {} is smaller than minimal we have, returning API level {} instead.".format(apilevel, max(levels)))
|
||||
logger.warning(
|
||||
"Requested API level {} is smaller than minimal we have, returning API level {} instead.".format(
|
||||
apilevel, max(levels)
|
||||
)
|
||||
)
|
||||
return load_permissions(min(levels), permtype)
|
||||
|
||||
# Missing level between existing ones, return the lower level
|
||||
lower_level = max(filter(lambda x: x < apilevel, levels))
|
||||
logger.warning("Requested API Level could not be found, using {} instead".format(lower_level))
|
||||
logger.warning(
|
||||
"Requested API Level could not be found, using {} instead".format(
|
||||
lower_level
|
||||
)
|
||||
)
|
||||
return load_permissions(lower_level, permtype)
|
||||
|
||||
with open(permissions_file, "r") as fp:
|
||||
return json.load(fp)[permtype]
|
||||
|
||||
|
||||
def load_permission_mappings(apilevel:Union[str,int]) -> dict[str, list[str]]:
|
||||
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.
|
||||
@ -74,7 +97,9 @@ def load_permission_mappings(apilevel:Union[str,int]) -> dict[str, list[str]]:
|
||||
:return: a dictionary of {MethodSignature: [List of Permissions]}
|
||||
"""
|
||||
root = os.path.dirname(os.path.realpath(__file__))
|
||||
permissions_file = os.path.join(root, "api_permission_mappings", "permissions_{}.json".format(apilevel))
|
||||
permissions_file = os.path.join(
|
||||
root, "api_permission_mappings", "permissions_{}.json".format(apilevel)
|
||||
)
|
||||
|
||||
if not os.path.isfile(permissions_file):
|
||||
return {}
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -35,17 +35,16 @@ TYPE_INT_HEX = 0x11
|
||||
# The 'data' is either 0 or 1, for input "false" or "true" respectively.
|
||||
TYPE_INT_BOOLEAN = 0x12
|
||||
# Beginning of color integer flavors...
|
||||
TYPE_FIRST_COLOR_INT = 0x1c
|
||||
TYPE_FIRST_COLOR_INT = 0x1C
|
||||
# The 'data' is a raw integer value of the form #aarrggbb.
|
||||
TYPE_INT_COLOR_ARGB8 = 0x1c
|
||||
TYPE_INT_COLOR_ARGB8 = 0x1C
|
||||
# The 'data' is a raw integer value of the form #rrggbb.
|
||||
TYPE_INT_COLOR_RGB8 = 0x1d
|
||||
TYPE_INT_COLOR_RGB8 = 0x1D
|
||||
# The 'data' is a raw integer value of the form #argb.
|
||||
TYPE_INT_COLOR_ARGB4 = 0x1e
|
||||
TYPE_INT_COLOR_ARGB4 = 0x1E
|
||||
# The 'data' is a raw integer value of the form #rgb.
|
||||
TYPE_INT_COLOR_RGB4 = 0x1f
|
||||
TYPE_INT_COLOR_RGB4 = 0x1F
|
||||
# ...end of integer flavors.
|
||||
TYPE_LAST_COLOR_INT = 0x1f
|
||||
TYPE_LAST_COLOR_INT = 0x1F
|
||||
# ...end of integer flavors.
|
||||
TYPE_LAST_INT = 0x1f
|
||||
|
||||
TYPE_LAST_INT = 0x1F
|
||||
|
@ -5,20 +5,22 @@ from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
from struct import pack
|
||||
import textwrap
|
||||
from struct import pack
|
||||
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
|
||||
|
||||
from loguru import logger
|
||||
|
||||
|
||||
def _PrintBanner():
|
||||
print_fct = CONF["PRINT_FCT"]
|
||||
print_fct("*" * 75 + "\n")
|
||||
@ -36,7 +38,9 @@ def _PrintNote(note, tab=0):
|
||||
print_fct = CONF["PRINT_FCT"]
|
||||
note_color = CONF["COLORS"]["NOTE"]
|
||||
normal_color = CONF["COLORS"]["NORMAL"]
|
||||
print_fct("\t" * tab + "{}# {}{}".format(note_color, note, normal_color) + "\n")
|
||||
print_fct(
|
||||
"\t" * tab + "{}# {}{}".format(note_color, note, normal_color) + "\n"
|
||||
)
|
||||
|
||||
|
||||
def _Print(name, arg):
|
||||
@ -57,26 +61,44 @@ def PrettyShowEx(exceptions):
|
||||
if len(exceptions) > 0:
|
||||
CONF["PRINT_FCT"]("Exceptions:\n")
|
||||
for i in exceptions:
|
||||
CONF["PRINT_FCT"]("\t%s%s%s\n" %
|
||||
(CONF["COLORS"]["EXCEPTION"], i.show_buff(),
|
||||
CONF["COLORS"]["NORMAL"]))
|
||||
CONF["PRINT_FCT"](
|
||||
"\t%s%s%s\n"
|
||||
% (
|
||||
CONF["COLORS"]["EXCEPTION"],
|
||||
i.show_buff(),
|
||||
CONF["COLORS"]["NORMAL"],
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def _PrintXRef(tag, items):
|
||||
print_fct = CONF["PRINT_FCT"]
|
||||
for i in items:
|
||||
print_fct("%s: %s %s %s %s\n" %
|
||||
(tag, i[0].get_class_name(), i[0].get_name(),
|
||||
i[0].get_descriptor(), ' '.join("%x" % j.get_idx()
|
||||
for j in i[1])))
|
||||
print_fct(
|
||||
"%s: %s %s %s %s\n"
|
||||
% (
|
||||
tag,
|
||||
i[0].get_class_name(),
|
||||
i[0].get_name(),
|
||||
i[0].get_descriptor(),
|
||||
' '.join("%x" % j.get_idx() for j in i[1]),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def _PrintDRef(tag, items):
|
||||
print_fct = CONF["PRINT_FCT"]
|
||||
for i in items:
|
||||
print_fct("%s: %s %s %s %s\n" %
|
||||
(tag, i[0].get_class_name(), i[0].get_name(),
|
||||
i[0].get_descriptor(), ' '.join("%x" % j for j in i[1])))
|
||||
print_fct(
|
||||
"%s: %s %s %s %s\n"
|
||||
% (
|
||||
tag,
|
||||
i[0].get_class_name(),
|
||||
i[0].get_name(),
|
||||
i[0].get_descriptor(),
|
||||
' '.join("%x" % j for j in i[1]),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def _PrintDefault(msg):
|
||||
@ -90,10 +112,14 @@ def _colorize_operands(operands, colors):
|
||||
"""
|
||||
for operand in operands:
|
||||
if operand[0] == Operand.REGISTER:
|
||||
yield "{}v{}{}".format(colors["registers"], operand[1], colors["normal"])
|
||||
yield "{}v{}{}".format(
|
||||
colors["registers"], operand[1], colors["normal"]
|
||||
)
|
||||
|
||||
elif operand[0] == Operand.LITERAL:
|
||||
yield "{}{}{}".format(colors["literal"], operand[1], colors["normal"])
|
||||
yield "{}{}{}".format(
|
||||
colors["literal"], operand[1], colors["normal"]
|
||||
)
|
||||
|
||||
elif operand[0] == Operand.RAW:
|
||||
yield "{}{}{}".format(colors["raw"], operand[1], colors["normal"])
|
||||
@ -103,20 +129,28 @@ def _colorize_operands(operands, colors):
|
||||
|
||||
elif operand[0] & Operand.KIND:
|
||||
if operand[0] == (Operand.KIND + Kind.STRING):
|
||||
yield "{}{}{}".format(colors["string"], operand[2], colors["normal"])
|
||||
yield "{}{}{}".format(
|
||||
colors["string"], operand[2], colors["normal"]
|
||||
)
|
||||
elif operand[0] == (Operand.KIND + Kind.METH):
|
||||
yield "{}{}{}".format(colors["meth"], operand[2], colors["normal"])
|
||||
yield "{}{}{}".format(
|
||||
colors["meth"], operand[2], colors["normal"]
|
||||
)
|
||||
elif operand[0] == (Operand.KIND + Kind.FIELD):
|
||||
yield "{}{}{}".format(colors["field"], operand[2], colors["normal"])
|
||||
yield "{}{}{}".format(
|
||||
colors["field"], operand[2], colors["normal"]
|
||||
)
|
||||
elif operand[0] == (Operand.KIND + Kind.TYPE):
|
||||
yield "{}{}{}".format(colors["type"], operand[2], colors["normal"])
|
||||
yield "{}{}{}".format(
|
||||
colors["type"], operand[2], colors["normal"]
|
||||
)
|
||||
else:
|
||||
yield "{}".format(repr(operands[2]))
|
||||
else:
|
||||
yield "{}".format(repr(operands[1]))
|
||||
|
||||
|
||||
def PrettyShow(basic_blocks: list[DEXBasicBlock], notes:list=[]) -> None:
|
||||
def PrettyShow(basic_blocks: list[DEXBasicBlock], notes: list = []) -> None:
|
||||
idx = 0
|
||||
|
||||
offset_color = CONF["COLORS"]["OFFSET"]
|
||||
@ -142,51 +176,87 @@ def PrettyShow(basic_blocks: list[DEXBasicBlock], notes:list=[]) -> None:
|
||||
for note in notes[nb]:
|
||||
_PrintNote(note, 1)
|
||||
|
||||
print_fct("\t%s%-3d%s(%s%08x%s) " %
|
||||
(offset_color, nb, normal_color, offset_addr_color, idx,
|
||||
normal_color))
|
||||
print_fct("%s%-20s%s" %
|
||||
(instruction_name_color, ins.get_name(), normal_color))
|
||||
print_fct(
|
||||
"\t%s%-3d%s(%s%08x%s) "
|
||||
% (
|
||||
offset_color,
|
||||
nb,
|
||||
normal_color,
|
||||
offset_addr_color,
|
||||
idx,
|
||||
normal_color,
|
||||
)
|
||||
)
|
||||
print_fct(
|
||||
"%s%-20s%s"
|
||||
% (instruction_name_color, ins.get_name(), normal_color)
|
||||
)
|
||||
|
||||
operands = ins.get_operands()
|
||||
print_fct(
|
||||
"%s" %
|
||||
", ".join(_colorize_operands(operands, colors)))
|
||||
print_fct("%s" % ", ".join(_colorize_operands(operands, colors)))
|
||||
|
||||
op_value = ins.get_op_value()
|
||||
if ins == instructions[-1] and i.childs:
|
||||
print_fct(" ")
|
||||
|
||||
# packed/sparse-switch
|
||||
if (op_value == 0x2b or op_value == 0x2c) and len(i.childs) > 1:
|
||||
if (op_value == 0x2B or op_value == 0x2C) and len(
|
||||
i.childs
|
||||
) > 1:
|
||||
values = i.get_special_ins(idx).get_values()
|
||||
print_fct("%s[ D:%s%s " %
|
||||
(branch_false_color, i.childs[0][2].get_name(),
|
||||
branch_color))
|
||||
print_fct(' '.join("%d:%s" % (
|
||||
values[j], i.childs[j + 1][2].get_name()) for j in
|
||||
range(0, len(i.childs) - 1)) + " ]%s" %
|
||||
normal_color)
|
||||
print_fct(
|
||||
"%s[ D:%s%s "
|
||||
% (
|
||||
branch_false_color,
|
||||
i.childs[0][2].get_name(),
|
||||
branch_color,
|
||||
)
|
||||
)
|
||||
print_fct(
|
||||
' '.join(
|
||||
"%d:%s"
|
||||
% (values[j], i.childs[j + 1][2].get_name())
|
||||
for j in range(0, len(i.childs) - 1)
|
||||
)
|
||||
+ " ]%s" % normal_color
|
||||
)
|
||||
else:
|
||||
if len(i.childs) == 2:
|
||||
print_fct("{}[ {}{} ".format(branch_false_color,
|
||||
i.childs[0][2].get_name(),
|
||||
branch_true_color))
|
||||
print_fct(' '.join("%s" % c[2].get_name(
|
||||
) for c in i.childs[1:]) + " ]%s" % normal_color)
|
||||
print_fct(
|
||||
"{}[ {}{} ".format(
|
||||
branch_false_color,
|
||||
i.childs[0][2].get_name(),
|
||||
branch_true_color,
|
||||
)
|
||||
)
|
||||
print_fct(
|
||||
' '.join(
|
||||
"%s" % c[2].get_name() for c in i.childs[1:]
|
||||
)
|
||||
+ " ]%s" % normal_color
|
||||
)
|
||||
else:
|
||||
print_fct("%s[ " % branch_color + ' '.join(
|
||||
"%s" % c[2].get_name() for c in i.childs) + " ]%s" %
|
||||
normal_color)
|
||||
print_fct(
|
||||
"%s[ " % branch_color
|
||||
+ ' '.join(
|
||||
"%s" % c[2].get_name() for c in i.childs
|
||||
)
|
||||
+ " ]%s" % normal_color
|
||||
)
|
||||
|
||||
idx += ins.get_length()
|
||||
|
||||
print_fct("\n")
|
||||
|
||||
if i.get_exception_analysis():
|
||||
print_fct("\t%s%s%s\n" %
|
||||
(exception_color, i.exception_analysis.show_buff(),
|
||||
normal_color))
|
||||
print_fct(
|
||||
"\t%s%s%s\n"
|
||||
% (
|
||||
exception_color,
|
||||
i.exception_analysis.show_buff(),
|
||||
normal_color,
|
||||
)
|
||||
)
|
||||
|
||||
print_fct("\n")
|
||||
|
||||
@ -205,36 +275,58 @@ def _get_operand_html(operand, registers_colors, colors):
|
||||
:returns: HTML code of the operands
|
||||
"""
|
||||
if operand[0] == Operand.REGISTER:
|
||||
return '<FONT color="{}">v{}</FONT>'.format(registers_colors[operand[1]], operand[1])
|
||||
return '<FONT color="{}">v{}</FONT>'.format(
|
||||
registers_colors[operand[1]], operand[1]
|
||||
)
|
||||
|
||||
if operand[0] == Operand.LITERAL:
|
||||
return '<FONT color="{}">0x{:x}</FONT>'.format(colors["literal"], operand[1])
|
||||
return '<FONT color="{}">0x{:x}</FONT>'.format(
|
||||
colors["literal"], operand[1]
|
||||
)
|
||||
|
||||
if operand[0] == Operand.RAW:
|
||||
wrapped_adjust = '<br />'.join(escape(repr(i)[1:-1]) for i in textwrap.wrap(operand[1], 64))
|
||||
return '<FONT color="{}">{}</FONT>'.format(colors["raw"], wrapped_adjust)
|
||||
wrapped_adjust = '<br />'.join(
|
||||
escape(repr(i)[1:-1]) for i in textwrap.wrap(operand[1], 64)
|
||||
)
|
||||
return '<FONT color="{}">{}</FONT>'.format(
|
||||
colors["raw"], wrapped_adjust
|
||||
)
|
||||
|
||||
if operand[0] == Operand.OFFSET:
|
||||
return '<FONT FACE="Times-Italic" color="{}">@0x{:x}</FONT>'.format(colors["offset"], operand[1])
|
||||
return '<FONT FACE="Times-Italic" color="{}">@0x{:x}</FONT>'.format(
|
||||
colors["offset"], operand[1]
|
||||
)
|
||||
|
||||
if operand[0] & Operand.KIND:
|
||||
if operand[0] == (Operand.KIND + Kind.STRING):
|
||||
wrapped_adjust = "" \<br />"".join(map(escape, textwrap.wrap(operand[2], 64)))
|
||||
return '<FONT color="{}">"{}"</FONT>'.format(colors["string"], wrapped_adjust)
|
||||
wrapped_adjust = "" \<br />"".join(
|
||||
map(escape, textwrap.wrap(operand[2], 64))
|
||||
)
|
||||
return '<FONT color="{}">"{}"</FONT>'.format(
|
||||
colors["string"], wrapped_adjust
|
||||
)
|
||||
|
||||
if operand[0] == (Operand.KIND + Kind.METH):
|
||||
return '<FONT color="{}">{}</FONT>'.format(colors["method"], escape(operand[2]))
|
||||
return '<FONT color="{}">{}</FONT>'.format(
|
||||
colors["method"], escape(operand[2])
|
||||
)
|
||||
if operand[0] == (Operand.KIND + Kind.FIELD):
|
||||
return '<FONT color="{}">{}</FONT>'.format(colors["field"], escape(operand[2]))
|
||||
return '<FONT color="{}">{}</FONT>'.format(
|
||||
colors["field"], escape(operand[2])
|
||||
)
|
||||
if operand[0] == (Operand.KIND + Kind.TYPE):
|
||||
return '<FONT color="{}">{}</FONT>'.format(colors["type"], escape(operand[2]))
|
||||
return '<FONT color="{}">{}</FONT>'.format(
|
||||
colors["type"], escape(operand[2])
|
||||
)
|
||||
|
||||
return escape(str(operand[2]))
|
||||
|
||||
return escape(str(operand[1]))
|
||||
|
||||
|
||||
def method2dot(mx: MethodAnalysis, colors:Union[dict[str,str],None]=None) -> str:
|
||||
def method2dot(
|
||||
mx: MethodAnalysis, colors: Union[dict[str, str], None] = None
|
||||
) -> str:
|
||||
"""
|
||||
Export analysis method to dot format.
|
||||
|
||||
@ -268,7 +360,7 @@ def method2dot(mx: MethodAnalysis, colors:Union[dict[str,str],None]=None) -> str
|
||||
"method": "#DF3A01",
|
||||
"field": "#088A08",
|
||||
"type": "#0000FF",
|
||||
"registers_range": ("#999933", "#6666FF")
|
||||
"registers_range": ("#999933", "#6666FF"),
|
||||
}
|
||||
|
||||
node_tpl = """
|
||||
@ -287,7 +379,9 @@ def method2dot(mx: MethodAnalysis, colors:Union[dict[str,str],None]=None) -> str
|
||||
<FONT FACE="{font_face}" color="%s">%s</FONT> %s
|
||||
</TD>
|
||||
</TR>
|
||||
""".format(font_face=font_face)
|
||||
""".format(
|
||||
font_face=font_face
|
||||
)
|
||||
|
||||
link_tpl = '<TR><TD PORT="{}"></TD></TR>\n'
|
||||
|
||||
@ -297,12 +391,26 @@ def method2dot(mx: MethodAnalysis, colors:Union[dict[str,str],None]=None) -> str
|
||||
method = mx.get_method()
|
||||
|
||||
# This is used as a seed to create unique hashes for the nodes
|
||||
sha256 = hashlib.sha256((mx.get_method().get_class_name() + mx.get_method().get_name() + mx.get_method().get_descriptor()).encode("utf-8")).hexdigest()
|
||||
sha256 = hashlib.sha256(
|
||||
(
|
||||
mx.get_method().get_class_name()
|
||||
+ mx.get_method().get_name()
|
||||
+ mx.get_method().get_descriptor()
|
||||
).encode("utf-8")
|
||||
).hexdigest()
|
||||
|
||||
# Collect all used Registers and create colors
|
||||
if method.get_code() and method.get_code().get_registers_size() != 0:
|
||||
registers = {i: c for i, c in enumerate(color_range(colors["registers_range"][0], colors["registers_range"][1],
|
||||
method.get_code().get_registers_size()))}
|
||||
registers = {
|
||||
i: c
|
||||
for i, c in enumerate(
|
||||
color_range(
|
||||
colors["registers_range"][0],
|
||||
colors["registers_range"][1],
|
||||
method.get_code().get_registers_size(),
|
||||
)
|
||||
)
|
||||
}
|
||||
else:
|
||||
registers = dict()
|
||||
|
||||
@ -311,27 +419,48 @@ def method2dot(mx: MethodAnalysis, colors:Union[dict[str,str],None]=None) -> str
|
||||
# Go through all basic blocks and create the CFG
|
||||
for basic_block in mx.basic_blocks:
|
||||
ins_idx = basic_block.start
|
||||
block_id = hashlib.md5((sha256 + basic_block.get_name()).encode("utf-8")).hexdigest()
|
||||
block_id = hashlib.md5(
|
||||
(sha256 + basic_block.get_name()).encode("utf-8")
|
||||
).hexdigest()
|
||||
|
||||
content = link_tpl.format('header')
|
||||
|
||||
for instruction in basic_block.get_instructions():
|
||||
if instruction.get_op_value() in (0x2b, 0x2c):
|
||||
new_links.append((basic_block, ins_idx, instruction.get_ref_off() * 2 + ins_idx))
|
||||
if instruction.get_op_value() in (0x2B, 0x2C):
|
||||
new_links.append(
|
||||
(
|
||||
basic_block,
|
||||
ins_idx,
|
||||
instruction.get_ref_off() * 2 + ins_idx,
|
||||
)
|
||||
)
|
||||
elif instruction.get_op_value() == 0x26:
|
||||
new_links.append((basic_block, ins_idx, instruction.get_ref_off() * 2 + ins_idx))
|
||||
new_links.append(
|
||||
(
|
||||
basic_block,
|
||||
ins_idx,
|
||||
instruction.get_ref_off() * 2 + ins_idx,
|
||||
)
|
||||
)
|
||||
|
||||
operands = instruction.get_operands(ins_idx)
|
||||
output = ", ".join(_get_operand_html(i, registers, colors) for i in operands)
|
||||
output = ", ".join(
|
||||
_get_operand_html(i, registers, colors) for i in operands
|
||||
)
|
||||
|
||||
bg_idx = colors["bg_idx"]
|
||||
if ins_idx == 0 and "bg_start_idx" in colors:
|
||||
bg_idx = colors["bg_start_idx"]
|
||||
|
||||
content += label_tpl % (
|
||||
bg_idx, colors["idx"], ins_idx, colors["bg_instruction"],
|
||||
bg_idx,
|
||||
colors["idx"],
|
||||
ins_idx,
|
||||
colors["bg_instruction"],
|
||||
colors["instruction_name"],
|
||||
instruction.get_name(), output)
|
||||
instruction.get_name(),
|
||||
output,
|
||||
)
|
||||
|
||||
ins_idx += instruction.get_length()
|
||||
|
||||
@ -350,10 +479,17 @@ def method2dot(mx: MethodAnalysis, colors:Union[dict[str,str],None]=None) -> str
|
||||
values = None
|
||||
# The last instruction is important and still set from the loop
|
||||
# FIXME: what if there is no instruction in the basic block?
|
||||
if instruction.get_op_value() in (0x2b, 0x2c) and len(basic_block.childs) > 1:
|
||||
if (
|
||||
instruction.get_op_value() in (0x2B, 0x2C)
|
||||
and len(basic_block.childs) > 1
|
||||
):
|
||||
val = colors["default_branch"]
|
||||
values = ["default"]
|
||||
values.extend(basic_block.get_special_ins(ins_idx - instruction.get_length()).get_values())
|
||||
values.extend(
|
||||
basic_block.get_special_ins(
|
||||
ins_idx - instruction.get_length()
|
||||
).get_values()
|
||||
)
|
||||
|
||||
# updating dot edges
|
||||
for DVMBasicMethodBlockChild in basic_block.childs:
|
||||
@ -362,10 +498,14 @@ def method2dot(mx: MethodAnalysis, colors:Union[dict[str,str],None]=None) -> str
|
||||
if values:
|
||||
label_edge = values.pop(0)
|
||||
|
||||
child_id = hashlib.md5((sha256 + DVMBasicMethodBlockChild[-1].get_name()).encode("utf-8")).hexdigest()
|
||||
edges_html += "struct_{}:tail -> struct_{}:header [color=\"{}\", label=\"{}\"];\n".format(block_id,
|
||||
child_id, val,
|
||||
label_edge)
|
||||
child_id = hashlib.md5(
|
||||
(sha256 + DVMBasicMethodBlockChild[-1].get_name()).encode(
|
||||
"utf-8"
|
||||
)
|
||||
).hexdigest()
|
||||
edges_html += "struct_{}:tail -> struct_{}:header [color=\"{}\", label=\"{}\"];\n".format(
|
||||
block_id, child_id, val, label_edge
|
||||
)
|
||||
|
||||
# color switch
|
||||
if val == colors["false_branch"]:
|
||||
@ -378,26 +518,42 @@ def method2dot(mx: MethodAnalysis, colors:Union[dict[str,str],None]=None) -> str
|
||||
for exception_elem in exception_analysis.exceptions:
|
||||
exception_block = exception_elem[-1]
|
||||
if exception_block:
|
||||
exception_id = hashlib.md5((sha256 + exception_block.get_name()).encode("utf-8")).hexdigest()
|
||||
exception_id = hashlib.md5(
|
||||
(sha256 + exception_block.get_name()).encode("utf-8")
|
||||
).hexdigest()
|
||||
edges_html += "struct_{}:tail -> struct_{}:header [color=\"{}\", label=\"{}\"];\n".format(
|
||||
block_id, exception_id, "black", exception_elem[0])
|
||||
block_id, exception_id, "black", exception_elem[0]
|
||||
)
|
||||
|
||||
for link in new_links:
|
||||
basic_block = link[0]
|
||||
DVMBasicMethodBlockChild = mx.basic_blocks.get_basic_block(link[2])
|
||||
|
||||
if DVMBasicMethodBlockChild:
|
||||
block_id = hashlib.md5((sha256 + basic_block.get_name()).encode("utf-8")).hexdigest()
|
||||
child_id = hashlib.md5((sha256 + DVMBasicMethodBlockChild.get_name()).encode("utf-8")).hexdigest()
|
||||
block_id = hashlib.md5(
|
||||
(sha256 + basic_block.get_name()).encode("utf-8")
|
||||
).hexdigest()
|
||||
child_id = hashlib.md5(
|
||||
(sha256 + DVMBasicMethodBlockChild.get_name()).encode("utf-8")
|
||||
).hexdigest()
|
||||
|
||||
edges_html += "struct_{}:tail -> struct_{}:header [color=\"{}\", label=\"data(0x{:x}) to @0x{:x}\", style=\"dashed\"];\n".format(
|
||||
block_id, child_id, "yellow", link[1], link[2])
|
||||
block_id, child_id, "yellow", link[1], link[2]
|
||||
)
|
||||
|
||||
method_label = method.get_class_name() + "." + method.get_name() + "->" + method.get_descriptor()
|
||||
method_label = (
|
||||
method.get_class_name()
|
||||
+ "."
|
||||
+ method.get_name()
|
||||
+ "->"
|
||||
+ method.get_descriptor()
|
||||
)
|
||||
|
||||
method_information = method.get_information()
|
||||
if method_information:
|
||||
method_label += "\\nLocal registers v{} ... v{}".format(*method_information["registers"])
|
||||
method_label += "\\nLocal registers v{} ... v{}".format(
|
||||
*method_information["registers"]
|
||||
)
|
||||
if "params" in method_information:
|
||||
for register, rtype in method_information["params"]:
|
||||
method_label += "\\nparam v%d = %s" % (register, rtype)
|
||||
@ -407,10 +563,11 @@ def method2dot(mx: MethodAnalysis, colors:Union[dict[str,str],None]=None) -> str
|
||||
|
||||
|
||||
def method2format(
|
||||
output:str,
|
||||
_format:str="png",
|
||||
mx:Union[MethodAnalysis,None]=None,
|
||||
raw:Union[str,None]=None):
|
||||
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`.
|
||||
@ -453,11 +610,12 @@ def method2format(
|
||||
|
||||
{edges}
|
||||
}}
|
||||
""".format(clustername=hashlib.md5(output.encode("UTF-8")).hexdigest(),
|
||||
classname=data['name'],
|
||||
nodes=data['nodes'],
|
||||
edges=data['edges'],
|
||||
)
|
||||
""".format(
|
||||
clustername=hashlib.md5(output.encode("UTF-8")).hexdigest(),
|
||||
classname=data['name'],
|
||||
nodes=data['nodes'],
|
||||
edges=data['edges'],
|
||||
)
|
||||
|
||||
# NOTE: In certain cases the graph_from_dot_data function might fail.
|
||||
# There is a bug in the code that certain html strings are interpreted as comment
|
||||
@ -478,17 +636,24 @@ def method2format(
|
||||
d = pydot.graph_from_dot_data(buff)
|
||||
if len(d) > 1:
|
||||
# Not sure what to do in this case?!
|
||||
logger.warning("The graph generated for '{}' has too many subgraphs! "
|
||||
"Only plotting the first one.".format(output))
|
||||
logger.warning(
|
||||
"The graph generated for '{}' has too many subgraphs! "
|
||||
"Only plotting the first one.".format(output)
|
||||
)
|
||||
for g in d:
|
||||
try:
|
||||
getattr(g, "write_" + _format.lower())(output)
|
||||
break
|
||||
except FileNotFoundError:
|
||||
logger.error("Could not write graph image, ensure graphviz is installed!")
|
||||
logger.error(
|
||||
"Could not write graph image, ensure graphviz is installed!"
|
||||
)
|
||||
raise
|
||||
|
||||
def method2png(output:str, mx:MethodAnalysis, raw:Union[str,None]=None) -> None:
|
||||
|
||||
def method2png(
|
||||
output: str, mx: MethodAnalysis, raw: Union[str, None] = None
|
||||
) -> None:
|
||||
"""
|
||||
Export method to a png file format
|
||||
|
||||
@ -506,7 +671,9 @@ def method2png(output:str, mx:MethodAnalysis, raw:Union[str,None]=None) -> None:
|
||||
method2format(output, "png", mx, buff)
|
||||
|
||||
|
||||
def method2jpg(output:str, mx:MethodAnalysis, raw:Union[str,None]=None) -> None:
|
||||
def method2jpg(
|
||||
output: str, mx: MethodAnalysis, raw: Union[str, None] = None
|
||||
) -> None:
|
||||
"""
|
||||
Export method to a jpg file format
|
||||
|
||||
@ -524,7 +691,7 @@ def method2jpg(output:str, mx:MethodAnalysis, raw:Union[str,None]=None) -> None:
|
||||
method2format(output, "jpg", mx, buff)
|
||||
|
||||
|
||||
def vm2json(vm:DEX) -> str:
|
||||
def vm2json(vm: DEX) -> str:
|
||||
"""
|
||||
Get a JSON representation of a DEX file
|
||||
|
||||
@ -547,14 +714,14 @@ def vm2json(vm:DEX) -> str:
|
||||
|
||||
|
||||
class TmpBlock:
|
||||
def __init__(self, name:str) -> None:
|
||||
def __init__(self, name: str) -> None:
|
||||
self.name = name
|
||||
|
||||
def get_name(self) -> str:
|
||||
return self.name
|
||||
|
||||
|
||||
def method2json(mx: MethodAnalysis, directed_graph:bool=False) -> str:
|
||||
def method2json(mx: MethodAnalysis, directed_graph: bool = False) -> str:
|
||||
"""
|
||||
Create directed or undirected graph in the json format.
|
||||
|
||||
@ -578,14 +745,23 @@ def method2json_undirect(mx: MethodAnalysis) -> str:
|
||||
d["reports"] = reports
|
||||
|
||||
for DVMBasicMethodBlock in mx.basic_blocks.gets():
|
||||
cblock = {"BasicBlockId": DVMBasicMethodBlock.get_name(),
|
||||
"registers": mx.get_method().get_code().get_registers_size(), "instructions": []}
|
||||
cblock = {
|
||||
"BasicBlockId": DVMBasicMethodBlock.get_name(),
|
||||
"registers": mx.get_method().get_code().get_registers_size(),
|
||||
"instructions": [],
|
||||
}
|
||||
|
||||
ins_idx = DVMBasicMethodBlock.start
|
||||
for DVMBasicMethodBlockInstruction in DVMBasicMethodBlock.get_instructions():
|
||||
c_ins = {"idx": ins_idx, "name": DVMBasicMethodBlockInstruction.get_name(),
|
||||
"operands": DVMBasicMethodBlockInstruction.get_operands(
|
||||
ins_idx)}
|
||||
for (
|
||||
DVMBasicMethodBlockInstruction
|
||||
) in DVMBasicMethodBlock.get_instructions():
|
||||
c_ins = {
|
||||
"idx": ins_idx,
|
||||
"name": DVMBasicMethodBlockInstruction.get_name(),
|
||||
"operands": DVMBasicMethodBlockInstruction.get_operands(
|
||||
ins_idx
|
||||
),
|
||||
}
|
||||
|
||||
cblock["instructions"].append(c_ins)
|
||||
ins_idx += DVMBasicMethodBlockInstruction.get_length()
|
||||
@ -613,18 +789,25 @@ def method2json_direct(mx: MethodAnalysis) -> str:
|
||||
|
||||
l = []
|
||||
for DVMBasicMethodBlock in mx.basic_blocks.gets():
|
||||
for index, DVMBasicMethodBlockChild in enumerate(DVMBasicMethodBlock.childs):
|
||||
if DVMBasicMethodBlock.get_name() == DVMBasicMethodBlockChild[-1].get_name():
|
||||
for index, DVMBasicMethodBlockChild in enumerate(
|
||||
DVMBasicMethodBlock.childs
|
||||
):
|
||||
if (
|
||||
DVMBasicMethodBlock.get_name()
|
||||
== DVMBasicMethodBlockChild[-1].get_name()
|
||||
):
|
||||
|
||||
preblock = TmpBlock(DVMBasicMethodBlock.get_name() + "-pre")
|
||||
|
||||
cnblock = {"BasicBlockId": DVMBasicMethodBlock.get_name() + "-pre",
|
||||
"start": DVMBasicMethodBlock.start,
|
||||
"notes": [],
|
||||
"Edge": [DVMBasicMethodBlock.get_name()],
|
||||
"registers": 0,
|
||||
"instructions": [],
|
||||
"info_bb": 0}
|
||||
cnblock = {
|
||||
"BasicBlockId": DVMBasicMethodBlock.get_name() + "-pre",
|
||||
"start": DVMBasicMethodBlock.start,
|
||||
"notes": [],
|
||||
"Edge": [DVMBasicMethodBlock.get_name()],
|
||||
"registers": 0,
|
||||
"instructions": [],
|
||||
"info_bb": 0,
|
||||
}
|
||||
|
||||
l.append(cnblock)
|
||||
|
||||
@ -633,29 +816,40 @@ def method2json_direct(mx: MethodAnalysis) -> str:
|
||||
hooks[parent[-1].get_name()].append(preblock)
|
||||
|
||||
for idx, child in enumerate(parent[-1].childs):
|
||||
if child[-1].get_name() == DVMBasicMethodBlock.get_name(
|
||||
if (
|
||||
child[-1].get_name()
|
||||
== DVMBasicMethodBlock.get_name()
|
||||
):
|
||||
hooks[parent[-1].get_name()].append(child[-1])
|
||||
|
||||
for DVMBasicMethodBlock in mx.basic_blocks.gets():
|
||||
cblock = {"BasicBlockId": DVMBasicMethodBlock.get_name(),
|
||||
"start": DVMBasicMethodBlock.start,
|
||||
"notes": DVMBasicMethodBlock.get_notes(),
|
||||
"registers": mx.get_method().get_code().get_registers_size(),
|
||||
"instructions": []}
|
||||
cblock = {
|
||||
"BasicBlockId": DVMBasicMethodBlock.get_name(),
|
||||
"start": DVMBasicMethodBlock.start,
|
||||
"notes": DVMBasicMethodBlock.get_notes(),
|
||||
"registers": mx.get_method().get_code().get_registers_size(),
|
||||
"instructions": [],
|
||||
}
|
||||
|
||||
ins_idx = DVMBasicMethodBlock.start
|
||||
last_instru = None
|
||||
for DVMBasicMethodBlockInstruction in DVMBasicMethodBlock.get_instructions():
|
||||
c_ins = {"idx": ins_idx,
|
||||
"name": DVMBasicMethodBlockInstruction.get_name(),
|
||||
"operands": DVMBasicMethodBlockInstruction.get_operands(ins_idx),
|
||||
}
|
||||
for (
|
||||
DVMBasicMethodBlockInstruction
|
||||
) in DVMBasicMethodBlock.get_instructions():
|
||||
c_ins = {
|
||||
"idx": ins_idx,
|
||||
"name": DVMBasicMethodBlockInstruction.get_name(),
|
||||
"operands": DVMBasicMethodBlockInstruction.get_operands(
|
||||
ins_idx
|
||||
),
|
||||
}
|
||||
|
||||
cblock["instructions"].append(c_ins)
|
||||
|
||||
if (DVMBasicMethodBlockInstruction.get_op_value() == 0x2b or
|
||||
DVMBasicMethodBlockInstruction.get_op_value() == 0x2c):
|
||||
if (
|
||||
DVMBasicMethodBlockInstruction.get_op_value() == 0x2B
|
||||
or DVMBasicMethodBlockInstruction.get_op_value() == 0x2C
|
||||
):
|
||||
values = DVMBasicMethodBlock.get_special_ins(ins_idx)
|
||||
cblock["info_next"] = values.get_values()
|
||||
|
||||
@ -667,17 +861,24 @@ def method2json_direct(mx: MethodAnalysis) -> str:
|
||||
if len(DVMBasicMethodBlock.childs) > 1:
|
||||
cblock["info_bb"] = 1
|
||||
|
||||
if (last_instru.get_op_value() == 0x2b or
|
||||
last_instru.get_op_value() == 0x2c):
|
||||
if (
|
||||
last_instru.get_op_value() == 0x2B
|
||||
or last_instru.get_op_value() == 0x2C
|
||||
):
|
||||
cblock["info_bb"] = 2
|
||||
|
||||
cblock["Edge"] = []
|
||||
for DVMBasicMethodBlockChild in DVMBasicMethodBlock.childs:
|
||||
ok = False
|
||||
if DVMBasicMethodBlock.get_name() in hooks:
|
||||
if DVMBasicMethodBlockChild[-1] in hooks[DVMBasicMethodBlock.get_name()]:
|
||||
if (
|
||||
DVMBasicMethodBlockChild[-1]
|
||||
in hooks[DVMBasicMethodBlock.get_name()]
|
||||
):
|
||||
ok = True
|
||||
cblock["Edge"].append(hooks[DVMBasicMethodBlock.get_name()][0].get_name())
|
||||
cblock["Edge"].append(
|
||||
hooks[DVMBasicMethodBlock.get_name()][0].get_name()
|
||||
)
|
||||
|
||||
if not ok:
|
||||
cblock["Edge"].append(DVMBasicMethodBlockChild[-1].get_name())
|
||||
@ -693,7 +894,7 @@ def method2json_direct(mx: MethodAnalysis) -> str:
|
||||
return json.dumps(d)
|
||||
|
||||
|
||||
def object_to_bytes(obj:Union[str,bool,int,bytearray]) -> bytearray:
|
||||
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.
|
||||
@ -712,7 +913,7 @@ def object_to_bytes(obj:Union[str,bool,int,bytearray]) -> bytearray:
|
||||
return obj.get_raw()
|
||||
|
||||
|
||||
def FormatClassToJava(i:str) -> str:
|
||||
def FormatClassToJava(i: str) -> str:
|
||||
"""
|
||||
Transform a java class name into the typed variant found in DEX files.
|
||||
|
||||
@ -727,7 +928,7 @@ def FormatClassToJava(i:str) -> str:
|
||||
return "L" + i.replace(".", "/") + ";"
|
||||
|
||||
|
||||
def FormatClassToPython(i:str) -> str:
|
||||
def FormatClassToPython(i: str) -> str:
|
||||
"""
|
||||
Transform a typed class name into a form which can be used as a python
|
||||
attribute
|
||||
@ -747,7 +948,7 @@ def FormatClassToPython(i:str) -> str:
|
||||
return i
|
||||
|
||||
|
||||
def get_package_class_name(name:str)->tuple[str, str]:
|
||||
def get_package_class_name(name: str) -> tuple[str, str]:
|
||||
"""
|
||||
Return package and class name in a java variant from a typed variant name.
|
||||
|
||||
@ -771,13 +972,17 @@ def get_package_class_name(name:str)->tuple[str, str]:
|
||||
# name is MUTF8, so make sure we get the string variant
|
||||
name = str(name)
|
||||
if name[-1] != ';':
|
||||
raise ValueError("The name '{}' does not look like a typed name!".format(name))
|
||||
raise ValueError(
|
||||
"The name '{}' does not look like a typed name!".format(name)
|
||||
)
|
||||
|
||||
# discard array types, there might be many...
|
||||
name = name.lstrip('[')
|
||||
|
||||
if name[0] != 'L':
|
||||
raise ValueError("The name '{}' does not look like a typed name!".format(name))
|
||||
raise ValueError(
|
||||
"The name '{}' does not look like a typed name!".format(name)
|
||||
)
|
||||
|
||||
name = name[1:-1]
|
||||
if '/' not in name:
|
||||
@ -789,7 +994,7 @@ def get_package_class_name(name:str)->tuple[str, str]:
|
||||
return package, clsname
|
||||
|
||||
|
||||
def FormatNameToPython(i:str) -> str:
|
||||
def FormatNameToPython(i: str) -> str:
|
||||
"""
|
||||
Transform a (method) name into a form which can be used as a python
|
||||
attribute
|
||||
@ -810,7 +1015,7 @@ def FormatNameToPython(i:str) -> str:
|
||||
return i
|
||||
|
||||
|
||||
def FormatDescriptorToPython(i:str) -> str:
|
||||
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
@ -1,8 +1,9 @@
|
||||
from enum import IntEnum
|
||||
from collections import OrderedDict
|
||||
from enum import IntEnum
|
||||
|
||||
# This file contains dictionaries used in the Dalvik Format.
|
||||
|
||||
|
||||
# Used to identify different types of operands
|
||||
class Kind(IntEnum):
|
||||
"""
|
||||
@ -12,6 +13,7 @@ class Kind(IntEnum):
|
||||
It is used to reference the actual item instead of the refernece index
|
||||
from the :class:`ClassManager` when disassembling the bytecode.
|
||||
"""
|
||||
|
||||
# Indicates a method reference
|
||||
METH = 0
|
||||
# Indicates that opcode argument is a string index
|
||||
@ -41,6 +43,7 @@ class Operand(IntEnum):
|
||||
"""
|
||||
Enumeration used for the operand type of opcodes
|
||||
"""
|
||||
|
||||
REGISTER = 0
|
||||
LITERAL = 1
|
||||
RAW = 2
|
||||
@ -72,44 +75,115 @@ class TypeMapItem(IntEnum):
|
||||
ANNOTATION_ITEM = 0x2004
|
||||
ENCODED_ARRAY_ITEM = 0x2005
|
||||
ANNOTATIONS_DIRECTORY_ITEM = 0x2006
|
||||
HIDDENAPI_CLASS_DATA_ITEM = 0xf000
|
||||
HIDDENAPI_CLASS_DATA_ITEM = 0xF000
|
||||
|
||||
@staticmethod
|
||||
def _get_dependencies():
|
||||
return OrderedDict([
|
||||
(TypeMapItem.HEADER_ITEM, set()),
|
||||
(TypeMapItem.STRING_ID_ITEM, {TypeMapItem.STRING_DATA_ITEM}),
|
||||
(TypeMapItem.TYPE_ID_ITEM, {TypeMapItem.STRING_ID_ITEM}),
|
||||
(TypeMapItem.PROTO_ID_ITEM, {TypeMapItem.STRING_ID_ITEM, TypeMapItem.TYPE_ID_ITEM, TypeMapItem.TYPE_LIST}),
|
||||
(TypeMapItem.FIELD_ID_ITEM, {TypeMapItem.STRING_ID_ITEM, TypeMapItem.TYPE_ID_ITEM}),
|
||||
(TypeMapItem.METHOD_ID_ITEM,
|
||||
{TypeMapItem.STRING_ID_ITEM, TypeMapItem.TYPE_ID_ITEM, TypeMapItem.PROTO_ID_ITEM}),
|
||||
(TypeMapItem.CLASS_DEF_ITEM,
|
||||
{TypeMapItem.TYPE_ID_ITEM, TypeMapItem.TYPE_LIST, TypeMapItem.STRING_ID_ITEM, TypeMapItem.DEBUG_INFO_ITEM,
|
||||
TypeMapItem.ANNOTATIONS_DIRECTORY_ITEM, TypeMapItem.CLASS_DATA_ITEM, TypeMapItem.ENCODED_ARRAY_ITEM}),
|
||||
(TypeMapItem.CALL_SITE_ITEM,
|
||||
{TypeMapItem.METHOD_HANDLE_ITEM, TypeMapItem.STRING_ID_ITEM, TypeMapItem.METHOD_ID_ITEM}),
|
||||
# TODO: check if this is correct
|
||||
(TypeMapItem.METHOD_HANDLE_ITEM, {TypeMapItem.FIELD_ID_ITEM, TypeMapItem.METHOD_ID_ITEM}),
|
||||
# TODO: check if this is correct
|
||||
(TypeMapItem.MAP_LIST, set()),
|
||||
(TypeMapItem.TYPE_LIST, {TypeMapItem.TYPE_ID_ITEM}),
|
||||
(TypeMapItem.ANNOTATION_SET_REF_LIST, {TypeMapItem.ANNOTATION_SET_ITEM}),
|
||||
(TypeMapItem.ANNOTATION_SET_ITEM, {TypeMapItem.ANNOTATION_ITEM}),
|
||||
(TypeMapItem.CLASS_DATA_ITEM, {TypeMapItem.FIELD_ID_ITEM, TypeMapItem.METHOD_ID_ITEM}),
|
||||
(TypeMapItem.CODE_ITEM, {TypeMapItem.DEBUG_INFO_ITEM, TypeMapItem.TYPE_ID_ITEM}),
|
||||
(TypeMapItem.STRING_DATA_ITEM, set()),
|
||||
(TypeMapItem.DEBUG_INFO_ITEM, {TypeMapItem.STRING_ID_ITEM, TypeMapItem.TYPE_ID_ITEM}),
|
||||
(TypeMapItem.ANNOTATION_ITEM,
|
||||
{TypeMapItem.PROTO_ID_ITEM, TypeMapItem.STRING_ID_ITEM, TypeMapItem.TYPE_ID_ITEM,
|
||||
TypeMapItem.FIELD_ID_ITEM, TypeMapItem.METHOD_ID_ITEM}),
|
||||
(TypeMapItem.ENCODED_ARRAY_ITEM,
|
||||
{TypeMapItem.PROTO_ID_ITEM, TypeMapItem.STRING_ID_ITEM, TypeMapItem.TYPE_ID_ITEM,
|
||||
TypeMapItem.FIELD_ID_ITEM, TypeMapItem.METHOD_ID_ITEM}),
|
||||
(TypeMapItem.ANNOTATIONS_DIRECTORY_ITEM,
|
||||
{TypeMapItem.FIELD_ID_ITEM, TypeMapItem.METHOD_ID_ITEM, TypeMapItem.ANNOTATION_SET_ITEM}),
|
||||
(TypeMapItem.HIDDENAPI_CLASS_DATA_ITEM, set()),
|
||||
])
|
||||
return OrderedDict(
|
||||
[
|
||||
(TypeMapItem.HEADER_ITEM, set()),
|
||||
(TypeMapItem.STRING_ID_ITEM, {TypeMapItem.STRING_DATA_ITEM}),
|
||||
(TypeMapItem.TYPE_ID_ITEM, {TypeMapItem.STRING_ID_ITEM}),
|
||||
(
|
||||
TypeMapItem.PROTO_ID_ITEM,
|
||||
{
|
||||
TypeMapItem.STRING_ID_ITEM,
|
||||
TypeMapItem.TYPE_ID_ITEM,
|
||||
TypeMapItem.TYPE_LIST,
|
||||
},
|
||||
),
|
||||
(
|
||||
TypeMapItem.FIELD_ID_ITEM,
|
||||
{TypeMapItem.STRING_ID_ITEM, TypeMapItem.TYPE_ID_ITEM},
|
||||
),
|
||||
(
|
||||
TypeMapItem.METHOD_ID_ITEM,
|
||||
{
|
||||
TypeMapItem.STRING_ID_ITEM,
|
||||
TypeMapItem.TYPE_ID_ITEM,
|
||||
TypeMapItem.PROTO_ID_ITEM,
|
||||
},
|
||||
),
|
||||
(
|
||||
TypeMapItem.CLASS_DEF_ITEM,
|
||||
{
|
||||
TypeMapItem.TYPE_ID_ITEM,
|
||||
TypeMapItem.TYPE_LIST,
|
||||
TypeMapItem.STRING_ID_ITEM,
|
||||
TypeMapItem.DEBUG_INFO_ITEM,
|
||||
TypeMapItem.ANNOTATIONS_DIRECTORY_ITEM,
|
||||
TypeMapItem.CLASS_DATA_ITEM,
|
||||
TypeMapItem.ENCODED_ARRAY_ITEM,
|
||||
},
|
||||
),
|
||||
(
|
||||
TypeMapItem.CALL_SITE_ITEM,
|
||||
{
|
||||
TypeMapItem.METHOD_HANDLE_ITEM,
|
||||
TypeMapItem.STRING_ID_ITEM,
|
||||
TypeMapItem.METHOD_ID_ITEM,
|
||||
},
|
||||
),
|
||||
# TODO: check if this is correct
|
||||
(
|
||||
TypeMapItem.METHOD_HANDLE_ITEM,
|
||||
{TypeMapItem.FIELD_ID_ITEM, TypeMapItem.METHOD_ID_ITEM},
|
||||
),
|
||||
# TODO: check if this is correct
|
||||
(TypeMapItem.MAP_LIST, set()),
|
||||
(TypeMapItem.TYPE_LIST, {TypeMapItem.TYPE_ID_ITEM}),
|
||||
(
|
||||
TypeMapItem.ANNOTATION_SET_REF_LIST,
|
||||
{TypeMapItem.ANNOTATION_SET_ITEM},
|
||||
),
|
||||
(
|
||||
TypeMapItem.ANNOTATION_SET_ITEM,
|
||||
{TypeMapItem.ANNOTATION_ITEM},
|
||||
),
|
||||
(
|
||||
TypeMapItem.CLASS_DATA_ITEM,
|
||||
{TypeMapItem.FIELD_ID_ITEM, TypeMapItem.METHOD_ID_ITEM},
|
||||
),
|
||||
(
|
||||
TypeMapItem.CODE_ITEM,
|
||||
{TypeMapItem.DEBUG_INFO_ITEM, TypeMapItem.TYPE_ID_ITEM},
|
||||
),
|
||||
(TypeMapItem.STRING_DATA_ITEM, set()),
|
||||
(
|
||||
TypeMapItem.DEBUG_INFO_ITEM,
|
||||
{TypeMapItem.STRING_ID_ITEM, TypeMapItem.TYPE_ID_ITEM},
|
||||
),
|
||||
(
|
||||
TypeMapItem.ANNOTATION_ITEM,
|
||||
{
|
||||
TypeMapItem.PROTO_ID_ITEM,
|
||||
TypeMapItem.STRING_ID_ITEM,
|
||||
TypeMapItem.TYPE_ID_ITEM,
|
||||
TypeMapItem.FIELD_ID_ITEM,
|
||||
TypeMapItem.METHOD_ID_ITEM,
|
||||
},
|
||||
),
|
||||
(
|
||||
TypeMapItem.ENCODED_ARRAY_ITEM,
|
||||
{
|
||||
TypeMapItem.PROTO_ID_ITEM,
|
||||
TypeMapItem.STRING_ID_ITEM,
|
||||
TypeMapItem.TYPE_ID_ITEM,
|
||||
TypeMapItem.FIELD_ID_ITEM,
|
||||
TypeMapItem.METHOD_ID_ITEM,
|
||||
},
|
||||
),
|
||||
(
|
||||
TypeMapItem.ANNOTATIONS_DIRECTORY_ITEM,
|
||||
{
|
||||
TypeMapItem.FIELD_ID_ITEM,
|
||||
TypeMapItem.METHOD_ID_ITEM,
|
||||
TypeMapItem.ANNOTATION_SET_ITEM,
|
||||
},
|
||||
),
|
||||
(TypeMapItem.HIDDENAPI_CLASS_DATA_ITEM, set()),
|
||||
]
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def determine_load_order():
|
||||
@ -129,6 +203,7 @@ class TypeMapItem(IntEnum):
|
||||
unloaded.discard(type_name)
|
||||
return ordered
|
||||
|
||||
|
||||
# https://source.android.com/devices/tech/dalvik/dex-format#access-flags
|
||||
ACCESS_FLAGS = {
|
||||
0x1: 'public',
|
||||
@ -162,4 +237,3 @@ TYPE_DESCRIPTOR = {
|
||||
'F': 'float',
|
||||
'D': 'double',
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
from mutf8 import decode_modified_utf8, encode_modified_utf8
|
||||
|
||||
decode = decode_modified_utf8
|
||||
encode = encode_modified_utf8
|
||||
encode = encode_modified_utf8
|
||||
|
@ -18,23 +18,25 @@ if _public_res is None:
|
||||
_public_res[_type] = {}
|
||||
_public_res[_type][_name] = _id
|
||||
else:
|
||||
raise Exception("need to copy the sdk/platforms/android-?/data/res/values/public.xml here")
|
||||
raise Exception(
|
||||
"need to copy the sdk/platforms/android-?/data/res/values/public.xml here"
|
||||
)
|
||||
|
||||
SYSTEM_RESOURCES = {
|
||||
"attributes": {
|
||||
"forward": {k: v for k, v in _public_res['attr'].items()},
|
||||
"inverse": {v: k for k, v in _public_res['attr'].items()}
|
||||
"inverse": {v: k for k, v in _public_res['attr'].items()},
|
||||
},
|
||||
"styles": {
|
||||
"forward": {k: v for k, v in _public_res['style'].items()},
|
||||
"inverse": {v: k for k, v in _public_res['style'].items()}
|
||||
}
|
||||
"inverse": {v: k for k, v in _public_res['style'].items()},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import json
|
||||
|
||||
_resources = None
|
||||
if _resources is None:
|
||||
root = os.path.dirname(os.path.realpath(__file__))
|
||||
@ -47,10 +49,15 @@ if __name__ == '__main__':
|
||||
# TODO raise error instead?
|
||||
_resources = {}
|
||||
for _type in set([] + list(_public_res.keys()) + list(_resources.keys())):
|
||||
for k in set([] + list(_public_res.get(_type, {}).keys())
|
||||
+ list(_resources.get(_type, {}).keys())):
|
||||
a,b = _public_res.get(_type, {}).get(k), \
|
||||
_resources.get(_type, {}).get(k),
|
||||
for k in set(
|
||||
[]
|
||||
+ list(_public_res.get(_type, {}).keys())
|
||||
+ list(_resources.get(_type, {}).keys())
|
||||
):
|
||||
a, b = (
|
||||
_public_res.get(_type, {}).get(k),
|
||||
_resources.get(_type, {}).get(k),
|
||||
)
|
||||
if a != b:
|
||||
print(k, a,b)
|
||||
print(None)
|
||||
print(k, a, b)
|
||||
print(None)
|
||||
|
@ -17,13 +17,13 @@
|
||||
|
||||
from collections import defaultdict
|
||||
|
||||
from androguard.decompiler.opcode_ins import INSTRUCTION_SET
|
||||
from loguru import logger
|
||||
|
||||
from androguard.decompiler.instruction import MoveExceptionExpression
|
||||
from androguard.decompiler.node import Node
|
||||
from androguard.decompiler.opcode_ins import INSTRUCTION_SET
|
||||
from androguard.decompiler.util import get_type
|
||||
|
||||
from loguru import logger
|
||||
|
||||
|
||||
class BasicBlock(Node):
|
||||
def __init__(self, name: str, block_ins: list) -> None:
|
||||
@ -191,8 +191,9 @@ class Condition:
|
||||
return loc_ins
|
||||
|
||||
def visit(self, visitor):
|
||||
return visitor.visit_short_circuit_condition(self.isnot, self.isand,
|
||||
self.cond1, self.cond2)
|
||||
return visitor.visit_short_circuit_condition(
|
||||
self.isnot, self.isand, self.cond1, self.cond2
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
if self.isnot:
|
||||
@ -326,19 +327,19 @@ def build_node_from_block(block, vmap, gen_ret, exception_type=None):
|
||||
fillarray = block.get_special_ins(idx)
|
||||
lins.append(_ins(ins, vmap, fillarray))
|
||||
# invoke-kind[/range]
|
||||
elif 0x6e <= opcode <= 0x72 or 0x74 <= opcode <= 0x78:
|
||||
elif 0x6E <= opcode <= 0x72 or 0x74 <= opcode <= 0x78:
|
||||
lins.append(_ins(ins, vmap, gen_ret))
|
||||
# filled-new-array[/range]
|
||||
elif 0x24 <= opcode <= 0x25:
|
||||
lins.append(_ins(ins, vmap, gen_ret.new()))
|
||||
# move-result*
|
||||
elif 0xa <= opcode <= 0xc:
|
||||
elif 0xA <= opcode <= 0xC:
|
||||
lins.append(_ins(ins, vmap, gen_ret.last()))
|
||||
# move-exception
|
||||
elif opcode == 0xd:
|
||||
elif opcode == 0xD:
|
||||
lins.append(_ins(ins, vmap, exception_type))
|
||||
# monitor-{enter,exit}
|
||||
elif 0x1d <= opcode <= 0x1e:
|
||||
elif 0x1D <= opcode <= 0x1E:
|
||||
idx += ins.get_length()
|
||||
continue
|
||||
else:
|
||||
@ -346,15 +347,15 @@ def build_node_from_block(block, vmap, gen_ret, exception_type=None):
|
||||
idx += ins.get_length()
|
||||
name = block.get_name()
|
||||
# return*
|
||||
if 0xe <= opcode <= 0x11:
|
||||
if 0xE <= opcode <= 0x11:
|
||||
node = ReturnBlock(name, lins)
|
||||
# {packed,sparse}-switch
|
||||
elif 0x2b <= opcode <= 0x2c:
|
||||
elif 0x2B <= opcode <= 0x2C:
|
||||
idx -= ins.get_length()
|
||||
values = block.get_special_ins(idx)
|
||||
node = SwitchBlock(name, values, lins)
|
||||
# if-test[z]
|
||||
elif 0x32 <= opcode <= 0x3d:
|
||||
elif 0x32 <= opcode <= 0x3D:
|
||||
node = CondBlock(name, lins)
|
||||
node.off_last_ins = ins.get_ref_off()
|
||||
# throw
|
||||
@ -362,7 +363,7 @@ def build_node_from_block(block, vmap, gen_ret, exception_type=None):
|
||||
node = ThrowBlock(name, lins)
|
||||
else:
|
||||
# goto*
|
||||
if 0x28 <= opcode <= 0x2a:
|
||||
if 0x28 <= opcode <= 0x2A:
|
||||
lins.pop()
|
||||
node = StatementBlock(name, lins)
|
||||
return node
|
||||
|
@ -16,14 +16,20 @@
|
||||
# limitations under the License.
|
||||
|
||||
from collections import defaultdict
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from androguard.decompiler.basic_blocks import (
|
||||
CatchBlock, Condition, LoopBlock, ShortCircuitBlock, TryBlock)
|
||||
CatchBlock,
|
||||
Condition,
|
||||
LoopBlock,
|
||||
ShortCircuitBlock,
|
||||
TryBlock,
|
||||
)
|
||||
from androguard.decompiler.graph import Graph
|
||||
from androguard.decompiler.node import Interval
|
||||
from androguard.decompiler.util import common_dom
|
||||
|
||||
from loguru import logger
|
||||
|
||||
|
||||
def intervals(graph):
|
||||
"""
|
||||
@ -53,7 +59,8 @@ def intervals(graph):
|
||||
change = False
|
||||
for node in graph.rpo[1:]:
|
||||
if all(
|
||||
p in interv_heads[head] for p in graph.all_preds(node)):
|
||||
p in interv_heads[head] for p in graph.all_preds(node)
|
||||
):
|
||||
change |= interv_heads[head].add_node(node)
|
||||
|
||||
# At this stage, a node which is not in the interval, but has one
|
||||
@ -62,9 +69,10 @@ def intervals(graph):
|
||||
for node in graph:
|
||||
if node not in interv_heads[head] and node not in heads:
|
||||
if any(
|
||||
p in interv_heads[head] for p in graph.all_preds(node)):
|
||||
p in interv_heads[head] for p in graph.all_preds(node)
|
||||
):
|
||||
edges[interv_heads[head]].append(node)
|
||||
assert (node not in heads)
|
||||
assert node not in heads
|
||||
heads.append(node)
|
||||
|
||||
interval_graph.add_node(interv_heads[head])
|
||||
@ -163,12 +171,13 @@ def loop_follow(start, end, nodes_in_loop):
|
||||
num_next = float('inf')
|
||||
for node in nodes_in_loop:
|
||||
if node.type.is_cond:
|
||||
if (node.true.num < num_next and
|
||||
node.true not in nodes_in_loop):
|
||||
if node.true.num < num_next and node.true not in nodes_in_loop:
|
||||
follow = node.true
|
||||
num_next = follow.num
|
||||
elif (node.false.num < num_next and
|
||||
node.false not in nodes_in_loop):
|
||||
elif (
|
||||
node.false.num < num_next
|
||||
and node.false not in nodes_in_loop
|
||||
):
|
||||
follow = node.false
|
||||
num_next = follow.num
|
||||
start.follow['loop'] = follow
|
||||
@ -423,7 +432,9 @@ def identify_structures(graph, idoms):
|
||||
loop_follow(node, node.latch, node.loop_nodes)
|
||||
|
||||
for node in if_unresolved:
|
||||
follows = [n for n in (node.follow['loop'], node.follow['switch']) if n]
|
||||
follows = [
|
||||
n for n in (node.follow['loop'], node.follow['switch']) if n
|
||||
]
|
||||
if len(follows) >= 1:
|
||||
follow = min(follows, key=lambda x: x.num)
|
||||
node.follow['if'] = follow
|
||||
|
@ -16,11 +16,11 @@
|
||||
"""This file is a simplified version of writer.py that outputs an AST instead of source code."""
|
||||
import struct
|
||||
|
||||
from androguard.decompiler import basic_blocks, instruction, opcode_ins
|
||||
from androguard.core.dex.dex_types import TYPE_DESCRIPTOR
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from androguard.core.dex.dex_types import TYPE_DESCRIPTOR
|
||||
from androguard.decompiler import basic_blocks, instruction, opcode_ins
|
||||
|
||||
|
||||
class JSONWriter:
|
||||
def __init__(self, graph, method):
|
||||
@ -73,8 +73,8 @@ class JSONWriter:
|
||||
|
||||
# DAD doesn't create any params for abstract methods
|
||||
if len(params) != len(m.params_type):
|
||||
assert ('abstract' in flags or 'native' in flags)
|
||||
assert (not params)
|
||||
assert 'abstract' in flags or 'native' in flags
|
||||
assert not params
|
||||
params = list(range(len(m.params_type)))
|
||||
|
||||
paramdecls = []
|
||||
@ -113,14 +113,18 @@ class JSONWriter:
|
||||
elif isinstance(node, basic_blocks.LoopBlock):
|
||||
return self.get_cond(node.cond)
|
||||
else:
|
||||
assert (type(node) == basic_blocks.CondBlock)
|
||||
assert (len(node.ins) == 1)
|
||||
assert type(node) == basic_blocks.CondBlock
|
||||
assert len(node.ins) == 1
|
||||
return self.visit_expr(node.ins[-1])
|
||||
|
||||
def visit_node(self, node):
|
||||
if node in (self.if_follow[-1], self.switch_follow[-1],
|
||||
self.loop_follow[-1], self.latch_node[-1],
|
||||
self.try_follow[-1]):
|
||||
if node in (
|
||||
self.if_follow[-1],
|
||||
self.switch_follow[-1],
|
||||
self.loop_follow[-1],
|
||||
self.latch_node[-1],
|
||||
self.try_follow[-1],
|
||||
):
|
||||
return
|
||||
if not node.type.is_return and node in self.visited_nodes:
|
||||
return
|
||||
@ -166,7 +170,7 @@ class JSONWriter:
|
||||
else:
|
||||
self.visit_node(loop.latch)
|
||||
|
||||
assert (cond_expr is not None and isDo is not None)
|
||||
assert cond_expr is not None and isDo is not None
|
||||
self.add(self.loop_stmt(isDo, cond_expr, body))
|
||||
if follow is not None:
|
||||
self.visit_node(follow)
|
||||
@ -197,8 +201,10 @@ class JSONWriter:
|
||||
|
||||
self.add(self.if_stmt(cond_expr, scopes))
|
||||
elif follow is not None:
|
||||
if cond.true in (follow, self.next_case) or \
|
||||
cond.num > cond.true.num:
|
||||
if (
|
||||
cond.true in (follow, self.next_case)
|
||||
or cond.num > cond.true.num
|
||||
):
|
||||
# or cond.true.num > cond.false.num:
|
||||
cond.neg()
|
||||
cond.true, cond.false = cond.false, cond.true
|
||||
@ -294,7 +300,7 @@ class JSONWriter:
|
||||
for catch_node in try_node.catch:
|
||||
if catch_node.exception_ins:
|
||||
ins = catch_node.exception_ins
|
||||
assert (isinstance(ins, instruction.MoveExceptionExpression))
|
||||
assert isinstance(ins, instruction.MoveExceptionExpression)
|
||||
var = ins.var_map[ins.ref]
|
||||
var.declared = True
|
||||
|
||||
@ -303,7 +309,9 @@ class JSONWriter:
|
||||
else:
|
||||
ctype = catch_node.catch_type
|
||||
name = '_'
|
||||
catch_decl = self.var_decl(self.parse_descriptor(ctype), self.local(name))
|
||||
catch_decl = self.var_decl(
|
||||
self.parse_descriptor(ctype), self.local(name)
|
||||
)
|
||||
|
||||
with self as body:
|
||||
self.visit_node(catch_node.catch_start)
|
||||
@ -323,7 +331,9 @@ class JSONWriter:
|
||||
|
||||
def _visit_ins(self, op, isCtor=False):
|
||||
if isinstance(op, instruction.ReturnInstruction):
|
||||
expr = None if op.arg is None else self.visit_expr(op.var_map[op.arg])
|
||||
expr = (
|
||||
None if op.arg is None else self.visit_expr(op.var_map[op.arg])
|
||||
)
|
||||
return self.return_stmt(expr)
|
||||
elif isinstance(op, instruction.ThrowExpression):
|
||||
return self.throw_stmt(self.visit_expr(op.var_map[op.ref]))
|
||||
@ -331,11 +341,20 @@ class JSONWriter:
|
||||
return None
|
||||
|
||||
# Local var decl statements
|
||||
if isinstance(op, (instruction.AssignExpression, instruction.MoveExpression,
|
||||
instruction.MoveResultExpression)):
|
||||
if isinstance(
|
||||
op,
|
||||
(
|
||||
instruction.AssignExpression,
|
||||
instruction.MoveExpression,
|
||||
instruction.MoveResultExpression,
|
||||
),
|
||||
):
|
||||
lhs = op.var_map.get(op.lhs)
|
||||
rhs = op.rhs if isinstance(
|
||||
op, instruction.AssignExpression) else op.var_map.get(op.rhs)
|
||||
rhs = (
|
||||
op.rhs
|
||||
if isinstance(op, instruction.AssignExpression)
|
||||
else op.var_map.get(op.rhs)
|
||||
)
|
||||
if isinstance(lhs, instruction.Variable) and not lhs.declared:
|
||||
lhs.declared = True
|
||||
expr = self.visit_expr(rhs)
|
||||
@ -344,9 +363,13 @@ class JSONWriter:
|
||||
# skip this() at top of constructors
|
||||
if isCtor and isinstance(op, instruction.AssignExpression):
|
||||
op2 = op.rhs
|
||||
if op.lhs is None and isinstance(op2, instruction.InvokeInstruction):
|
||||
if op.lhs is None and isinstance(
|
||||
op2, instruction.InvokeInstruction
|
||||
):
|
||||
if op2.name == '<init>' and len(op2.args) == 0:
|
||||
if isinstance(op2.var_map[op2.base], instruction.ThisParam):
|
||||
if isinstance(
|
||||
op2.var_map[op2.base], instruction.ThisParam
|
||||
):
|
||||
return None
|
||||
|
||||
# MoveExpression is skipped when lhs = rhs
|
||||
@ -357,14 +380,22 @@ class JSONWriter:
|
||||
return self.expression_stmt(self.visit_expr(op))
|
||||
|
||||
def write_inplace_if_possible(self, lhs, rhs):
|
||||
if isinstance(rhs, instruction.BinaryExpression) and lhs == rhs.var_map[rhs.arg1]:
|
||||
if (
|
||||
isinstance(rhs, instruction.BinaryExpression)
|
||||
and lhs == rhs.var_map[rhs.arg1]
|
||||
):
|
||||
exp_rhs = rhs.var_map[rhs.arg2]
|
||||
# post increment/decrement
|
||||
if rhs.op in '+-' and isinstance(exp_rhs,
|
||||
instruction.Constant) and exp_rhs.get_int_value() == 1:
|
||||
if (
|
||||
rhs.op in '+-'
|
||||
and isinstance(exp_rhs, instruction.Constant)
|
||||
and exp_rhs.get_int_value() == 1
|
||||
):
|
||||
return self.unary_postfix(self.visit_expr(lhs), rhs.op * 2)
|
||||
# compound assignment
|
||||
return self.assignment(self.visit_expr(lhs), self.visit_expr(exp_rhs), op=rhs.op)
|
||||
return self.assignment(
|
||||
self.visit_expr(lhs), self.visit_expr(exp_rhs), op=rhs.op
|
||||
)
|
||||
return self.assignment(self.visit_expr(lhs), self.visit_expr(rhs))
|
||||
|
||||
def visit_expr(self, op):
|
||||
@ -379,7 +410,9 @@ class JSONWriter:
|
||||
array_expr = self.visit_expr(op.var_map[op.array])
|
||||
index_expr = self.visit_expr(op.var_map[op.index])
|
||||
rhs = self.visit_expr(op.var_map[op.rhs])
|
||||
return self.assignment(self.array_access(array_expr, index_expr), rhs)
|
||||
return self.assignment(
|
||||
self.array_access(array_expr, index_expr), rhs
|
||||
)
|
||||
|
||||
if isinstance(op, instruction.AssignExpression):
|
||||
lhs = op.var_map.get(op.lhs)
|
||||
@ -390,25 +423,32 @@ class JSONWriter:
|
||||
|
||||
if isinstance(op, instruction.BaseClass):
|
||||
if op.clsdesc is None:
|
||||
assert (op.cls == "super")
|
||||
assert op.cls == "super"
|
||||
return self.local(op.cls)
|
||||
return self.parse_descriptor(op.clsdesc)
|
||||
if isinstance(op, instruction.BinaryExpression):
|
||||
lhs = op.var_map.get(op.arg1)
|
||||
rhs = op.var_map.get(op.arg2)
|
||||
expr = self.binary_infix(op.op, self.visit_expr(lhs), self.visit_expr(rhs))
|
||||
expr = self.binary_infix(
|
||||
op.op, self.visit_expr(lhs), self.visit_expr(rhs)
|
||||
)
|
||||
if not isinstance(op, instruction.BinaryCompExpression):
|
||||
expr = self.parenthesis(expr)
|
||||
return expr
|
||||
|
||||
if isinstance(op, instruction.CheckCastExpression):
|
||||
lhs = op.var_map.get(op.arg)
|
||||
return self.parenthesis(self.cast(self.parse_descriptor(op.clsdesc),
|
||||
self.visit_expr(lhs)))
|
||||
return self.parenthesis(
|
||||
self.cast(
|
||||
self.parse_descriptor(op.clsdesc), self.visit_expr(lhs)
|
||||
)
|
||||
)
|
||||
if isinstance(op, instruction.ConditionalExpression):
|
||||
lhs = op.var_map.get(op.arg1)
|
||||
rhs = op.var_map.get(op.arg2)
|
||||
return self.binary_infix(op.op, self.visit_expr(lhs), self.visit_expr(rhs))
|
||||
return self.binary_infix(
|
||||
op.op, self.visit_expr(lhs), self.visit_expr(rhs)
|
||||
)
|
||||
if isinstance(op, instruction.ConditionalZExpression):
|
||||
arg = op.var_map[op.arg]
|
||||
if isinstance(arg, instruction.BinaryCompExpression):
|
||||
@ -457,7 +497,9 @@ class JSONWriter:
|
||||
return self.field_access(triple, expr)
|
||||
if isinstance(op, instruction.InstanceInstruction):
|
||||
triple = op.clsdesc[1:-1], op.name, op.atype
|
||||
lhs = self.field_access(triple, self.visit_expr(op.var_map[op.lhs]))
|
||||
lhs = self.field_access(
|
||||
triple, self.visit_expr(op.var_map[op.lhs])
|
||||
)
|
||||
rhs = self.visit_expr(op.var_map[op.rhs])
|
||||
return self.assignment(lhs, rhs)
|
||||
|
||||
@ -467,20 +509,34 @@ class JSONWriter:
|
||||
params = list(map(self.visit_expr, params))
|
||||
if op.name == '<init>':
|
||||
if isinstance(base, instruction.ThisParam):
|
||||
keyword = 'this' if base.type[1:-1] == op.triple[0] else 'super'
|
||||
return self.method_invocation(op.triple, keyword, None, params)
|
||||
keyword = (
|
||||
'this' if base.type[1:-1] == op.triple[0] else 'super'
|
||||
)
|
||||
return self.method_invocation(
|
||||
op.triple, keyword, None, params
|
||||
)
|
||||
elif isinstance(base, instruction.NewInstance):
|
||||
return ['ClassInstanceCreation', op.triple, params,
|
||||
self.parse_descriptor(base.type)]
|
||||
return [
|
||||
'ClassInstanceCreation',
|
||||
op.triple,
|
||||
params,
|
||||
self.parse_descriptor(base.type),
|
||||
]
|
||||
else:
|
||||
assert (isinstance(base, instruction.Variable))
|
||||
assert isinstance(base, instruction.Variable)
|
||||
# fallthrough to create dummy <init> call
|
||||
return self.method_invocation(op.triple, op.name, self.visit_expr(base), params)
|
||||
return self.method_invocation(
|
||||
op.triple, op.name, self.visit_expr(base), params
|
||||
)
|
||||
# for unmatched monitor instructions, just create dummy expressions
|
||||
if isinstance(op, instruction.MonitorEnterExpression):
|
||||
return self.dummy("monitor enter(", self.visit_expr(op.var_map[op.ref]), ")")
|
||||
return self.dummy(
|
||||
"monitor enter(", self.visit_expr(op.var_map[op.ref]), ")"
|
||||
)
|
||||
if isinstance(op, instruction.MonitorExitExpression):
|
||||
return self.dummy("monitor exit(", self.visit_expr(op.var_map[op.ref]), ")")
|
||||
return self.dummy(
|
||||
"monitor exit(", self.visit_expr(op.var_map[op.ref]), ")"
|
||||
)
|
||||
if isinstance(op, instruction.MoveExpression):
|
||||
lhs = op.var_map.get(op.lhs)
|
||||
rhs = op.var_map.get(op.rhs)
|
||||
@ -513,7 +569,9 @@ class JSONWriter:
|
||||
if isinstance(op, instruction.UnaryExpression):
|
||||
lhs = op.var_map.get(op.arg)
|
||||
if isinstance(op, instruction.CastExpression):
|
||||
expr = self.cast(self.parse_descriptor(op.clsdesc), self.visit_expr(lhs))
|
||||
expr = self.cast(
|
||||
self.parse_descriptor(op.clsdesc), self.visit_expr(lhs)
|
||||
)
|
||||
else:
|
||||
expr = self.unary_prefix(op.op, self.visit_expr(lhs))
|
||||
return self.parenthesis(expr)
|
||||
@ -528,7 +586,7 @@ class JSONWriter:
|
||||
elem_size = value.element_width
|
||||
if elem_size == 4:
|
||||
for i in range(0, value.size * 4, 4):
|
||||
tab.append(struct.unpack('<i', data[i:i + 4])[0])
|
||||
tab.append(struct.unpack('<i', data[i : i + 4])[0])
|
||||
else: # FIXME: other cases
|
||||
for i in range(value.size):
|
||||
tab.append(data[i])
|
||||
@ -569,7 +627,9 @@ class JSONWriter:
|
||||
|
||||
@staticmethod
|
||||
def literal_class(desc):
|
||||
return JSONWriter.literal(JSONWriter.parse_descriptor(desc), ('java/lang/Class', 0))
|
||||
return JSONWriter.literal(
|
||||
JSONWriter.parse_descriptor(desc), ('java/lang/Class', 0)
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def literal_string(s):
|
||||
@ -592,7 +652,7 @@ class JSONWriter:
|
||||
@staticmethod
|
||||
def _append(sb, stmt):
|
||||
# Add a statement to the end of a statement block
|
||||
assert (sb[0] == 'BlockStatement')
|
||||
assert sb[0] == 'BlockStatement'
|
||||
if stmt is not None:
|
||||
sb[2].append(stmt)
|
||||
|
||||
|
@ -16,12 +16,13 @@
|
||||
# limitations under the License.
|
||||
|
||||
from collections import defaultdict
|
||||
from androguard.decompiler.instruction import (Variable, ThisParam, Param)
|
||||
from androguard.decompiler.util import build_path, common_dom
|
||||
from androguard.decompiler.node import Node
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from androguard.decompiler.instruction import Param, ThisParam, Variable
|
||||
from androguard.decompiler.node import Node
|
||||
from androguard.decompiler.util import build_path, common_dom
|
||||
|
||||
|
||||
class BasicReachDef:
|
||||
def __init__(self, graph, params):
|
||||
@ -150,8 +151,9 @@ def clear_path_node(graph, reg, loc1, loc2):
|
||||
logger.debug(' treat loc: %d, ins: %s', loc, ins)
|
||||
if ins is None:
|
||||
continue
|
||||
logger.debug(' LHS: %s, side_effect: %s', ins.get_lhs(),
|
||||
ins.has_side_effect())
|
||||
logger.debug(
|
||||
' LHS: %s, side_effect: %s', ins.get_lhs(), ins.has_side_effect()
|
||||
)
|
||||
if ins.get_lhs() == reg or ins.has_side_effect():
|
||||
return False
|
||||
return True
|
||||
@ -218,8 +220,9 @@ def register_propagation(graph, du, ud):
|
||||
continue
|
||||
orig_ins = graph.get_ins_from_loc(loc)
|
||||
logger.debug(' -> %s', orig_ins)
|
||||
logger.debug(' -> DU(%s, %s) = %s', var, loc,
|
||||
du[var, loc])
|
||||
logger.debug(
|
||||
' -> DU(%s, %s) = %s', var, loc, du[var, loc]
|
||||
)
|
||||
|
||||
# We defined some instructions as not propagable.
|
||||
# Actually this is the case only for array creation
|
||||
@ -232,8 +235,10 @@ def register_propagation(graph, du, ud):
|
||||
# We only try to propagate constants and definition
|
||||
# points which are used at only one location.
|
||||
if len(du[var, loc]) > 1:
|
||||
logger.debug(' => variable has multiple uses'
|
||||
' and is not const => skip')
|
||||
logger.debug(
|
||||
' => variable has multiple uses'
|
||||
' and is not const => skip'
|
||||
)
|
||||
continue
|
||||
|
||||
# We check that the propagation is safe for all the
|
||||
@ -244,8 +249,11 @@ def register_propagation(graph, du, ud):
|
||||
# be redifined along this path.
|
||||
safe = True
|
||||
orig_ins_used_vars = orig_ins.get_used_vars()
|
||||
logger.debug(' variables used by the original '
|
||||
'instruction: %s', orig_ins_used_vars)
|
||||
logger.debug(
|
||||
' variables used by the original '
|
||||
'instruction: %s',
|
||||
orig_ins_used_vars,
|
||||
)
|
||||
for var2 in orig_ins_used_vars:
|
||||
# loc is the location of the defined variable
|
||||
# i is the location of the current instruction
|
||||
@ -262,8 +270,11 @@ def register_propagation(graph, du, ud):
|
||||
# along the path
|
||||
if orig_ins.has_side_effect():
|
||||
if not clear_path(graph, None, loc, i):
|
||||
logger.debug(' %s has side effect and the '
|
||||
'path is not clear !', orig_ins)
|
||||
logger.debug(
|
||||
' %s has side effect and the '
|
||||
'path is not clear !',
|
||||
orig_ins,
|
||||
)
|
||||
continue
|
||||
|
||||
logger.debug(' => Modification of the instruction!')
|
||||
@ -289,8 +300,9 @@ def register_propagation(graph, du, ud):
|
||||
if old_ud is None:
|
||||
continue
|
||||
ud[var2, i].extend(old_ud)
|
||||
logger.debug('\t - ud(%s, %s) = %s', var2, i,
|
||||
ud[var2, i])
|
||||
logger.debug(
|
||||
'\t - ud(%s, %s) = %s', var2, i, ud[var2, i]
|
||||
)
|
||||
ud.pop((var2, loc))
|
||||
|
||||
for def_loc in old_ud:
|
||||
@ -444,7 +456,8 @@ def build_def_use(graph, lparams):
|
||||
UD[var, i].append(prior_def)
|
||||
else:
|
||||
intersect = analysis.def_to_loc[var].intersection(
|
||||
analysis.R[node])
|
||||
analysis.R[node]
|
||||
)
|
||||
UD[var, i].extend(intersect)
|
||||
DU = defaultdict(list)
|
||||
for var_loc, defs_loc in UD.items():
|
||||
@ -460,8 +473,9 @@ def place_declarations(graph, dvars, du, ud):
|
||||
for node in graph.post_order():
|
||||
for loc, ins in node.get_loc_with_ins():
|
||||
for var in ins.get_used_vars():
|
||||
if (not isinstance(dvars[var], Variable) or
|
||||
isinstance(dvars[var], Param)):
|
||||
if not isinstance(dvars[var], Variable) or isinstance(
|
||||
dvars[var], Param
|
||||
):
|
||||
continue
|
||||
var_defs_locs = ud[var, loc]
|
||||
def_nodes = set()
|
||||
@ -476,8 +490,11 @@ def place_declarations(graph, dvars, du, ud):
|
||||
common_dominator = def_nodes.pop()
|
||||
for def_node in def_nodes:
|
||||
common_dominator = common_dom(
|
||||
idom, common_dominator, def_node)
|
||||
if any(var in range(*common_dominator.ins_range)
|
||||
for var in ud[var, loc]):
|
||||
idom, common_dominator, def_node
|
||||
)
|
||||
if any(
|
||||
var in range(*common_dominator.ins_range)
|
||||
for var in ud[var, loc]
|
||||
):
|
||||
continue
|
||||
common_dominator.add_variable_declaration(dvars[var])
|
||||
|
@ -19,7 +19,9 @@
|
||||
# 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
|
||||
@ -32,27 +34,34 @@ from loguru import logger
|
||||
|
||||
import androguard.core.androconf as androconf
|
||||
import androguard.decompiler.util as util
|
||||
from androguard.core.analysis import analysis
|
||||
from androguard.core import apk, dex
|
||||
from androguard.core.analysis import analysis
|
||||
from androguard.decompiler.control_flow import identify_structures
|
||||
from androguard.decompiler.dast import JSONWriter
|
||||
from androguard.decompiler.dataflow import (
|
||||
build_def_use,
|
||||
place_declarations,
|
||||
dead_code_elimination,
|
||||
place_declarations,
|
||||
register_propagation,
|
||||
split_variables
|
||||
split_variables,
|
||||
)
|
||||
from androguard.decompiler.graph import construct, simplify, split_if_nodes
|
||||
from androguard.decompiler.instruction import Param, ThisParam
|
||||
from androguard.decompiler.writer import Writer
|
||||
from androguard.util import readFile
|
||||
|
||||
logger.add(sys.stderr, format="{time} {level} {message}", filter="dad", level="INFO")
|
||||
logger.add(
|
||||
sys.stderr, format="{time} {level} {message}", filter="dad", level="INFO"
|
||||
)
|
||||
|
||||
|
||||
# No seperate DvField class currently
|
||||
def get_field_ast(field: EncodedField) -> dict:
|
||||
triple = field.get_class_name()[1:-1], field.get_name(), field.get_descriptor()
|
||||
triple = (
|
||||
field.get_class_name()[1:-1],
|
||||
field.get_name(),
|
||||
field.get_descriptor(),
|
||||
)
|
||||
|
||||
expr = None
|
||||
if field.init_value:
|
||||
@ -63,7 +72,9 @@ def get_field_ast(field: EncodedField) -> dict:
|
||||
if field.get_descriptor() == 'Ljava/lang/String;':
|
||||
expr = JSONWriter.literal_string(val)
|
||||
elif field.proto == 'B':
|
||||
expr = JSONWriter.literal_hex_int(struct.unpack('<b', struct.pack("B", val))[0])
|
||||
expr = JSONWriter.literal_hex_int(
|
||||
struct.unpack('<b', struct.pack("B", val))[0]
|
||||
)
|
||||
|
||||
return {
|
||||
'triple': triple,
|
||||
@ -80,6 +91,7 @@ class DvMethod:
|
||||
|
||||
:param androguard.core.analysis.analysis.MethodAnalysis methanalysis:
|
||||
"""
|
||||
|
||||
def __init__(self, methanalysis: MethodAnalysis) -> None:
|
||||
method = methanalysis.get_method()
|
||||
self.method = method
|
||||
@ -119,10 +131,16 @@ class DvMethod:
|
||||
|
||||
if not __debug__:
|
||||
from androguard.core import bytecode
|
||||
# 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:bool=False) -> None:
|
||||
# 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: bool = False) -> None:
|
||||
"""
|
||||
Processes the method and decompile the code.
|
||||
|
||||
@ -182,13 +200,17 @@ class DvMethod:
|
||||
|
||||
if not __debug__:
|
||||
# TODO: use tempfile to create a correct tempfile (cross platform compatible)
|
||||
util.create_png(self.cls_name, self.name, graph, '/tmp/dad/pre-structured')
|
||||
util.create_png(
|
||||
self.cls_name, self.name, graph, '/tmp/dad/pre-structured'
|
||||
)
|
||||
|
||||
identify_structures(graph, graph.immediate_dominators())
|
||||
|
||||
if not __debug__:
|
||||
# TODO: use tempfile to create a correct tempfile (cross platform compatible)
|
||||
util.create_png(self.cls_name, self.name, graph, '/tmp/dad/structured')
|
||||
util.create_png(
|
||||
self.cls_name, self.name, graph, '/tmp/dad/structured'
|
||||
)
|
||||
|
||||
if doAST:
|
||||
self.ast = JSONWriter(graph, self).get_ast()
|
||||
@ -243,7 +265,10 @@ class DvClass:
|
||||
:param androguard.core.dex.ClassDefItem dvclass: the class item
|
||||
:param androguard.core.analysis.analysis.Analysis vma: an Analysis object
|
||||
"""
|
||||
def __init__(self, dvclass: dex.ClassDefItem, vma: analysis.Analysis) -> None:
|
||||
|
||||
def __init__(
|
||||
self, dvclass: dex.ClassDefItem, vma: analysis.Analysis
|
||||
) -> None:
|
||||
name = dvclass.get_name()
|
||||
if name.find('/') > 0:
|
||||
pckg, name = name.rsplit('/', 1)
|
||||
@ -277,13 +302,15 @@ class DvClass:
|
||||
logger.debug('Class : %s', self.name)
|
||||
logger.debug('Methods added :')
|
||||
for meth in self.methods:
|
||||
logger.debug('%s (%s, %s)', meth.get_method_idx(), self.name, meth.name)
|
||||
logger.debug(
|
||||
'%s (%s, %s)', meth.get_method_idx(), self.name, meth.name
|
||||
)
|
||||
logger.debug('')
|
||||
|
||||
def get_methods(self) -> list[dex.EncodedMethod]:
|
||||
return self.methods
|
||||
|
||||
def process_method(self, num: int, doAST:bool=False) -> None:
|
||||
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))
|
||||
@ -291,13 +318,15 @@ class DvClass:
|
||||
else:
|
||||
method.process(doAST=doAST)
|
||||
|
||||
def process(self, doAST:bool=False) -> None:
|
||||
def process(self, doAST: bool = False) -> None:
|
||||
for i in range(len(self.methods)):
|
||||
try:
|
||||
self.process_method(i, doAST=doAST)
|
||||
except Exception as e:
|
||||
# FIXME: too broad exception?
|
||||
logger.warning('Error decompiling method %s: %s', self.methods[i], e)
|
||||
logger.warning(
|
||||
'Error decompiling method %s: %s', self.methods[i], e
|
||||
)
|
||||
|
||||
def get_ast(self) -> dict:
|
||||
fields = [get_field_ast(f) for f in self.fields]
|
||||
@ -312,7 +341,9 @@ class DvClass:
|
||||
'super': JSONWriter.parse_descriptor(self.superclass),
|
||||
'flags': self.access,
|
||||
'isInterface': isInterface,
|
||||
'interfaces': list(map(JSONWriter.parse_descriptor, self.interfaces)),
|
||||
'interfaces': list(
|
||||
map(JSONWriter.parse_descriptor, self.interfaces)
|
||||
),
|
||||
'fields': fields,
|
||||
'methods': methods,
|
||||
}
|
||||
@ -329,7 +360,8 @@ class DvClass:
|
||||
|
||||
if len(self.interfaces) > 0:
|
||||
prototype += ' implements %s' % ', '.join(
|
||||
[str(n[1:-1].replace('/', '.')) for n in self.interfaces])
|
||||
[str(n[1:-1].replace('/', '.')) for n in self.interfaces]
|
||||
)
|
||||
|
||||
source.append('%s {\n' % prototype)
|
||||
for field in self.fields:
|
||||
@ -345,7 +377,9 @@ class DvClass:
|
||||
value = init_value.value
|
||||
if f_type == 'String':
|
||||
if value:
|
||||
value = '"%s"' % str(value).encode("unicode-escape").decode("ascii")
|
||||
value = '"%s"' % str(value).encode(
|
||||
"unicode-escape"
|
||||
).decode("ascii")
|
||||
else:
|
||||
# FIXME we can not check if this value here is null or ""
|
||||
# In both cases we end up here...
|
||||
@ -369,11 +403,19 @@ class DvClass:
|
||||
source = []
|
||||
if not self.inner and self.package:
|
||||
source.append(
|
||||
('PACKAGE', [('PACKAGE_START', 'package '), (
|
||||
'NAME_PACKAGE', '%s' % self.package), ('PACKAGE_END', ';\n')
|
||||
]))
|
||||
list_proto = [('PROTOTYPE_ACCESS', '%s class ' % ' '.join(self.access)),
|
||||
('NAME_PROTOTYPE', '%s' % self.name, self.package)]
|
||||
(
|
||||
'PACKAGE',
|
||||
[
|
||||
('PACKAGE_START', 'package '),
|
||||
('NAME_PACKAGE', '%s' % self.package),
|
||||
('PACKAGE_END', ';\n'),
|
||||
],
|
||||
)
|
||||
)
|
||||
list_proto = [
|
||||
('PROTOTYPE_ACCESS', '%s class ' % ' '.join(self.access)),
|
||||
('NAME_PROTOTYPE', '%s' % self.name, self.package),
|
||||
]
|
||||
superclass = self.superclass
|
||||
if superclass is not None and superclass != 'Ljava/lang/Object;':
|
||||
superclass = superclass[1:-1].replace('/', '.')
|
||||
@ -386,15 +428,18 @@ class DvClass:
|
||||
if i != 0:
|
||||
list_proto.append(('COMMA', ', '))
|
||||
list_proto.append(
|
||||
('NAME_INTERFACE', interface[1:-1].replace('/', '.')))
|
||||
('NAME_INTERFACE', interface[1:-1].replace('/', '.'))
|
||||
)
|
||||
list_proto.append(('PROTOTYPE_END', ' {\n'))
|
||||
source.append(("PROTOTYPE", list_proto))
|
||||
|
||||
for field in self.fields:
|
||||
field_access_flags = field.get_access_flags()
|
||||
access = [util.ACCESS_FLAGS_FIELDS[flag]
|
||||
for flag in util.ACCESS_FLAGS_FIELDS
|
||||
if flag & field_access_flags]
|
||||
access = [
|
||||
util.ACCESS_FLAGS_FIELDS[flag]
|
||||
for flag in util.ACCESS_FLAGS_FIELDS
|
||||
if flag & field_access_flags
|
||||
]
|
||||
f_type = util.get_type(field.get_descriptor())
|
||||
name = field.get_name()
|
||||
if access:
|
||||
@ -408,28 +453,47 @@ class DvClass:
|
||||
value = init_value.value
|
||||
if f_type == 'String':
|
||||
if value:
|
||||
value = ' = "%s"' % value.encode("unicode-escape").decode("ascii")
|
||||
value = ' = "%s"' % value.encode(
|
||||
"unicode-escape"
|
||||
).decode("ascii")
|
||||
else:
|
||||
# FIXME we can not check if this value here is null or ""
|
||||
# In both cases we end up here...
|
||||
value = ' = ""'
|
||||
elif field.proto == 'B':
|
||||
# a byte
|
||||
value = ' = %s' % hex(struct.unpack("b", struct.pack("B", value))[0])
|
||||
value = ' = %s' % hex(
|
||||
struct.unpack("b", struct.pack("B", value))[0]
|
||||
)
|
||||
else:
|
||||
value = ' = %s' % str(value)
|
||||
if value:
|
||||
source.append(
|
||||
('FIELD', [('FIELD_ACCESS', access_str), (
|
||||
'FIELD_TYPE', '%s' % f_type), ('SPACE', ' '), (
|
||||
'NAME_FIELD', '%s' % name, f_type, field), ('FIELD_VALUE', value), ('FIELD_END',
|
||||
';\n')]))
|
||||
(
|
||||
'FIELD',
|
||||
[
|
||||
('FIELD_ACCESS', access_str),
|
||||
('FIELD_TYPE', '%s' % f_type),
|
||||
('SPACE', ' '),
|
||||
('NAME_FIELD', '%s' % name, f_type, field),
|
||||
('FIELD_VALUE', value),
|
||||
('FIELD_END', ';\n'),
|
||||
],
|
||||
)
|
||||
)
|
||||
else:
|
||||
source.append(
|
||||
('FIELD', [('FIELD_ACCESS', access_str), (
|
||||
'FIELD_TYPE', '%s' % f_type), ('SPACE', ' '), (
|
||||
'NAME_FIELD', '%s' % name, f_type, field), ('FIELD_END',
|
||||
';\n')]))
|
||||
(
|
||||
'FIELD',
|
||||
[
|
||||
('FIELD_ACCESS', access_str),
|
||||
('FIELD_TYPE', '%s' % f_type),
|
||||
('SPACE', ' '),
|
||||
('NAME_FIELD', '%s' % name, f_type, field),
|
||||
('FIELD_END', ';\n'),
|
||||
],
|
||||
)
|
||||
)
|
||||
|
||||
for method in self.methods:
|
||||
if isinstance(method, DvMethod):
|
||||
@ -455,6 +519,7 @@ class DvMachine:
|
||||
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: str) -> None:
|
||||
"""
|
||||
|
||||
@ -474,7 +539,10 @@ class DvMachine:
|
||||
else:
|
||||
raise ValueError("Format not recognised for filename '%s'" % name)
|
||||
|
||||
self.classes = {dvclass.orig_class.get_name(): dvclass.orig_class for dvclass in self.vma.get_classes()}
|
||||
self.classes = {
|
||||
dvclass.orig_class.get_name(): dvclass.orig_class
|
||||
for dvclass in self.vma.get_classes()
|
||||
}
|
||||
# TODO why not?
|
||||
# util.merge_inner(self.classes)
|
||||
|
||||
@ -556,4 +624,4 @@ class DvMachine:
|
||||
cls = DvClass(cls, self.vma)
|
||||
cls.process(doAST=True)
|
||||
ret[name] = cls.get_ast()
|
||||
return ret
|
||||
return ret
|
||||
|
@ -21,18 +21,18 @@
|
||||
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 loguru import logger
|
||||
from pygments import highlight
|
||||
from pygments.lexers import get_lexer_by_name
|
||||
from pygments.formatters import TerminalFormatter
|
||||
from pygments.lexers import get_lexer_by_name
|
||||
from pygments.token import Token
|
||||
|
||||
from loguru import logger
|
||||
from androguard.decompiler import decompile
|
||||
|
||||
|
||||
class DecompilerDAD:
|
||||
@ -81,7 +81,9 @@ class DecompilerDAD:
|
||||
c.process(doAST=True)
|
||||
return c.get_ast()
|
||||
|
||||
def get_source_class_ext(self, _class: ClassDefItem) -> list[tuple[str, list]]:
|
||||
def get_source_class_ext(
|
||||
self, _class: ClassDefItem
|
||||
) -> list[tuple[str, list]]:
|
||||
c = decompile.DvClass(_class, self.vmx)
|
||||
c.process()
|
||||
|
||||
@ -95,4 +97,4 @@ class DecompilerDAD:
|
||||
lexer = get_lexer_by_name("java", stripall=True)
|
||||
formatter = TerminalFormatter()
|
||||
result = highlight(result, lexer, formatter)
|
||||
print(result)
|
||||
print(result)
|
||||
|
@ -16,12 +16,15 @@
|
||||
# limitations under the License.
|
||||
from collections import defaultdict
|
||||
|
||||
from androguard.decompiler.basic_blocks import (build_node_from_block,
|
||||
StatementBlock, CondBlock)
|
||||
from androguard.decompiler.instruction import Variable
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from androguard.decompiler.basic_blocks import (
|
||||
CondBlock,
|
||||
StatementBlock,
|
||||
build_node_from_block,
|
||||
)
|
||||
from androguard.decompiler.instruction import Variable
|
||||
|
||||
|
||||
# TODO Could use networkx here, as it has plenty of tools already, no need to reengineer the wheel
|
||||
class Graph:
|
||||
@ -31,6 +34,7 @@ class Graph:
|
||||
The CFG defines an entry node :py:attr:`entry`, a single exit node :py:attr:`exit`, a list of nodes
|
||||
:py:attr:`nodes` and a list of edges :py:attr:`edges`.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.entry = None
|
||||
self.exit = None
|
||||
@ -54,8 +58,9 @@ class Graph:
|
||||
return [n for n in self.reverse_edges.get(node, []) if not n.in_catch]
|
||||
|
||||
def all_preds(self, node):
|
||||
return (self.reverse_edges.get(node, []) + self.reverse_catch_edges.get(
|
||||
node, []))
|
||||
return self.reverse_edges.get(node, []) + self.reverse_catch_edges.get(
|
||||
node, []
|
||||
)
|
||||
|
||||
def add_node(self, node):
|
||||
"""
|
||||
@ -152,6 +157,7 @@ class Graph:
|
||||
Yields the :class`~androguard.decompiler.node.Node`s of the graph in post-order i.e we visit all the
|
||||
children of a node before visiting the node itself.
|
||||
"""
|
||||
|
||||
def _visit(n, cnt):
|
||||
visited.add(n)
|
||||
for suc in self.all_sucs(n):
|
||||
@ -174,15 +180,18 @@ class Graph:
|
||||
:param draw_branches:
|
||||
:return:
|
||||
"""
|
||||
from pydot import Dot, Edge
|
||||
import os
|
||||
|
||||
from pydot import Dot, Edge
|
||||
|
||||
g = Dot()
|
||||
g.set_node_defaults(color='lightgray',
|
||||
style='filled',
|
||||
shape='box',
|
||||
fontname='Courier',
|
||||
fontsize='10')
|
||||
g.set_node_defaults(
|
||||
color='lightgray',
|
||||
style='filled',
|
||||
shape='box',
|
||||
fontname='Courier',
|
||||
fontsize='10',
|
||||
)
|
||||
for node in sorted(self.nodes, key=lambda x: x.num):
|
||||
if draw_branches and node.type.is_cond:
|
||||
g.add_edge(Edge(str(node), str(node.true), color='green'))
|
||||
@ -191,10 +200,14 @@ class Graph:
|
||||
for suc in self.sucs(node):
|
||||
g.add_edge(Edge(str(node), str(suc), color='blue'))
|
||||
for except_node in self.catch_edges.get(node, []):
|
||||
g.add_edge(Edge(str(node),
|
||||
str(except_node),
|
||||
color='black',
|
||||
style='dashed'))
|
||||
g.add_edge(
|
||||
Edge(
|
||||
str(node),
|
||||
str(except_node),
|
||||
color='black',
|
||||
style='dashed',
|
||||
)
|
||||
)
|
||||
|
||||
g.write(os.path.join(dname, '%s.png' % name), format='png')
|
||||
|
||||
@ -295,8 +308,9 @@ def simplify(graph):
|
||||
continue
|
||||
suc = sucs[0]
|
||||
if len(node.get_ins()) == 0:
|
||||
if any(pred.type.is_switch
|
||||
for pred in graph.all_preds(node)):
|
||||
if any(
|
||||
pred.type.is_switch for pred in graph.all_preds(node)
|
||||
):
|
||||
continue
|
||||
if node is suc:
|
||||
continue
|
||||
@ -312,9 +326,12 @@ def simplify(graph):
|
||||
if node is graph.entry:
|
||||
graph.entry = suc
|
||||
graph.remove_node(node)
|
||||
elif (suc.type.is_stmt and len(graph.all_preds(suc)) == 1 and
|
||||
not (suc in graph.catch_edges) and not (
|
||||
(node is suc) or (suc is graph.entry))):
|
||||
elif (
|
||||
suc.type.is_stmt
|
||||
and len(graph.all_preds(suc)) == 1
|
||||
and not (suc in graph.catch_edges)
|
||||
and not ((node is suc) or (suc is graph.entry))
|
||||
):
|
||||
ins_to_merge = suc.get_ins()
|
||||
node.add_ins(ins_to_merge)
|
||||
for var in suc.var_to_declare:
|
||||
@ -445,8 +462,9 @@ def make_node(graph, block, block_to_node, vmap, gen_ret):
|
||||
for _type, _, exception_target in block.exception_analysis.exceptions:
|
||||
exception_node = block_to_node.get(exception_target)
|
||||
if exception_node is None:
|
||||
exception_node = build_node_from_block(exception_target, vmap,
|
||||
gen_ret, _type)
|
||||
exception_node = build_node_from_block(
|
||||
exception_target, vmap, gen_ret, _type
|
||||
)
|
||||
exception_node.in_catch = True
|
||||
block_to_node[exception_target] = exception_node
|
||||
node.set_catch_type(_type)
|
||||
@ -461,8 +479,9 @@ def make_node(graph, block, block_to_node, vmap, gen_ret):
|
||||
if node.type.is_switch:
|
||||
node.add_case(child_node)
|
||||
if node.type.is_cond:
|
||||
if_target = ((block.end // 2) -
|
||||
(block.last_length // 2) + node.off_last_ins)
|
||||
if_target = (
|
||||
(block.end // 2) - (block.last_length // 2) + node.off_last_ins
|
||||
)
|
||||
child_addr = child_block.start // 2
|
||||
if if_target == child_addr:
|
||||
node.true = child_node
|
||||
|
@ -340,10 +340,9 @@ class ArrayStoreInstruction(IRForm):
|
||||
|
||||
def visit(self, visitor):
|
||||
v_m = self.var_map
|
||||
return visitor.visit_astore(v_m[self.array],
|
||||
v_m[self.index],
|
||||
v_m[self.rhs],
|
||||
data=self)
|
||||
return visitor.visit_astore(
|
||||
v_m[self.array], v_m[self.index], v_m[self.rhs], data=self
|
||||
)
|
||||
|
||||
def replace_var(self, old, new):
|
||||
if self.rhs == old:
|
||||
@ -379,7 +378,9 @@ class ArrayStoreInstruction(IRForm):
|
||||
|
||||
def __str__(self):
|
||||
v_m = self.var_map
|
||||
return '{}[{}] = {}'.format(v_m[self.array], v_m[self.index], v_m[self.rhs])
|
||||
return '{}[{}] = {}'.format(
|
||||
v_m[self.array], v_m[self.index], v_m[self.rhs]
|
||||
)
|
||||
|
||||
|
||||
class StaticInstruction(IRForm):
|
||||
@ -404,7 +405,8 @@ class StaticInstruction(IRForm):
|
||||
|
||||
def visit(self, visitor):
|
||||
return visitor.visit_put_static(
|
||||
self.cls, self.name, self.var_map[self.rhs])
|
||||
self.cls, self.name, self.var_map[self.rhs]
|
||||
)
|
||||
|
||||
def replace_var(self, old, new):
|
||||
self.rhs = new.v
|
||||
@ -454,10 +456,8 @@ class InstanceInstruction(IRForm):
|
||||
def visit(self, visitor):
|
||||
v_m = self.var_map
|
||||
return visitor.visit_put_instance(
|
||||
v_m[self.lhs],
|
||||
self.name,
|
||||
v_m[self.rhs],
|
||||
data=self.atype)
|
||||
v_m[self.lhs], self.name, v_m[self.rhs], data=self.atype
|
||||
)
|
||||
|
||||
def replace_var(self, old, new):
|
||||
if self.lhs == old:
|
||||
@ -527,7 +527,7 @@ class InvokeInstruction(IRForm):
|
||||
self.var_map[arg.v] = arg
|
||||
|
||||
self.triple = triple
|
||||
assert (triple[1] == name)
|
||||
assert triple[1] == name
|
||||
|
||||
def get_type(self):
|
||||
if self.name == '<init>':
|
||||
@ -593,32 +593,33 @@ class InvokeInstruction(IRForm):
|
||||
def visit(self, visitor):
|
||||
v_m = self.var_map
|
||||
largs = [v_m[arg] for arg in self.args]
|
||||
return visitor.visit_invoke(self.name, v_m[self.base], self.ptype,
|
||||
self.rtype, largs, self)
|
||||
return visitor.visit_invoke(
|
||||
self.name, v_m[self.base], self.ptype, self.rtype, largs, self
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
v_m = self.var_map
|
||||
return '{}.{}({})'.format(v_m[self.base], self.name,
|
||||
', '.join('%s' % v_m[i] for i in self.args))
|
||||
return '{}.{}({})'.format(
|
||||
v_m[self.base],
|
||||
self.name,
|
||||
', '.join('%s' % v_m[i] for i in self.args),
|
||||
)
|
||||
|
||||
|
||||
class InvokeRangeInstruction(InvokeInstruction):
|
||||
def __init__(self, clsname, name, rtype, ptype, args, triple):
|
||||
base = args.pop(0)
|
||||
super().__init__(clsname, name, base, rtype,
|
||||
ptype, args, triple)
|
||||
super().__init__(clsname, name, base, rtype, ptype, args, triple)
|
||||
|
||||
|
||||
class InvokeDirectInstruction(InvokeInstruction):
|
||||
def __init__(self, clsname, name, base, rtype, ptype, args, triple):
|
||||
super().__init__(
|
||||
clsname, name, base, rtype, ptype, args, triple)
|
||||
super().__init__(clsname, name, base, rtype, ptype, args, triple)
|
||||
|
||||
|
||||
class InvokeStaticInstruction(InvokeInstruction):
|
||||
def __init__(self, clsname, name, base, rtype, ptype, args, triple):
|
||||
super().__init__(
|
||||
clsname, name, base, rtype, ptype, args, triple)
|
||||
super().__init__(clsname, name, base, rtype, ptype, args, triple)
|
||||
|
||||
def get_used_vars(self):
|
||||
v_m = self.var_map
|
||||
@ -737,8 +738,9 @@ class CheckCastExpression(IRForm):
|
||||
return self.var_map[self.arg].get_used_vars()
|
||||
|
||||
def visit(self, visitor):
|
||||
return visitor.visit_check_cast(self.var_map[self.arg],
|
||||
util.get_type(self.type))
|
||||
return visitor.visit_check_cast(
|
||||
self.var_map[self.arg], util.get_type(self.type)
|
||||
)
|
||||
|
||||
def replace_var(self, old, new):
|
||||
self.arg = new.v
|
||||
@ -1083,8 +1085,10 @@ class BinaryExpression(IRForm):
|
||||
|
||||
def has_side_effect(self):
|
||||
v_m = self.var_map
|
||||
return (v_m[self.arg1].has_side_effect() or
|
||||
v_m[self.arg2].has_side_effect())
|
||||
return (
|
||||
v_m[self.arg1].has_side_effect()
|
||||
or v_m[self.arg2].has_side_effect()
|
||||
)
|
||||
|
||||
def get_used_vars(self):
|
||||
v_m = self.var_map
|
||||
@ -1094,8 +1098,9 @@ class BinaryExpression(IRForm):
|
||||
|
||||
def visit(self, visitor):
|
||||
v_m = self.var_map
|
||||
return visitor.visit_binary_expression(self.op, v_m[self.arg1],
|
||||
v_m[self.arg2])
|
||||
return visitor.visit_binary_expression(
|
||||
self.op, v_m[self.arg1], v_m[self.arg2]
|
||||
)
|
||||
|
||||
def replace_var(self, old, new):
|
||||
if self.arg1 == old:
|
||||
@ -1136,8 +1141,9 @@ class BinaryCompExpression(BinaryExpression):
|
||||
|
||||
def visit(self, visitor):
|
||||
v_m = self.var_map
|
||||
return visitor.visit_cond_expression(self.op, v_m[self.arg1],
|
||||
v_m[self.arg2])
|
||||
return visitor.visit_cond_expression(
|
||||
self.op, v_m[self.arg1], v_m[self.arg2]
|
||||
)
|
||||
|
||||
|
||||
class BinaryExpression2Addr(BinaryExpression):
|
||||
@ -1209,7 +1215,14 @@ class CastExpression(UnaryExpression):
|
||||
return 'CAST_{}({})'.format(self.op, self.var_map[self.arg])
|
||||
|
||||
|
||||
CONDS = {'==': '!=', '!=': '==', '<': '>=', '<=': '>', '>=': '<', '>': '<=', }
|
||||
CONDS = {
|
||||
'==': '!=',
|
||||
'!=': '==',
|
||||
'<': '>=',
|
||||
'<=': '>',
|
||||
'>=': '<',
|
||||
'>': '<=',
|
||||
}
|
||||
|
||||
|
||||
class ConditionalExpression(IRForm):
|
||||
@ -1237,8 +1250,9 @@ class ConditionalExpression(IRForm):
|
||||
|
||||
def visit(self, visitor):
|
||||
v_m = self.var_map
|
||||
return visitor.visit_cond_expression(self.op, v_m[self.arg1],
|
||||
v_m[self.arg2])
|
||||
return visitor.visit_cond_expression(
|
||||
self.op, v_m[self.arg1], v_m[self.arg2]
|
||||
)
|
||||
|
||||
def replace_var(self, old, new):
|
||||
if self.arg1 == old:
|
||||
@ -1270,7 +1284,9 @@ class ConditionalExpression(IRForm):
|
||||
|
||||
def __str__(self):
|
||||
v_m = self.var_map
|
||||
return 'COND({}, {}, {})'.format(self.op, v_m[self.arg1], v_m[self.arg2])
|
||||
return 'COND({}, {}, {})'.format(
|
||||
self.op, v_m[self.arg1], v_m[self.arg2]
|
||||
)
|
||||
|
||||
|
||||
class ConditionalZExpression(IRForm):
|
||||
@ -1335,9 +1351,8 @@ class InstanceExpression(IRForm):
|
||||
|
||||
def visit(self, visitor):
|
||||
return visitor.visit_get_instance(
|
||||
self.var_map[self.arg],
|
||||
self.name,
|
||||
data=self.ftype)
|
||||
self.var_map[self.arg], self.name, data=self.ftype
|
||||
)
|
||||
|
||||
def replace_var(self, old, new):
|
||||
self.arg = new.v
|
||||
|
@ -43,8 +43,11 @@ class MakeProperties(type):
|
||||
attrs.append(key[4:])
|
||||
delattr(cls, key)
|
||||
for attr in attrs:
|
||||
setattr(cls, attr[1:],
|
||||
property(_wrap_get(attr), _wrap_set(attrs, attr)))
|
||||
setattr(
|
||||
cls,
|
||||
attr[1:],
|
||||
property(_wrap_get(attr), _wrap_set(attrs, attr)),
|
||||
)
|
||||
cls._attrs = attrs
|
||||
|
||||
def __call__(cls, *args, **kwds):
|
||||
@ -132,8 +135,9 @@ class Interval:
|
||||
if item in self.content:
|
||||
return True
|
||||
# If the interval contains intervals, we need to check them
|
||||
return any(item in node for node in self.content
|
||||
if isinstance(node, Interval))
|
||||
return any(
|
||||
item in node for node in self.content if isinstance(node, Interval)
|
||||
)
|
||||
|
||||
def add_node(self, node):
|
||||
if node in self.content:
|
||||
|
@ -16,22 +16,51 @@
|
||||
# limitations under the License.
|
||||
|
||||
from struct import pack, unpack
|
||||
import androguard.decompiler.util as util
|
||||
from androguard.decompiler.instruction import (
|
||||
ArrayLengthExpression, ArrayLoadExpression, ArrayStoreInstruction,
|
||||
AssignExpression, BaseClass, BinaryCompExpression, BinaryExpression,
|
||||
BinaryExpression2Addr, BinaryExpressionLit, CastExpression,
|
||||
CheckCastExpression, ConditionalExpression, ConditionalZExpression,
|
||||
Constant, FillArrayExpression, FilledArrayExpression, InstanceExpression,
|
||||
InstanceInstruction, InvokeInstruction, InvokeDirectInstruction,
|
||||
InvokeRangeInstruction, InvokeStaticInstruction, MonitorEnterExpression,
|
||||
MonitorExitExpression, MoveExceptionExpression, MoveExpression,
|
||||
MoveResultExpression, NewArrayExpression, NewInstance, NopExpression,
|
||||
ThrowExpression, Variable, ReturnInstruction, StaticExpression,
|
||||
StaticInstruction, SwitchExpression, ThisParam, UnaryExpression)
|
||||
|
||||
from loguru import logger
|
||||
|
||||
import androguard.decompiler.util as util
|
||||
from androguard.decompiler.instruction import (
|
||||
ArrayLengthExpression,
|
||||
ArrayLoadExpression,
|
||||
ArrayStoreInstruction,
|
||||
AssignExpression,
|
||||
BaseClass,
|
||||
BinaryCompExpression,
|
||||
BinaryExpression,
|
||||
BinaryExpression2Addr,
|
||||
BinaryExpressionLit,
|
||||
CastExpression,
|
||||
CheckCastExpression,
|
||||
ConditionalExpression,
|
||||
ConditionalZExpression,
|
||||
Constant,
|
||||
FillArrayExpression,
|
||||
FilledArrayExpression,
|
||||
InstanceExpression,
|
||||
InstanceInstruction,
|
||||
InvokeDirectInstruction,
|
||||
InvokeInstruction,
|
||||
InvokeRangeInstruction,
|
||||
InvokeStaticInstruction,
|
||||
MonitorEnterExpression,
|
||||
MonitorExitExpression,
|
||||
MoveExceptionExpression,
|
||||
MoveExpression,
|
||||
MoveResultExpression,
|
||||
NewArrayExpression,
|
||||
NewInstance,
|
||||
NopExpression,
|
||||
ReturnInstruction,
|
||||
StaticExpression,
|
||||
StaticInstruction,
|
||||
SwitchExpression,
|
||||
ThisParam,
|
||||
ThrowExpression,
|
||||
UnaryExpression,
|
||||
Variable,
|
||||
)
|
||||
|
||||
|
||||
class Op:
|
||||
CMP = 'cmp'
|
||||
@ -93,14 +122,16 @@ def assign_cast_exp(val_a, val_b, val_op, op_type, vmap):
|
||||
|
||||
def assign_binary_exp(ins, val_op, op_type, vmap):
|
||||
reg_a, reg_b, reg_c = get_variables(vmap, ins.AA, ins.BB, ins.CC)
|
||||
return AssignExpression(reg_a, BinaryExpression(val_op, reg_b, reg_c,
|
||||
op_type))
|
||||
return AssignExpression(
|
||||
reg_a, BinaryExpression(val_op, reg_b, reg_c, op_type)
|
||||
)
|
||||
|
||||
|
||||
def assign_binary_2addr_exp(ins, val_op, op_type, vmap):
|
||||
reg_a, reg_b = get_variables(vmap, ins.A, ins.B)
|
||||
return AssignExpression(reg_a, BinaryExpression2Addr(val_op, reg_a, reg_b,
|
||||
op_type))
|
||||
return AssignExpression(
|
||||
reg_a, BinaryExpression2Addr(val_op, reg_a, reg_b, op_type)
|
||||
)
|
||||
|
||||
|
||||
def assign_lit(op_type, val_cst, val_a, val_b, vmap):
|
||||
@ -111,6 +142,7 @@ def assign_lit(op_type, val_cst, val_a, val_b, vmap):
|
||||
|
||||
## From here on, there are all defined instructions
|
||||
|
||||
|
||||
# nop
|
||||
def nop(ins, vmap):
|
||||
return NopExpression()
|
||||
@ -300,9 +332,11 @@ def conststringjumbo(ins, vmap):
|
||||
# const-class vAA, type@BBBB ( 8b )
|
||||
def constclass(ins, vmap):
|
||||
logger.debug('ConstClass : %s', ins.get_output())
|
||||
cst = Constant(util.get_type(ins.get_string()),
|
||||
'Ljava/lang/Class;',
|
||||
descriptor=ins.get_string())
|
||||
cst = Constant(
|
||||
util.get_type(ins.get_string()),
|
||||
'Ljava/lang/Class;',
|
||||
descriptor=ins.get_string(),
|
||||
)
|
||||
return assign_const(ins.AA, cst, vmap)
|
||||
|
||||
|
||||
@ -324,9 +358,9 @@ def checkcast(ins, vmap):
|
||||
logger.debug('CheckCast: %s', ins.get_output())
|
||||
cast_type = util.get_type(ins.get_translated_kind())
|
||||
cast_var = get_variables(vmap, ins.AA)
|
||||
cast_expr = CheckCastExpression(cast_var,
|
||||
cast_type,
|
||||
descriptor=ins.get_translated_kind())
|
||||
cast_expr = CheckCastExpression(
|
||||
cast_var, cast_type, descriptor=ins.get_translated_kind()
|
||||
)
|
||||
return AssignExpression(cast_var, cast_expr)
|
||||
|
||||
|
||||
@ -334,8 +368,10 @@ def checkcast(ins, vmap):
|
||||
def instanceof(ins, vmap):
|
||||
logger.debug('InstanceOf : %s', ins.get_output())
|
||||
reg_a, reg_b = get_variables(vmap, ins.A, ins.B)
|
||||
reg_c = BaseClass(util.get_type(ins.get_translated_kind()),
|
||||
descriptor=ins.get_translated_kind())
|
||||
reg_c = BaseClass(
|
||||
util.get_type(ins.get_translated_kind()),
|
||||
descriptor=ins.get_translated_kind(),
|
||||
)
|
||||
exp = BinaryExpression('instanceof', reg_b, reg_c, 'Z')
|
||||
return AssignExpression(reg_a, exp)
|
||||
|
||||
@ -368,7 +404,7 @@ def fillednewarray(ins, vmap, ret):
|
||||
logger.debug('FilledNewArray : %s', ins.get_output())
|
||||
c, d, e, f, g = get_variables(vmap, ins.C, ins.D, ins.E, ins.F, ins.G)
|
||||
array_type = ins.cm.get_type(ins.BBBB)
|
||||
exp = FilledArrayExpression(ins.A, array_type, [c, d, e, f, g][:ins.A])
|
||||
exp = FilledArrayExpression(ins.A, array_type, [c, d, e, f, g][: ins.A])
|
||||
return AssignExpression(ret, exp)
|
||||
|
||||
|
||||
@ -887,8 +923,9 @@ def invokevirtual(ins, vmap, ret):
|
||||
args = get_args(vmap, param_type, largs)
|
||||
c = get_variables(vmap, ins.C)
|
||||
returned = None if ret_type == 'V' else ret.new()
|
||||
exp = InvokeInstruction(cls_name, name, c, ret_type, param_type, args,
|
||||
method.get_triple())
|
||||
exp = InvokeInstruction(
|
||||
cls_name, name, c, ret_type, param_type, args, method.get_triple()
|
||||
)
|
||||
return AssignExpression(returned, exp)
|
||||
|
||||
|
||||
@ -904,8 +941,15 @@ def invokesuper(ins, vmap, ret):
|
||||
args = get_args(vmap, param_type, largs)
|
||||
superclass = BaseClass('super')
|
||||
returned = None if ret_type == 'V' else ret.new()
|
||||
exp = InvokeInstruction(cls_name, name, superclass, ret_type, param_type,
|
||||
args, method.get_triple())
|
||||
exp = InvokeInstruction(
|
||||
cls_name,
|
||||
name,
|
||||
superclass,
|
||||
ret_type,
|
||||
param_type,
|
||||
args,
|
||||
method.get_triple(),
|
||||
)
|
||||
return AssignExpression(returned, exp)
|
||||
|
||||
|
||||
@ -928,8 +972,9 @@ def invokedirect(ins, vmap, ret):
|
||||
ret.set_to(base)
|
||||
else:
|
||||
returned = ret.new()
|
||||
exp = InvokeDirectInstruction(cls_name, name, base, ret_type, param_type,
|
||||
args, method.get_triple())
|
||||
exp = InvokeDirectInstruction(
|
||||
cls_name, name, base, ret_type, param_type, args, method.get_triple()
|
||||
)
|
||||
return AssignExpression(returned, exp)
|
||||
|
||||
|
||||
@ -945,8 +990,9 @@ def invokestatic(ins, vmap, ret):
|
||||
args = get_args(vmap, param_type, largs)
|
||||
base = BaseClass(cls_name, descriptor=method.get_class_name())
|
||||
returned = None if ret_type == 'V' else ret.new()
|
||||
exp = InvokeStaticInstruction(cls_name, name, base, ret_type, param_type,
|
||||
args, method.get_triple())
|
||||
exp = InvokeStaticInstruction(
|
||||
cls_name, name, base, ret_type, param_type, args, method.get_triple()
|
||||
)
|
||||
return AssignExpression(returned, exp)
|
||||
|
||||
|
||||
@ -962,8 +1008,9 @@ def invokeinterface(ins, vmap, ret):
|
||||
args = get_args(vmap, param_type, largs)
|
||||
c = get_variables(vmap, ins.C)
|
||||
returned = None if ret_type == 'V' else ret.new()
|
||||
exp = InvokeInstruction(cls_name, name, c, ret_type, param_type, args,
|
||||
method.get_triple())
|
||||
exp = InvokeInstruction(
|
||||
cls_name, name, c, ret_type, param_type, args, method.get_triple()
|
||||
)
|
||||
return AssignExpression(returned, exp)
|
||||
|
||||
|
||||
@ -979,8 +1026,14 @@ def invokevirtualrange(ins, vmap, ret):
|
||||
this_arg = get_variables(vmap, largs[0])
|
||||
args = get_args(vmap, param_type, largs[1:])
|
||||
returned = None if ret_type == 'V' else ret.new()
|
||||
exp = InvokeRangeInstruction(cls_name, name, ret_type, param_type,
|
||||
[this_arg] + args, method.get_triple())
|
||||
exp = InvokeRangeInstruction(
|
||||
cls_name,
|
||||
name,
|
||||
ret_type,
|
||||
param_type,
|
||||
[this_arg] + args,
|
||||
method.get_triple(),
|
||||
)
|
||||
return AssignExpression(returned, exp)
|
||||
|
||||
|
||||
@ -1001,8 +1054,14 @@ def invokesuperrange(ins, vmap, ret):
|
||||
returned = base
|
||||
ret.set_to(base)
|
||||
superclass = BaseClass('super')
|
||||
exp = InvokeRangeInstruction(cls_name, name, ret_type, param_type,
|
||||
[superclass] + args, method.get_triple())
|
||||
exp = InvokeRangeInstruction(
|
||||
cls_name,
|
||||
name,
|
||||
ret_type,
|
||||
param_type,
|
||||
[superclass] + args,
|
||||
method.get_triple(),
|
||||
)
|
||||
return AssignExpression(returned, exp)
|
||||
|
||||
|
||||
@ -1023,8 +1082,14 @@ def invokedirectrange(ins, vmap, ret):
|
||||
else:
|
||||
returned = base
|
||||
ret.set_to(base)
|
||||
exp = InvokeRangeInstruction(cls_name, name, ret_type, param_type,
|
||||
[this_arg] + args, method.get_triple())
|
||||
exp = InvokeRangeInstruction(
|
||||
cls_name,
|
||||
name,
|
||||
ret_type,
|
||||
param_type,
|
||||
[this_arg] + args,
|
||||
method.get_triple(),
|
||||
)
|
||||
return AssignExpression(returned, exp)
|
||||
|
||||
|
||||
@ -1040,8 +1105,9 @@ def invokestaticrange(ins, vmap, ret):
|
||||
args = get_args(vmap, param_type, largs)
|
||||
base = BaseClass(cls_name, descriptor=method.get_class_name())
|
||||
returned = None if ret_type == 'V' else ret.new()
|
||||
exp = InvokeStaticInstruction(cls_name, name, base, ret_type, param_type,
|
||||
args, method.get_triple())
|
||||
exp = InvokeStaticInstruction(
|
||||
cls_name, name, base, ret_type, param_type, args, method.get_triple()
|
||||
)
|
||||
return AssignExpression(returned, exp)
|
||||
|
||||
|
||||
@ -1057,8 +1123,14 @@ def invokeinterfacerange(ins, vmap, ret):
|
||||
base_arg = get_variables(vmap, largs[0])
|
||||
args = get_args(vmap, param_type, largs[1:])
|
||||
returned = None if ret_type == 'V' else ret.new()
|
||||
exp = InvokeRangeInstruction(cls_name, name, ret_type, param_type,
|
||||
[base_arg] + args, method.get_triple())
|
||||
exp = InvokeRangeInstruction(
|
||||
cls_name,
|
||||
name,
|
||||
ret_type,
|
||||
param_type,
|
||||
[base_arg] + args,
|
||||
method.get_triple(),
|
||||
)
|
||||
return AssignExpression(returned, exp)
|
||||
|
||||
|
||||
|
@ -19,7 +19,9 @@
|
||||
# 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
|
||||
|
||||
@ -79,28 +81,54 @@ ACCESS_FLAGS_METHODS = {
|
||||
0x20000: 'declared_synchronized',
|
||||
}
|
||||
|
||||
ACCESS_ORDER = [0x1, 0x4, 0x2, 0x400, 0x8, 0x10, 0x80, 0x40, 0x20, 0x100, 0x800,
|
||||
0x200, 0x1000, 0x2000, 0x4000, 0x10000, 0x20000]
|
||||
ACCESS_ORDER = [
|
||||
0x1,
|
||||
0x4,
|
||||
0x2,
|
||||
0x400,
|
||||
0x8,
|
||||
0x10,
|
||||
0x80,
|
||||
0x40,
|
||||
0x20,
|
||||
0x100,
|
||||
0x800,
|
||||
0x200,
|
||||
0x1000,
|
||||
0x2000,
|
||||
0x4000,
|
||||
0x10000,
|
||||
0x20000,
|
||||
]
|
||||
|
||||
TYPE_LEN = {'J': 2, 'D': 2, }
|
||||
TYPE_LEN = {
|
||||
'J': 2,
|
||||
'D': 2,
|
||||
}
|
||||
|
||||
|
||||
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]
|
||||
return [
|
||||
ACCESS_FLAGS_CLASSES.get(flag, 'unkn_%d' % flag)
|
||||
for flag in sorted_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]
|
||||
return [
|
||||
ACCESS_FLAGS_METHODS.get(flag, 'unkn_%d' % flag)
|
||||
for flag in sorted_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]
|
||||
return [
|
||||
ACCESS_FLAGS_FIELDS.get(flag, 'unkn_%d' % flag)
|
||||
for flag in sorted_access
|
||||
]
|
||||
|
||||
|
||||
def build_path(graph, node1, node2, path=None):
|
||||
@ -151,8 +179,9 @@ def merge_inner(clsdict):
|
||||
innerclass = innerclass[:-1] # remove ';' of the name
|
||||
mainclass += ';'
|
||||
if mainclass in clsdict:
|
||||
clsdict[mainclass].add_subclass(innerclass,
|
||||
clsdict[classname])
|
||||
clsdict[mainclass].add_subclass(
|
||||
innerclass, clsdict[classname]
|
||||
)
|
||||
clsdict[classname].name = innerclass
|
||||
done[classname] = clsdict[classname]
|
||||
del clsdict[classname]
|
||||
@ -173,7 +202,7 @@ def get_type_size(param):
|
||||
return TYPE_LEN.get(param, 1)
|
||||
|
||||
|
||||
def get_type(atype:str, size:int=None) -> str:
|
||||
def get_type(atype: str, size: int = None) -> str:
|
||||
"""
|
||||
Retrieve the java type of a descriptor (e.g : I)
|
||||
"""
|
||||
@ -205,7 +234,9 @@ def get_params_type(descriptor: str) -> list[str]:
|
||||
return []
|
||||
|
||||
|
||||
def create_png(cls_name: str, meth_name: str, graph: Graph, dir_name:str='graphs2') -> None:
|
||||
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`.
|
||||
|
||||
|
@ -16,21 +16,30 @@
|
||||
# limitations under the License.
|
||||
|
||||
from struct import unpack
|
||||
from androguard.core import mutf8
|
||||
from androguard.decompiler.util import get_type
|
||||
from androguard.decompiler.opcode_ins import Op
|
||||
from androguard.decompiler.instruction import (
|
||||
Constant, ThisParam, BinaryExpression, BaseClass, InstanceExpression,
|
||||
NewInstance, Variable, BinaryCompExpression)
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from androguard.core import mutf8
|
||||
from androguard.decompiler.instruction import (
|
||||
BaseClass,
|
||||
BinaryCompExpression,
|
||||
BinaryExpression,
|
||||
Constant,
|
||||
InstanceExpression,
|
||||
NewInstance,
|
||||
ThisParam,
|
||||
Variable,
|
||||
)
|
||||
from androguard.decompiler.opcode_ins import Op
|
||||
from androguard.decompiler.util import get_type
|
||||
|
||||
|
||||
class Writer:
|
||||
"""
|
||||
Transforms a method into Java code.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, graph, method):
|
||||
self.graph = graph
|
||||
self.method = method
|
||||
@ -54,10 +63,10 @@ class Writer:
|
||||
return self.buffer2
|
||||
|
||||
def inc_ind(self, i=1):
|
||||
self.ind += (4 * i)
|
||||
self.ind += 4 * i
|
||||
|
||||
def dec_ind(self, i=1):
|
||||
self.ind -= (4 * i)
|
||||
self.ind -= 4 * i
|
||||
|
||||
def space(self):
|
||||
if self.skip:
|
||||
@ -104,14 +113,16 @@ class Writer:
|
||||
|
||||
# TODO: prefer this class as write_ind_visit_end that should be deprecated
|
||||
# at the end
|
||||
def write_ind_visit_end_ext(self,
|
||||
lhs,
|
||||
before,
|
||||
s,
|
||||
after,
|
||||
rhs=None,
|
||||
data=None,
|
||||
subsection='UNKNOWN_SUBSECTION'):
|
||||
def write_ind_visit_end_ext(
|
||||
self,
|
||||
lhs,
|
||||
before,
|
||||
s,
|
||||
after,
|
||||
rhs=None,
|
||||
data=None,
|
||||
subsection='UNKNOWN_SUBSECTION',
|
||||
):
|
||||
self.write_ind()
|
||||
lhs.visit(self)
|
||||
self.write(before + s + after)
|
||||
@ -125,14 +136,15 @@ class Writer:
|
||||
def write_inplace_if_possible(self, lhs, rhs):
|
||||
if isinstance(rhs, BinaryExpression) and lhs == rhs.var_map[rhs.arg1]:
|
||||
exp_rhs = rhs.var_map[rhs.arg2]
|
||||
if rhs.op in '+-' and isinstance(exp_rhs, Constant) and \
|
||||
exp_rhs.get_int_value() == 1:
|
||||
if (
|
||||
rhs.op in '+-'
|
||||
and isinstance(exp_rhs, Constant)
|
||||
and exp_rhs.get_int_value() == 1
|
||||
):
|
||||
return self.write_ind_visit_end(lhs, rhs.op * 2, data=rhs)
|
||||
return self.write_ind_visit_end(
|
||||
lhs,
|
||||
' %s= ' % rhs.op,
|
||||
exp_rhs,
|
||||
data=rhs)
|
||||
lhs, ' %s= ' % rhs.op, exp_rhs, data=rhs
|
||||
)
|
||||
return self.write_ind_visit_end(lhs, ' = ', rhs, data=rhs)
|
||||
|
||||
def visit_ins(self, ins):
|
||||
@ -157,20 +169,28 @@ class Writer:
|
||||
self.write(name)
|
||||
self.write_ext(('NAME_METHOD_PROTOTYPE', '%s' % name, self.method))
|
||||
else:
|
||||
self.write('{} {}'.format(get_type(self.method.type), self.method.name))
|
||||
self.write(
|
||||
'{} {}'.format(get_type(self.method.type), self.method.name)
|
||||
)
|
||||
self.write_ext(
|
||||
('PROTOTYPE_TYPE', '%s' % get_type(self.method.type)))
|
||||
('PROTOTYPE_TYPE', '%s' % get_type(self.method.type))
|
||||
)
|
||||
self.write_ext(('SPACE', ' '))
|
||||
self.write_ext(
|
||||
('NAME_METHOD_PROTOTYPE', '%s' % self.method.name, self.method))
|
||||
('NAME_METHOD_PROTOTYPE', '%s' % self.method.name, self.method)
|
||||
)
|
||||
params = self.method.lparams
|
||||
if 'static' not in access:
|
||||
params = params[1:]
|
||||
proto = ''
|
||||
self.write_ext(('PARENTHESIS_START', '('))
|
||||
if self.method.params_type:
|
||||
proto = ', '.join(['{} p{}'.format(get_type(p_type), param) for p_type,
|
||||
param in zip(self.method.params_type, params)])
|
||||
proto = ', '.join(
|
||||
[
|
||||
'{} p{}'.format(get_type(p_type), param)
|
||||
for p_type, param in zip(self.method.params_type, params)
|
||||
]
|
||||
)
|
||||
first = True
|
||||
for p_type, param in zip(self.method.params_type, params):
|
||||
if not first:
|
||||
@ -179,7 +199,9 @@ class Writer:
|
||||
first = False
|
||||
self.write_ext(('ARG_TYPE', '%s' % get_type(p_type)))
|
||||
self.write_ext(('SPACE', ' '))
|
||||
self.write_ext(('NAME_ARG', 'p%s' % param, p_type, self.method))
|
||||
self.write_ext(
|
||||
('NAME_ARG', 'p%s' % param, p_type, self.method)
|
||||
)
|
||||
self.write_ext(('PARENTHESIS_END', ')'))
|
||||
self.write('(%s)' % proto)
|
||||
if self.graph is None:
|
||||
@ -195,9 +217,13 @@ class Writer:
|
||||
self.write_ext(('METHOD_END', '%s}\n' % self.space()))
|
||||
|
||||
def visit_node(self, node):
|
||||
if node in (self.if_follow[-1], self.switch_follow[-1],
|
||||
self.loop_follow[-1], self.latch_node[-1],
|
||||
self.try_follow[-1]):
|
||||
if node in (
|
||||
self.if_follow[-1],
|
||||
self.switch_follow[-1],
|
||||
self.loop_follow[-1],
|
||||
self.latch_node[-1],
|
||||
self.try_follow[-1],
|
||||
):
|
||||
return
|
||||
if not node.type.is_return and node in self.visited_nodes:
|
||||
return
|
||||
@ -257,12 +283,17 @@ class Writer:
|
||||
def visit_cond_node(self, cond):
|
||||
follow = cond.follow['if']
|
||||
if cond.false is cond.true:
|
||||
self.write('%s// Both branches of the condition point to the same'
|
||||
' code.\n' % self.space())
|
||||
self.write(
|
||||
'%s// Both branches of the condition point to the same'
|
||||
' code.\n' % self.space()
|
||||
)
|
||||
self.write_ext(
|
||||
('COMMENT_ERROR_MSG',
|
||||
'%s// Both branches of the condition point to the same'
|
||||
' code.\n' % self.space()))
|
||||
(
|
||||
'COMMENT_ERROR_MSG',
|
||||
'%s// Both branches of the condition point to the same'
|
||||
' code.\n' % self.space(),
|
||||
)
|
||||
)
|
||||
self.write('%s// if (' % self.space())
|
||||
self.write_ext(('COMMENT_IF', '%s// if (' % self.space()))
|
||||
cond.visit_cond(self)
|
||||
@ -286,8 +317,10 @@ class Writer:
|
||||
self.write('%s}\n' % self.space(), data="IF_END_2")
|
||||
self.visit_node(cond.false)
|
||||
elif follow is not None:
|
||||
if cond.true in (follow, self.next_case) or \
|
||||
cond.num > cond.true.num:
|
||||
if (
|
||||
cond.true in (follow, self.next_case)
|
||||
or cond.num > cond.true.num
|
||||
):
|
||||
# or cond.true.num > cond.false.num:
|
||||
cond.neg()
|
||||
cond.true, cond.false = cond.false, cond.true
|
||||
@ -348,8 +381,8 @@ class Writer:
|
||||
self.inc_ind()
|
||||
for case in switch.node_to_case[node]:
|
||||
self.write(
|
||||
'%scase %d:\n' % (self.space(), case),
|
||||
data="CASE_XX")
|
||||
'%scase %d:\n' % (self.space(), case), data="CASE_XX"
|
||||
)
|
||||
if i + 1 < len(cases):
|
||||
self.next_case = cases[i + 1]
|
||||
else:
|
||||
@ -419,16 +452,18 @@ class Writer:
|
||||
def visit_decl(self, var):
|
||||
if not var.declared:
|
||||
var_type = var.get_type() or 'unknownType'
|
||||
self.write('{}{} v{}'.format(
|
||||
self.space(), get_type(var_type), var.name),
|
||||
data="DECLARATION")
|
||||
self.write(
|
||||
'{}{} v{}'.format(self.space(), get_type(var_type), var.name),
|
||||
data="DECLARATION",
|
||||
)
|
||||
self.end_ins()
|
||||
|
||||
def visit_constant(self, cst):
|
||||
if isinstance(cst, str):
|
||||
return self.write(string(cst), data="CONSTANT_STRING")
|
||||
self.write('%r' % cst,
|
||||
data="CONSTANT_INTEGER") # INTEGER or also others?
|
||||
self.write(
|
||||
'%r' % cst, data="CONSTANT_INTEGER"
|
||||
) # INTEGER or also others?
|
||||
|
||||
def visit_base_class(self, cls, data=None):
|
||||
self.write(cls)
|
||||
@ -439,7 +474,8 @@ class Writer:
|
||||
if not var.declared:
|
||||
self.write('%s ' % get_type(var_type))
|
||||
self.write_ext(
|
||||
('VARIABLE_TYPE', '%s' % get_type(var_type), var_type))
|
||||
('VARIABLE_TYPE', '%s' % get_type(var_type), var_type)
|
||||
)
|
||||
self.write_ext(('SPACE', ' '))
|
||||
var.declared = True
|
||||
self.write('v%s' % var.name)
|
||||
@ -493,13 +529,15 @@ class Writer:
|
||||
' = ',
|
||||
rhs,
|
||||
data=data,
|
||||
subsection='NAME_CLASS_ASSIGNMENT')
|
||||
subsection='NAME_CLASS_ASSIGNMENT',
|
||||
)
|
||||
|
||||
def visit_new(self, atype, data=None):
|
||||
self.write('new %s' % get_type(atype))
|
||||
self.write_ext(('NEW', 'new '))
|
||||
self.write_ext(
|
||||
('NAME_CLASS_NEW', '%s' % get_type(atype), data.type, data))
|
||||
('NAME_CLASS_NEW', '%s' % get_type(atype), data.type, data)
|
||||
)
|
||||
|
||||
def visit_invoke(self, name, base, ptype, rtype, args, invokeInstr):
|
||||
if isinstance(base, ThisParam):
|
||||
@ -507,7 +545,10 @@ class Writer:
|
||||
if self.constructor and len(args) == 0:
|
||||
self.skip = True
|
||||
return
|
||||
if invokeInstr and base.type[1:-1].replace('/', '.') != invokeInstr.cls:
|
||||
if (
|
||||
invokeInstr
|
||||
and base.type[1:-1].replace('/', '.') != invokeInstr.cls
|
||||
):
|
||||
base.super = True
|
||||
base.visit(self)
|
||||
if name != '<init>':
|
||||
@ -522,8 +563,9 @@ class Writer:
|
||||
if isinstance(base2base, NewInstance):
|
||||
call_name = "{} -> {}".format(base2base.type, name)
|
||||
break
|
||||
elif (hasattr(base2base, "base") and
|
||||
hasattr(base2base, "var_map")):
|
||||
elif hasattr(base2base, "base") and hasattr(
|
||||
base2base, "var_map"
|
||||
):
|
||||
continue
|
||||
else:
|
||||
call_name = "UNKNOWN_TODO"
|
||||
@ -537,8 +579,16 @@ class Writer:
|
||||
self.write('.%s' % name)
|
||||
self.write_ext(('INVOKE', '.'))
|
||||
self.write_ext(
|
||||
('NAME_METHOD_INVOKE', '%s' % name, call_name, ptype, rtype,
|
||||
base, invokeInstr))
|
||||
(
|
||||
'NAME_METHOD_INVOKE',
|
||||
'%s' % name,
|
||||
call_name,
|
||||
ptype,
|
||||
rtype,
|
||||
base,
|
||||
invokeInstr,
|
||||
)
|
||||
)
|
||||
self.write('(', data="PARAM_START")
|
||||
comma = False
|
||||
for arg in args:
|
||||
@ -608,12 +658,16 @@ class Writer:
|
||||
elem_id = data_types[elem_size]
|
||||
else:
|
||||
# FIXME for other types we just assume bytes...
|
||||
logger.warning("Unknown element size {} for array. Assume bytes.".format(elem_size))
|
||||
logger.warning(
|
||||
"Unknown element size {} for array. Assume bytes.".format(
|
||||
elem_size
|
||||
)
|
||||
)
|
||||
elem_id = 'b'
|
||||
elem_size = 1
|
||||
|
||||
for i in range(0, value.size*elem_size, elem_size):
|
||||
tab.append('%s' % unpack(elem_id, data[i:i+elem_size])[0])
|
||||
for i in range(0, value.size * elem_size, elem_size):
|
||||
tab.append('%s' % unpack(elem_id, data[i : i + elem_size])[0])
|
||||
self.write(', '.join(tab), data="COMMA")
|
||||
self.write('}', data="ARRAY_FILLED_END")
|
||||
self.end_ins()
|
||||
@ -622,10 +676,13 @@ class Writer:
|
||||
var.declared = True
|
||||
var_type = var.get_type() or 'unknownType'
|
||||
self.write('{} v{}'.format(get_type(var_type), var.name))
|
||||
self.write_ext(('EXCEPTION_TYPE', '%s' % get_type(var_type), data.type))
|
||||
self.write_ext(
|
||||
('EXCEPTION_TYPE', '%s' % get_type(var_type), data.type)
|
||||
)
|
||||
self.write_ext(('SPACE', ' '))
|
||||
self.write_ext(
|
||||
('NAME_CLASS_EXCEPTION', 'v%s' % var.value(), data.type, data))
|
||||
('NAME_CLASS_EXCEPTION', 'v%s' % var.value(), data.type, data)
|
||||
)
|
||||
|
||||
def visit_monitor_enter(self, ref):
|
||||
self.write_ind()
|
||||
@ -718,8 +775,8 @@ def string(s):
|
||||
i = ord(c)
|
||||
ret.append('\\u')
|
||||
ret.append('%x' % (i >> 12))
|
||||
ret.append('%x' % ((i >> 8) & 0x0f))
|
||||
ret.append('%x' % ((i >> 4) & 0x0f))
|
||||
ret.append('%x' % (i & 0x0f))
|
||||
ret.append('%x' % ((i >> 8) & 0x0F))
|
||||
ret.append('%x' % ((i >> 4) & 0x0F))
|
||||
ret.append('%x' % (i & 0x0F))
|
||||
ret.append('"')
|
||||
return ''.join(ret)
|
||||
|
@ -2,6 +2,7 @@
|
||||
# in Python >= 3.7
|
||||
# see https://peps.python.org/pep-0563/
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import os
|
||||
import re
|
||||
@ -9,11 +10,11 @@ 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 import androconf, apk, dex
|
||||
from androguard.core.analysis.analysis import Analysis
|
||||
from androguard.decompiler import decompiler
|
||||
from androguard.session import Session
|
||||
|
||||
|
||||
def get_default_session() -> Session:
|
||||
"""
|
||||
@ -27,7 +28,11 @@ def get_default_session() -> Session:
|
||||
return androconf.CONF["SESSION"]
|
||||
|
||||
|
||||
def AnalyzeAPK(_file: Union[str,bytes], session:Union[Session,None]=None, raw:bool=False) -> tuple[apk.APK, list[dex.DEX], Analysis]:
|
||||
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!
|
||||
@ -76,7 +81,9 @@ def AnalyzeAPK(_file: Union[str,bytes], session:Union[Session,None]=None, raw:bo
|
||||
return a, d, dx
|
||||
|
||||
|
||||
def AnalyzeDex(filename: str, session:Session=None, raw:bool=False) -> tuple[str, dex.DEX, Analysis]:
|
||||
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 !
|
||||
|
||||
@ -126,7 +133,12 @@ def AnalyzeDex(filename: str, session:Session=None, raw:bool=False) -> tuple[str
|
||||
# return session.addDEY(filename, data) # <- this function is missing
|
||||
|
||||
|
||||
def clean_file_name(filename: str, unique:bool=True, replace:str="_", force_nt:bool=False) -> str:
|
||||
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 <, /, ?, ...
|
||||
@ -167,7 +179,7 @@ def clean_file_name(filename: str, unique:bool=True, replace:str="_", force_nt:b
|
||||
if len(fname) > PATH_MAX_LENGTH:
|
||||
if "." in fname:
|
||||
f, ext = fname.rsplit(".", 1)
|
||||
fname = "{}.{}".format(f[:PATH_MAX_LENGTH-(len(ext)+1)], ext)
|
||||
fname = "{}.{}".format(f[: PATH_MAX_LENGTH - (len(ext) + 1)], ext)
|
||||
else:
|
||||
fname = fname[:PATH_MAX_LENGTH]
|
||||
|
||||
@ -175,7 +187,7 @@ def clean_file_name(filename: str, unique:bool=True, replace:str="_", force_nt:b
|
||||
# Special behaviour... On Windows, there is also a problem with the maximum path length in explorer.exe
|
||||
# maximum length is limited to 260 chars, so use 250 to have room for other stuff
|
||||
if len(os.path.abspath(os.path.join(path, fname))) > 250:
|
||||
fname = fname[:250 - (len(os.path.abspath(path)) + 1)]
|
||||
fname = fname[: 250 - (len(os.path.abspath(path)) + 1)]
|
||||
|
||||
if unique:
|
||||
counter = 0
|
||||
@ -190,5 +202,3 @@ def clean_file_name(filename: str, unique:bool=True, replace:str="_", force_nt:b
|
||||
counter += 1
|
||||
|
||||
return os.path.join(path, fname)
|
||||
|
||||
|
||||
|
@ -1,36 +1,45 @@
|
||||
from . import adb
|
||||
|
||||
import frida
|
||||
import glob
|
||||
import hashlib
|
||||
import json
|
||||
import threading
|
||||
import glob
|
||||
import os
|
||||
import queue
|
||||
import threading
|
||||
|
||||
import frida
|
||||
|
||||
from . import adb
|
||||
|
||||
md5 = lambda bs: hashlib.md5(bs).hexdigest()
|
||||
|
||||
from loguru import logger
|
||||
|
||||
|
||||
class Message:
|
||||
pass
|
||||
|
||||
|
||||
class MessageEvent(Message):
|
||||
def __init__(self, index, function_callee, function_call, params, ret_value):
|
||||
def __init__(
|
||||
self, index, function_callee, function_call, params, ret_value
|
||||
):
|
||||
self.index = index
|
||||
self.from_method = function_call
|
||||
self.to_method = function_callee
|
||||
self.params = params
|
||||
self.ret_value = ret_value
|
||||
|
||||
|
||||
class MessageSystem(Message):
|
||||
def __init__(self, index, function_callee, function_call, params, information):
|
||||
def __init__(
|
||||
self, index, function_callee, function_call, params, information
|
||||
):
|
||||
self.index = index
|
||||
self.from_method = function_call
|
||||
self.to_method = function_callee
|
||||
self.params = params
|
||||
self.ret_value = information
|
||||
|
||||
|
||||
class Pentest:
|
||||
def __init__(self):
|
||||
self.ag_session = None
|
||||
@ -48,7 +57,7 @@ class Pentest:
|
||||
|
||||
def is_detached(self):
|
||||
return self.detached
|
||||
|
||||
|
||||
def disconnect(self):
|
||||
logger.info("Disconnected from frida server")
|
||||
|
||||
@ -58,7 +67,7 @@ class Pentest:
|
||||
script.unload()
|
||||
except frida.InvalidOperationError as e:
|
||||
logger.error(e)
|
||||
|
||||
|
||||
self.frida_session.detach()
|
||||
|
||||
self.package_name = None
|
||||
@ -69,7 +78,6 @@ class Pentest:
|
||||
self.pending = []
|
||||
self.list_file_scripts = []
|
||||
|
||||
|
||||
def print_devices(self):
|
||||
logger.info("List of devices")
|
||||
devices = frida.enumerate_devices()
|
||||
@ -87,11 +95,16 @@ class Pentest:
|
||||
with open(script_file, 'r') as file:
|
||||
data_scripts += file.read()
|
||||
data_scripts += '\n\n'
|
||||
|
||||
|
||||
return data_scripts
|
||||
|
||||
def read_scripts(self, scripts):
|
||||
return "Java.perform(function () {\n" + self._read_scripts(self.ag_scripts + scripts) + "\n" + "});"
|
||||
return (
|
||||
"Java.perform(function () {\n"
|
||||
+ self._read_scripts(self.ag_scripts + scripts)
|
||||
+ "\n"
|
||||
+ "});"
|
||||
)
|
||||
|
||||
def install_apk(self, filename):
|
||||
adb.adb(self.device.id, "install {}".format(filename))
|
||||
@ -99,7 +112,11 @@ class Pentest:
|
||||
def attach_package(self, package_name, list_file_scripts, pid=None):
|
||||
self.package_name = package_name
|
||||
|
||||
logger.info("Starting package {} {} {}".format(package_name, list_file_scripts, pid))
|
||||
logger.info(
|
||||
"Starting package {} {} {}".format(
|
||||
package_name, list_file_scripts, pid
|
||||
)
|
||||
)
|
||||
|
||||
self.list_file_scripts = list_file_scripts
|
||||
|
||||
@ -116,20 +133,18 @@ class Pentest:
|
||||
logger.info('Enabled spawn gating')
|
||||
|
||||
try:
|
||||
# It is not an existing process, spawn a new one
|
||||
# It is not an existing process, spawn a new one
|
||||
if not pid:
|
||||
pid = self.device.spawn([package_name])
|
||||
|
||||
|
||||
self.pid = pid
|
||||
|
||||
|
||||
self.frida_session = self.device.attach(self.pid)
|
||||
self.load_scripts(self.frida_session, list_file_scripts)
|
||||
self.frida_session.on('detached', self.on_detached)
|
||||
except frida.NotSupportedError as e:
|
||||
logger.error(e)
|
||||
|
||||
|
||||
|
||||
def load_scripts(self, current_session, scripts):
|
||||
try:
|
||||
logger.info('Loading scripts {}'.format(scripts))
|
||||
@ -144,9 +159,9 @@ class Pentest:
|
||||
def run_frida(self):
|
||||
logger.info("Running frida ! Resuming the PID {}".format(self.pid))
|
||||
self.device.resume(self.pid)
|
||||
|
||||
|
||||
def androguard_message_handler(self, message, payload):
|
||||
# use for system event
|
||||
# use for system event
|
||||
previous_stacktrace = None
|
||||
|
||||
logger.debug("MESSAGE {} {}".format(message, payload))
|
||||
@ -164,15 +179,41 @@ class Pentest:
|
||||
function_callee = msg_payload["stacktrace"][1]
|
||||
ret_value = json.dumps(msg_payload.get("ret"))
|
||||
|
||||
logger.info("{} - [{}:{}] [{}] -> [{}]".format(msg_payload["timestamp"], function_call, function_callee, params, ret_value))
|
||||
self.message_queue.put(MessageEvent(self.idx, function_call, function_callee, params, ret_value))
|
||||
self.ag_session.insert_event(call=function_call, callee=function_callee, params=params, ret=ret_value)
|
||||
logger.info(
|
||||
"{} - [{}:{}] [{}] -> [{}]".format(
|
||||
msg_payload["timestamp"],
|
||||
function_call,
|
||||
function_callee,
|
||||
params,
|
||||
ret_value,
|
||||
)
|
||||
)
|
||||
self.message_queue.put(
|
||||
MessageEvent(
|
||||
self.idx,
|
||||
function_call,
|
||||
function_callee,
|
||||
params,
|
||||
ret_value,
|
||||
)
|
||||
)
|
||||
self.ag_session.insert_event(
|
||||
call=function_call,
|
||||
callee=function_callee,
|
||||
params=params,
|
||||
ret=ret_value,
|
||||
)
|
||||
previous_stacktrace = msg_payload["stacktrace"]
|
||||
|
||||
self.idx += 1
|
||||
elif msg_payload["id"] == "AG-SYSTEM":
|
||||
for i in msg_payload:
|
||||
if i not in ["id", "information", "timestamp", "stacktrace"]:
|
||||
if i not in [
|
||||
"id",
|
||||
"information",
|
||||
"timestamp",
|
||||
"stacktrace",
|
||||
]:
|
||||
params[i] = msg_payload[i]
|
||||
|
||||
function_call = None
|
||||
@ -184,10 +225,31 @@ class Pentest:
|
||||
function_callee = previous_stacktrace[1]
|
||||
else:
|
||||
function_callee = information
|
||||
|
||||
logger.warning("{} - [{}:{}] [{}] -> [{}]".format(msg_payload["timestamp"], function_call, function_callee, information, params))
|
||||
self.message_queue.put(MessageSystem(self.idx, function_call, function_callee, params, information))
|
||||
self.ag_session.insert_system_event(call=function_call, callee=function_callee, params=params, information=information)
|
||||
|
||||
logger.warning(
|
||||
"{} - [{}:{}] [{}] -> [{}]".format(
|
||||
msg_payload["timestamp"],
|
||||
function_call,
|
||||
function_callee,
|
||||
information,
|
||||
params,
|
||||
)
|
||||
)
|
||||
self.message_queue.put(
|
||||
MessageSystem(
|
||||
self.idx,
|
||||
function_call,
|
||||
function_callee,
|
||||
params,
|
||||
information,
|
||||
)
|
||||
)
|
||||
self.ag_session.insert_system_event(
|
||||
call=function_call,
|
||||
callee=function_callee,
|
||||
params=params,
|
||||
information=information,
|
||||
)
|
||||
|
||||
if not previous_stacktrace:
|
||||
self.idx += 1
|
||||
@ -207,7 +269,12 @@ class Pentest:
|
||||
bs = api.memorydump(info['addr'], info['size'])
|
||||
md = md5(bs)
|
||||
if md in mds:
|
||||
logger.warning("[DEXDump]: Skip duplicate dex {}<{}>".format(info['addr'], md), fg="blue")
|
||||
logger.warning(
|
||||
"[DEXDump]: Skip duplicate dex {}<{}>".format(
|
||||
info['addr'], md
|
||||
),
|
||||
fg="blue",
|
||||
)
|
||||
continue
|
||||
mds.append(md)
|
||||
|
||||
@ -215,36 +282,45 @@ class Pentest:
|
||||
os.mkdir("./" + package_name + "/")
|
||||
if bs[:4] != "dex\n":
|
||||
bs = b"dex\n035\x00" + bs[8:]
|
||||
readable_hash = hashlib.sha256(bs).hexdigest();
|
||||
with open(package_name + "/" + readable_hash + ".dex", 'wb') as out:
|
||||
readable_hash = hashlib.sha256(bs).hexdigest()
|
||||
with open(
|
||||
package_name + "/" + readable_hash + ".dex", 'wb'
|
||||
) as out:
|
||||
out.write(bs)
|
||||
logger.info("[DEXDump]: DexSize={}, SavePath={}/{}/{}.dex"
|
||||
.format(hex(info['size']), os.getcwd(), package_name, readable_hash), fg='green')
|
||||
logger.info(
|
||||
"[DEXDump]: DexSize={}, SavePath={}/{}/{}.dex".format(
|
||||
hex(info['size']),
|
||||
os.getcwd(),
|
||||
package_name,
|
||||
readable_hash,
|
||||
),
|
||||
fg='green',
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error("[Except] - {}: {}".format(e, info), bg='yellow')
|
||||
|
||||
def on_detached(self, reason):
|
||||
logger.info("Session is detached due to: {}".format(reason))
|
||||
self.detached = True
|
||||
|
||||
|
||||
def on_spawned(self, spawn):
|
||||
#logger.info('on_spawned: {}'.format(spawn))
|
||||
# logger.info('on_spawned: {}'.format(spawn))
|
||||
self.pending.append(spawn)
|
||||
self.event.set()
|
||||
|
||||
|
||||
def spawn_added(self, spawn):
|
||||
#logger.info('spawn_added: {}'.format(spawn))
|
||||
|
||||
# logger.info('spawn_added: {}'.format(spawn))
|
||||
|
||||
self.event.set()
|
||||
|
||||
if(spawn.identifier.startswith(self.package_name)):
|
||||
#logger.info('added tace: {}'.format(spawn))
|
||||
|
||||
session = self.device.attach(spawn.pid)
|
||||
if spawn.identifier.startswith(self.package_name):
|
||||
# logger.info('added tace: {}'.format(spawn))
|
||||
|
||||
session = self.device.attach(spawn.pid)
|
||||
self.load_scripts(session, self.list_file_scripts)
|
||||
self.device.resume(spawn.pid)
|
||||
#logger.info('Resumed')
|
||||
|
||||
# logger.info('Resumed')
|
||||
|
||||
def spawn_removed(self, spawn):
|
||||
logger.info('spawn_removed: {}'.format(spawn))
|
||||
self.event.set()
|
||||
@ -252,7 +328,15 @@ class Pentest:
|
||||
def child_added(self, spawn):
|
||||
logger.info('child_added: {}'.format(spawn))
|
||||
|
||||
def start_trace(self, filename, session, list_modules, live=False, noapk=False, dump=False):
|
||||
def start_trace(
|
||||
self,
|
||||
filename,
|
||||
session,
|
||||
list_modules,
|
||||
live=False,
|
||||
noapk=False,
|
||||
dump=False,
|
||||
):
|
||||
self.ag_session = session
|
||||
|
||||
logger.info("Start to trace {} {}".format(filename, list_modules))
|
||||
@ -262,12 +346,12 @@ class Pentest:
|
||||
if not apk_obj:
|
||||
logger.error("Can't find any APK object associated")
|
||||
return
|
||||
|
||||
|
||||
if not self.device:
|
||||
logger.error("Not connected to any device yet")
|
||||
return
|
||||
|
||||
list_scripts_to_load = []
|
||||
list_scripts_to_load = []
|
||||
for new_module in list_modules:
|
||||
if '*' in new_module:
|
||||
for i_module in glob.iglob(new_module, recursive=True):
|
||||
@ -284,7 +368,15 @@ class Pentest:
|
||||
package_name = apk_obj.get_package()
|
||||
else:
|
||||
package_name = filename
|
||||
pid_value = os.popen("adb -s {} shell pidof {}".format(self.device.id, package_name)).read().strip()
|
||||
pid_value = (
|
||||
os.popen(
|
||||
"adb -s {} shell pidof {}".format(
|
||||
self.device.id, package_name
|
||||
)
|
||||
)
|
||||
.read()
|
||||
.strip()
|
||||
)
|
||||
if pid_value:
|
||||
pid = int(pid_value)
|
||||
|
||||
@ -293,4 +385,4 @@ class Pentest:
|
||||
if not dump:
|
||||
self.run_frida()
|
||||
else:
|
||||
self.dump(package_name)
|
||||
self.dump(package_name)
|
||||
|
@ -2,6 +2,7 @@ import subprocess
|
||||
|
||||
from loguru import logger
|
||||
|
||||
|
||||
def adb(device_id, cmd=None):
|
||||
logger.info("ADB: Running on {}:{}".format(device_id, cmd))
|
||||
subprocess.run('adb -s {} {}'.format(device_id, cmd), shell=True)
|
||||
|
@ -1,15 +1,14 @@
|
||||
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
|
||||
from typing import Iterator, Union
|
||||
|
||||
import dataset
|
||||
from loguru import logger
|
||||
|
||||
from androguard.core import androconf, apk, dex
|
||||
from androguard.core.analysis.analysis import Analysis, StringAnalysis
|
||||
from androguard.decompiler.decompiler import DecompilerDAD
|
||||
|
||||
|
||||
class Session:
|
||||
"""
|
||||
@ -23,7 +22,11 @@ class Session:
|
||||
>>> Should we go back to pickling or proceed further with the dataset ?<<<
|
||||
"""
|
||||
|
||||
def __init__(self, export_ipython:bool=False, db_url:str='sqlite:///androguard.db') -> None:
|
||||
def __init__(
|
||||
self,
|
||||
export_ipython: bool = False,
|
||||
db_url: str = 'sqlite:///androguard.db',
|
||||
) -> None:
|
||||
"""
|
||||
Create a new Session object
|
||||
|
||||
@ -45,7 +48,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:Union[str,None]=None) -> None:
|
||||
def save(self, filename: Union[str, None] = None) -> None:
|
||||
"""
|
||||
Save the current session, see also :func:`~androguard.session.Save`.
|
||||
"""
|
||||
@ -101,13 +104,27 @@ class Session:
|
||||
|
||||
def insert_event(self, call, callee, params, ret):
|
||||
self.table_pentest.insert(
|
||||
dict(session_id=str(self.session_id), call=call, callee=callee, params=params, ret=ret))
|
||||
dict(
|
||||
session_id=str(self.session_id),
|
||||
call=call,
|
||||
callee=callee,
|
||||
params=params,
|
||||
ret=ret,
|
||||
)
|
||||
)
|
||||
|
||||
def insert_system_event(self, call, callee, information, params):
|
||||
self.table_system.insert(
|
||||
dict(session_id=str(self.session_id), call=call, callee=callee, information=information, params=params))
|
||||
dict(
|
||||
session_id=str(self.session_id),
|
||||
call=call,
|
||||
callee=callee,
|
||||
information=information,
|
||||
params=params,
|
||||
)
|
||||
)
|
||||
|
||||
def addAPK(self, filename: str, data:bytes) -> tuple[str, apk.APK]:
|
||||
def addAPK(self, filename: str, data: bytes) -> tuple[str, apk.APK]:
|
||||
"""
|
||||
Add an APK file to the Session and run analysis on it.
|
||||
|
||||
@ -119,7 +136,13 @@ class Session:
|
||||
|
||||
logger.info("add APK {}:{}".format(filename, digest))
|
||||
self.table_information.insert(
|
||||
dict(session_id=str(self.session_id), filename=filename, digest=digest, type="APK"))
|
||||
dict(
|
||||
session_id=str(self.session_id),
|
||||
filename=filename,
|
||||
digest=digest,
|
||||
type="APK",
|
||||
)
|
||||
)
|
||||
|
||||
newapk = apk.APK(data, True)
|
||||
self.analyzed_apk[digest] = [newapk]
|
||||
@ -140,7 +163,13 @@ class Session:
|
||||
logger.info("added APK {}:{}".format(filename, digest))
|
||||
return digest, newapk
|
||||
|
||||
def addDEX(self, filename: str, data: bytes, dx:Union[Analysis,None]=None, postpone_xref:bool=False) -> tuple[str, dex.DEX, Analysis]:
|
||||
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.
|
||||
|
||||
@ -154,7 +183,13 @@ class Session:
|
||||
logger.info("add DEX:{}".format(digest))
|
||||
|
||||
self.table_information.insert(
|
||||
dict(session_id=str(self.session_id), filename=filename, digest=digest, type="DEX"))
|
||||
dict(
|
||||
session_id=str(self.session_id),
|
||||
filename=filename,
|
||||
digest=digest,
|
||||
type="DEX",
|
||||
)
|
||||
)
|
||||
|
||||
logger.debug("Parsing format ...")
|
||||
d = dex.DEX(data)
|
||||
@ -185,7 +220,9 @@ class Session:
|
||||
|
||||
return digest, d, dx
|
||||
|
||||
def addODEX(self, filename:str, data:bytes, dx:Union[Analysis,None]=None) -> tuple[str, dex.ODEX, Analysis]:
|
||||
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
|
||||
"""
|
||||
@ -193,7 +230,13 @@ class Session:
|
||||
logger.info("add ODEX:%s" % digest)
|
||||
|
||||
self.table_information.insert(
|
||||
dict(session_id=str(self.session_id), filename=filename, digest=digest, type="ODEX"))
|
||||
dict(
|
||||
session_id=str(self.session_id),
|
||||
filename=filename,
|
||||
digest=digest,
|
||||
type="ODEX",
|
||||
)
|
||||
)
|
||||
|
||||
d = dex.ODEX(data)
|
||||
logger.debug("added ODEX:%s" % digest)
|
||||
@ -221,7 +264,12 @@ class Session:
|
||||
|
||||
return digest, d, dx
|
||||
|
||||
def add(self, filename: str, raw_data:Union[bytes,None]=None, dx:Union[Analysis,None]=None) -> Union[str,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.
|
||||
|
||||
@ -259,7 +307,9 @@ class Session:
|
||||
|
||||
return digest
|
||||
|
||||
def get_classes(self) -> Iterator[tuple[int, str, str, list[dex.ClassDefItem]]]:
|
||||
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.
|
||||
"""
|
||||
@ -293,7 +343,9 @@ class Session:
|
||||
"""
|
||||
return current_class.CM.vm
|
||||
|
||||
def get_filename_by_class(self, current_class: dex.ClassDefItem) -> Union[str,None]:
|
||||
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.
|
||||
|
||||
@ -309,7 +361,9 @@ class Session:
|
||||
return self.analyzed_digest[digest]
|
||||
return None
|
||||
|
||||
def get_digest_by_class(self, current_class: dex.ClassDefItem) -> Union[str,None]:
|
||||
def get_digest_by_class(
|
||||
self, current_class: dex.ClassDefItem
|
||||
) -> Union[str, None]:
|
||||
"""
|
||||
Return the SHA256 hash of the object containing the ClassDefItem
|
||||
|
||||
@ -322,7 +376,9 @@ class Session:
|
||||
return digest
|
||||
return None
|
||||
|
||||
def get_strings(self) -> Iterator[tuple[str, str, dict[str,StringAnalysis]]]:
|
||||
def get_strings(
|
||||
self,
|
||||
) -> Iterator[tuple[str, str, dict[str, StringAnalysis]]]:
|
||||
"""
|
||||
Yields all StringAnalysis for all unique Analysis objects
|
||||
"""
|
||||
@ -331,7 +387,9 @@ class Session:
|
||||
if dx in seen:
|
||||
continue
|
||||
seen.append(dx)
|
||||
yield digest, self.analyzed_digest[digest], dx.get_strings_analysis()
|
||||
yield digest, self.analyzed_digest[
|
||||
digest
|
||||
], dx.get_strings_analysis()
|
||||
|
||||
def get_nb_strings(self) -> int:
|
||||
"""
|
||||
@ -354,7 +412,11 @@ class Session:
|
||||
for digest, a in self.analyzed_apk.items():
|
||||
yield digest, a
|
||||
|
||||
def get_objects_apk(self, filename:Union[str,None]=None, digest:Union[str,None]=None) -> Iterator[tuple[apk.APK, list[dex.DEX], Analysis]]:
|
||||
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.
|
||||
|
||||
|
@ -1,26 +1,35 @@
|
||||
import os
|
||||
import queue
|
||||
|
||||
from loguru import logger
|
||||
from prompt_toolkit import Application
|
||||
from prompt_toolkit.application import get_app
|
||||
from prompt_toolkit.filters import Condition
|
||||
from prompt_toolkit.formatted_text import StyleAndTextTuples
|
||||
from prompt_toolkit.layout import HSplit, Layout, VSplit, Window, FloatContainer, Float, ConditionalContainer, UIControl, UIContent
|
||||
from prompt_toolkit.key_binding import KeyBindings, merge_key_bindings
|
||||
from prompt_toolkit.layout import (
|
||||
ConditionalContainer,
|
||||
Float,
|
||||
FloatContainer,
|
||||
HSplit,
|
||||
Layout,
|
||||
UIContent,
|
||||
UIControl,
|
||||
VSplit,
|
||||
Window,
|
||||
)
|
||||
from prompt_toolkit.styles import Style
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from androguard.ui.selection import SelectionViewList
|
||||
from androguard.ui.widget.transactions import TransactionFrame
|
||||
from androguard.ui.widget.toolbar import StatusToolbar
|
||||
from androguard.ui.widget.filters import FiltersPanel
|
||||
from androguard.ui.widget.help import HelpPanel
|
||||
from androguard.ui.widget.details import DetailsFrame
|
||||
from androguard.pentest import Message
|
||||
from androguard.ui.data_types import DisplayTransaction
|
||||
from androguard.ui.filter import Filter
|
||||
from androguard.ui.selection import SelectionViewList
|
||||
from androguard.ui.widget.details import DetailsFrame
|
||||
from androguard.ui.widget.filters import FiltersPanel
|
||||
from androguard.ui.widget.help import HelpPanel
|
||||
from androguard.ui.widget.toolbar import StatusToolbar
|
||||
from androguard.ui.widget.transactions import TransactionFrame
|
||||
|
||||
from androguard.pentest import Message
|
||||
|
||||
class DummyControl(UIControl):
|
||||
"""
|
||||
@ -44,7 +53,7 @@ class DummyControl(UIControl):
|
||||
|
||||
|
||||
class DynamicUI:
|
||||
def __init__(self, input_queue):
|
||||
def __init__(self, input_queue):
|
||||
logger.info("Starting the Terminal UI")
|
||||
self.filter: Filter | None = None
|
||||
|
||||
@ -61,7 +70,6 @@ class DynamicUI:
|
||||
|
||||
self.resize_components(os.get_terminal_size())
|
||||
|
||||
|
||||
def run(self):
|
||||
self.focusable = [self.transaction_table, self.details_pane]
|
||||
self.focus_index = 0
|
||||
@ -77,7 +85,11 @@ class DynamicUI:
|
||||
|
||||
@kb1.add('s-tab')
|
||||
def _(event):
|
||||
self.focus_index = len(self.focusable) - 1 if self.focus_index == 0 else self.focus_index -1
|
||||
self.focus_index = (
|
||||
len(self.focusable) - 1
|
||||
if self.focus_index == 0
|
||||
else self.focus_index - 1
|
||||
)
|
||||
for i, f in enumerate(self.focusable):
|
||||
f.activated = i == self.focus_index
|
||||
|
||||
@ -86,12 +98,14 @@ class DynamicUI:
|
||||
key_bindings=kb1,
|
||||
children=[
|
||||
self.transaction_table,
|
||||
VSplit([
|
||||
self.details_pane,
|
||||
# self.structure_pane,
|
||||
]),
|
||||
VSplit(
|
||||
[
|
||||
self.details_pane,
|
||||
# self.structure_pane,
|
||||
]
|
||||
),
|
||||
StatusToolbar(self.transactions, self.filter_panel),
|
||||
Window(content=dummy_control)
|
||||
Window(content=dummy_control),
|
||||
],
|
||||
)
|
||||
|
||||
@ -107,41 +121,52 @@ class DynamicUI:
|
||||
def show_help():
|
||||
return self.help_panel.visible
|
||||
|
||||
|
||||
layout = Layout(
|
||||
container=FloatContainer(
|
||||
content=main_layout,
|
||||
floats=[
|
||||
Float(top=10, content=ConditionalContainer(content=self.filter_panel, filter=show_filters)),
|
||||
Float(top=10, content=ConditionalContainer(content=self.help_panel, filter=show_help)),
|
||||
]
|
||||
Float(
|
||||
top=10,
|
||||
content=ConditionalContainer(
|
||||
content=self.filter_panel, filter=show_filters
|
||||
),
|
||||
),
|
||||
Float(
|
||||
top=10,
|
||||
content=ConditionalContainer(
|
||||
content=self.help_panel, filter=show_help
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
)
|
||||
|
||||
style = Style([
|
||||
('field.selected', 'ansiblack bg:ansiwhite'),
|
||||
('field.default', 'fg:ansiwhite'),
|
||||
('frame.label', 'fg:ansiwhite'),
|
||||
('frame.border', 'fg:ansiwhite'),
|
||||
('frame.border.selected', 'fg:ansibrightgreen'),
|
||||
('transaction.heading', 'ansiblack bg:ansigray'),
|
||||
('transaction.selected', 'ansiblack bg:ansiwhite'),
|
||||
('transaction.default', 'fg:ansiwhite'),
|
||||
('transaction.unsupported', 'fg:ansibrightblack'),
|
||||
('transaction.error', 'fg:ansired'),
|
||||
('transaction.no_aidl', 'fg:ansiwhite'),
|
||||
('transaction.oneway', 'fg:ansimagenta'),
|
||||
('transaction.request', 'fg:ansicyan'),
|
||||
('transaction.response', 'fg:ansiyellow'),
|
||||
('hexdump.default', 'fg:ansiwhite'),
|
||||
('hexdump.selected', 'fg:ansiblack bg:ansiwhite'),
|
||||
('toolbar', 'bg:ansigreen'),
|
||||
('toolbar.text', 'fg:ansiblack'),
|
||||
('dialog', 'fg:ansiblack bg:ansiwhite'),
|
||||
('dialog frame.border', 'fg:ansiblack bg:ansiwhite'),
|
||||
('dialog frame.label', 'fg:ansiblack bg:ansiwhite'),
|
||||
('dialogger.textarea', 'fg:ansiwhite bg:ansiblack'),
|
||||
])
|
||||
style = Style(
|
||||
[
|
||||
('field.selected', 'ansiblack bg:ansiwhite'),
|
||||
('field.default', 'fg:ansiwhite'),
|
||||
('frame.label', 'fg:ansiwhite'),
|
||||
('frame.border', 'fg:ansiwhite'),
|
||||
('frame.border.selected', 'fg:ansibrightgreen'),
|
||||
('transaction.heading', 'ansiblack bg:ansigray'),
|
||||
('transaction.selected', 'ansiblack bg:ansiwhite'),
|
||||
('transaction.default', 'fg:ansiwhite'),
|
||||
('transaction.unsupported', 'fg:ansibrightblack'),
|
||||
('transaction.error', 'fg:ansired'),
|
||||
('transaction.no_aidl', 'fg:ansiwhite'),
|
||||
('transaction.oneway', 'fg:ansimagenta'),
|
||||
('transaction.request', 'fg:ansicyan'),
|
||||
('transaction.response', 'fg:ansiyellow'),
|
||||
('hexdump.default', 'fg:ansiwhite'),
|
||||
('hexdump.selected', 'fg:ansiblack bg:ansiwhite'),
|
||||
('toolbar', 'bg:ansigreen'),
|
||||
('toolbar.text', 'fg:ansiblack'),
|
||||
('dialog', 'fg:ansiblack bg:ansiwhite'),
|
||||
('dialog frame.border', 'fg:ansiblack bg:ansiwhite'),
|
||||
('dialog frame.label', 'fg:ansiblack bg:ansiwhite'),
|
||||
('dialogger.textarea', 'fg:ansiwhite bg:ansiblack'),
|
||||
]
|
||||
)
|
||||
|
||||
kb = KeyBindings()
|
||||
|
||||
@ -150,13 +175,11 @@ class DynamicUI:
|
||||
logger.info("Q pressed. App exiting.")
|
||||
event.app.exit(exception=KeyboardInterrupt, style='class:aborting')
|
||||
|
||||
|
||||
@kb.add('h', filter=~modal_panel_visible | show_help)
|
||||
@kb.add("enter", filter=show_help)
|
||||
def _(event):
|
||||
self.help_panel.visible = not self.help_panel.visible
|
||||
|
||||
|
||||
@kb.add('f', filter=~modal_panel_visible)
|
||||
@kb.add("enter", filter=show_filters)
|
||||
def _(event):
|
||||
@ -165,7 +188,9 @@ class DynamicUI:
|
||||
get_app().layout.focus(self.filter_panel.interface_textarea)
|
||||
else:
|
||||
self.filter = self.filter_panel.filter()
|
||||
self.transactions.assign([t for t in self.all_transactions if self.filter.passes(t)])
|
||||
self.transactions.assign(
|
||||
[t for t in self.all_transactions if self.filter.passes(t)]
|
||||
)
|
||||
get_app().layout.focus(dummy_control)
|
||||
|
||||
@kb.add("c-c")
|
||||
@ -173,17 +198,18 @@ class DynamicUI:
|
||||
active_frame = self.focusable[self.focus_index]
|
||||
active_frame.copy_to_clipboard()
|
||||
|
||||
|
||||
app = Application(
|
||||
layout,
|
||||
key_bindings=merge_key_bindings([
|
||||
kb,
|
||||
self.transaction_table.key_bindings(),
|
||||
#self.structure_pane.key_bindings(),
|
||||
#self.hexdump_pane.key_bindings()
|
||||
]),
|
||||
key_bindings=merge_key_bindings(
|
||||
[
|
||||
kb,
|
||||
self.transaction_table.key_bindings(),
|
||||
# self.structure_pane.key_bindings(),
|
||||
# self.hexdump_pane.key_bindings()
|
||||
]
|
||||
),
|
||||
full_screen=True,
|
||||
style=style
|
||||
style=style,
|
||||
)
|
||||
app.before_render += self.check_resize
|
||||
|
||||
@ -210,11 +236,13 @@ class DynamicUI:
|
||||
lower_panels_height = available_height // 2
|
||||
|
||||
logger.debug(f"New terminal dimension: {dimensions}")
|
||||
logger.debug(f"{border_allowance=}, {transactions_height=}, {lower_panels_height=}, total={border_allowance+transactions_height+lower_panels_height}")
|
||||
logger.debug(
|
||||
f"{border_allowance=}, {transactions_height=}, {lower_panels_height=}, total={border_allowance+transactions_height+lower_panels_height}"
|
||||
)
|
||||
|
||||
self.transaction_table.resize(transactions_height)
|
||||
#self.structure_pane.max_height = lower_panels_height
|
||||
#self.hexdump_pane.max_lines = lower_panels_height
|
||||
# self.structure_pane.max_height = lower_panels_height
|
||||
# self.hexdump_pane.max_lines = lower_panels_height
|
||||
|
||||
def get_available_blocks(self):
|
||||
blocks: list[Message] = []
|
||||
|
@ -1,26 +1,26 @@
|
||||
|
||||
import datetime
|
||||
|
||||
from androguard.pentest import Message, MessageEvent, MessageSystem
|
||||
|
||||
|
||||
class DisplayTransaction:
|
||||
|
||||
def __init__(self, block: Message) -> None:
|
||||
self.block: Message = block
|
||||
self.timestamp = datetime.datetime.now().strftime('%H:%M:%S'),
|
||||
self.timestamp = (datetime.datetime.now().strftime('%H:%M:%S'),)
|
||||
|
||||
@property
|
||||
def index(self) -> int:
|
||||
return self.block.index
|
||||
|
||||
|
||||
@property
|
||||
def unsupported_call(self) -> bool:
|
||||
return '' #self.block.unsupported_call
|
||||
return '' # self.block.unsupported_call
|
||||
|
||||
@property
|
||||
def to_method(self) -> str:
|
||||
return self.block.to_method
|
||||
|
||||
|
||||
@property
|
||||
def from_method(self) -> str:
|
||||
return self.block.from_method
|
||||
@ -34,13 +34,13 @@ class DisplayTransaction:
|
||||
return self.block.ret_value
|
||||
|
||||
@property
|
||||
def fields(self): #-> Field | None:
|
||||
return None #self.block.root_field
|
||||
def fields(self): # -> Field | None:
|
||||
return None # self.block.root_field
|
||||
|
||||
@property
|
||||
def direction_indicator(self) -> str:
|
||||
return '\u21D0'
|
||||
|
||||
|
||||
if self.block.direction == Direction.IN:
|
||||
return '\u21D0' if self.block.oneway else '\u21D2'
|
||||
elif self.block.direction == Direction.OUT:
|
||||
@ -48,7 +48,6 @@ class DisplayTransaction:
|
||||
else:
|
||||
return ''
|
||||
|
||||
|
||||
def style(self) -> str:
|
||||
if type(self.block) is MessageEvent:
|
||||
style = "class:transaction.oneway"
|
||||
@ -65,7 +64,11 @@ class DisplayTransaction:
|
||||
elif self.block.unsupported_call:
|
||||
style = "class:transaction.no_aidl"
|
||||
elif self.block.direction == Direction.IN:
|
||||
style = "class:transaction.oneway" if self.block.oneway else "class:transaction.request"
|
||||
style = (
|
||||
"class:transaction.oneway"
|
||||
if self.block.oneway
|
||||
else "class:transaction.request"
|
||||
)
|
||||
elif self.block.direction == Direction.OUT:
|
||||
style = "class:transaction.response"
|
||||
else:
|
||||
@ -75,7 +78,7 @@ class DisplayTransaction:
|
||||
def type(self) -> str:
|
||||
"""Gets the type of the Block as a simple short string for use in pattern matching"""
|
||||
return "oneway"
|
||||
|
||||
|
||||
if self.unsupported_call:
|
||||
type_name = "unsupported type"
|
||||
elif self.block.errors:
|
||||
@ -89,4 +92,3 @@ class DisplayTransaction:
|
||||
type_name = "unknown" # Should be impossible
|
||||
|
||||
return type_name
|
||||
|
||||
|
@ -16,11 +16,13 @@ class Filter:
|
||||
interface: Optional[str] = None,
|
||||
method: Optional[str] = None,
|
||||
types: Optional[list[str]] = None,
|
||||
include: bool = True
|
||||
include: bool = True,
|
||||
):
|
||||
self.interface = interface
|
||||
self.method = method
|
||||
self.types = types or [] # List of associated types of the filter (call, return, etc)
|
||||
self.types = (
|
||||
types or []
|
||||
) # List of associated types of the filter (call, return, etc)
|
||||
self.inclusive = include
|
||||
|
||||
def passes(self):
|
||||
@ -34,12 +36,12 @@ class Filter:
|
||||
The code checks if the filter passes the checks, and then tailors the output to the filter_mode
|
||||
The type is either Inclusive ("Incl") or Exclusive ("Excl")
|
||||
"""
|
||||
#matches = (
|
||||
# matches = (
|
||||
# (not self.types or block.type() in self.types) and
|
||||
# (not self.interface or self.interface in block.interface) and
|
||||
# (not self.method or self.method in block.method)
|
||||
#)
|
||||
#return not matches ^ self.inclusive
|
||||
# )
|
||||
# return not matches ^ self.inclusive
|
||||
return False
|
||||
|
||||
def toggle_inclusivity(self):
|
||||
@ -52,6 +54,7 @@ class Filter:
|
||||
|
||||
return f"interface={interface}, method={method}, types={types}"
|
||||
|
||||
|
||||
_T = TypeVar('_T', bound=Filter)
|
||||
|
||||
|
||||
|
@ -15,8 +15,10 @@ class View:
|
||||
def size(self):
|
||||
return self.end - self.start
|
||||
|
||||
|
||||
_T = TypeVar('_T')
|
||||
|
||||
|
||||
class SelectionViewList(UserList[_T]):
|
||||
|
||||
def __init__(self, iterable=None, max_view_size=80, view_padding=5):
|
||||
@ -53,7 +55,7 @@ class SelectionViewList(UserList[_T]):
|
||||
return self.data[self.selection]
|
||||
|
||||
def view_slice(self):
|
||||
return self.data[self.view.start:self.view.end]
|
||||
return self.data[self.view.start : self.view.end]
|
||||
|
||||
def resize_view(self, view_size):
|
||||
if self.selection_valid():
|
||||
@ -81,7 +83,9 @@ class SelectionViewList(UserList[_T]):
|
||||
|
||||
# We're adding a negative here so although it looks a bit odd we are moving the view backwards
|
||||
self.view.start = max(self.view.start + step, 0)
|
||||
self.view.end = min(len(self), self.view.start + self.max_view_size)
|
||||
self.view.end = min(
|
||||
len(self), self.view.start + self.max_view_size
|
||||
)
|
||||
self.on_update_event()
|
||||
|
||||
def __delitem__(self, i: int):
|
||||
@ -89,7 +93,9 @@ class SelectionViewList(UserList[_T]):
|
||||
self._delete_from_view(i)
|
||||
|
||||
def _expand_view(self):
|
||||
self.view.end = self.view.start + min(self.max_view_size, len(self) - self.view.start)
|
||||
self.view.end = self.view.start + min(
|
||||
self.max_view_size, len(self) - self.view.start
|
||||
)
|
||||
if not self.selection_valid():
|
||||
self.selection = 0
|
||||
self.on_selection_change()
|
||||
@ -104,16 +110,16 @@ class SelectionViewList(UserList[_T]):
|
||||
self.move_selection(-1)
|
||||
|
||||
self.on_update_event()
|
||||
# TODO: once the selection is within padding of the start of the window it shoud move up
|
||||
# if i <= self.view.end:
|
||||
# self.view.end = min(self.view.end, len(self))
|
||||
# if i < self.selection:
|
||||
# self.selection -= 1
|
||||
# elif i == self.selection:
|
||||
# self.selection = min(self.selection, len(self))
|
||||
# if i <= self.view.start:
|
||||
# self.view.start = max(0, self.view.start - 1)
|
||||
# self.view.end -= 1
|
||||
# TODO: once the selection is within padding of the start of the window it shoud move up
|
||||
# if i <= self.view.end:
|
||||
# self.view.end = min(self.view.end, len(self))
|
||||
# if i < self.selection:
|
||||
# self.selection -= 1
|
||||
# elif i == self.selection:
|
||||
# self.selection = min(self.selection, len(self))
|
||||
# if i <= self.view.start:
|
||||
# self.view.start = max(0, self.view.start - 1)
|
||||
# self.view.end -= 1
|
||||
|
||||
def append(self, item: _T):
|
||||
super().append(item)
|
||||
@ -156,4 +162,3 @@ class SelectionViewList(UserList[_T]):
|
||||
self.clear()
|
||||
self.data += items
|
||||
self._reset_view()
|
||||
|
||||
|
@ -1,18 +1,32 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from typing import Type, Union
|
||||
|
||||
from prompt_toolkit import Application
|
||||
from prompt_toolkit.layout.layout import Layout
|
||||
from prompt_toolkit.layout.dimension import Dimension as D, sum_layout_dimensions, max_layout_dimensions, to_dimension
|
||||
from prompt_toolkit.widgets import Box, TextArea, Label, Button
|
||||
from prompt_toolkit.cache import SimpleCache
|
||||
# from prompt_toolkit.widgets.base import Border
|
||||
from prompt_toolkit.layout.containers import Window, VSplit, HSplit, HorizontalAlign, VerticalAlign
|
||||
from prompt_toolkit.key_binding.key_bindings import KeyBindings
|
||||
|
||||
# from prompt_toolkit.widgets.base import Border
|
||||
from prompt_toolkit.layout.containers import (
|
||||
HorizontalAlign,
|
||||
HSplit,
|
||||
VerticalAlign,
|
||||
VSplit,
|
||||
Window,
|
||||
)
|
||||
from prompt_toolkit.layout.dimension import Dimension as D
|
||||
from prompt_toolkit.layout.dimension import (
|
||||
max_layout_dimensions,
|
||||
sum_layout_dimensions,
|
||||
to_dimension,
|
||||
)
|
||||
from prompt_toolkit.layout.layout import Layout
|
||||
from prompt_toolkit.utils import take_using_weights
|
||||
from prompt_toolkit.widgets import Box, Button, Label, TextArea
|
||||
|
||||
from androguard.ui.selection import SelectionViewList
|
||||
|
||||
|
||||
class EmptyBorder:
|
||||
HORIZONTAL = ''
|
||||
VERTICAL = ''
|
||||
@ -31,7 +45,7 @@ class EmptyBorder:
|
||||
|
||||
|
||||
class SpaceBorder:
|
||||
" Box drawing characters. (Spaces) "
|
||||
"Box drawing characters. (Spaces)"
|
||||
HORIZONTAL = ' '
|
||||
VERTICAL = ' '
|
||||
|
||||
@ -49,7 +63,7 @@ class SpaceBorder:
|
||||
|
||||
|
||||
class AsciiBorder:
|
||||
" Box drawing characters. (ASCII) "
|
||||
"Box drawing characters. (ASCII)"
|
||||
HORIZONTAL = '-'
|
||||
VERTICAL = '|'
|
||||
|
||||
@ -67,7 +81,7 @@ class AsciiBorder:
|
||||
|
||||
|
||||
class ThinBorder:
|
||||
" Box drawing characters. (Thin) "
|
||||
"Box drawing characters. (Thin)"
|
||||
HORIZONTAL = '\u2500'
|
||||
VERTICAL = '\u2502'
|
||||
|
||||
@ -85,7 +99,7 @@ class ThinBorder:
|
||||
|
||||
|
||||
class RoundedBorder(ThinBorder):
|
||||
" Box drawing characters. (Rounded) "
|
||||
"Box drawing characters. (Rounded)"
|
||||
TOP_LEFT = '\u256d'
|
||||
TOP_RIGHT = '\u256e'
|
||||
BOTTOM_LEFT = '\u2570'
|
||||
@ -93,7 +107,7 @@ class RoundedBorder(ThinBorder):
|
||||
|
||||
|
||||
class ThickBorder:
|
||||
" Box drawing characters. (Thick) "
|
||||
"Box drawing characters. (Thick)"
|
||||
HORIZONTAL = '\u2501'
|
||||
VERTICAL = '\u2503'
|
||||
|
||||
@ -111,7 +125,7 @@ class ThickBorder:
|
||||
|
||||
|
||||
class DoubleBorder:
|
||||
" Box drawing characters. (Thin) "
|
||||
"Box drawing characters. (Thin)"
|
||||
HORIZONTAL = '\u2550'
|
||||
VERTICAL = '\u2551'
|
||||
|
||||
@ -127,6 +141,7 @@ class DoubleBorder:
|
||||
|
||||
INTERSECT = '\u256c'
|
||||
|
||||
|
||||
AnyBorderStyle = Union[
|
||||
Type[EmptyBorder],
|
||||
Type[SpaceBorder],
|
||||
@ -150,12 +165,25 @@ class Merge:
|
||||
|
||||
class Table(HSplit):
|
||||
|
||||
def __init__(self, table,
|
||||
borders: AnyBorderStyle=ThinBorder, column_width=None, column_widths=[],
|
||||
window_too_small=None, align=VerticalAlign.JUSTIFY,
|
||||
padding=0, padding_char=None, padding_style='',
|
||||
width=None, height=None, z_index=None,
|
||||
modal=False, key_bindings=None, style='', selected_style=''):
|
||||
def __init__(
|
||||
self,
|
||||
table,
|
||||
borders: AnyBorderStyle = ThinBorder,
|
||||
column_width=None,
|
||||
column_widths=[],
|
||||
window_too_small=None,
|
||||
align=VerticalAlign.JUSTIFY,
|
||||
padding=0,
|
||||
padding_char=None,
|
||||
padding_style='',
|
||||
width=None,
|
||||
height=None,
|
||||
z_index=None,
|
||||
modal=False,
|
||||
key_bindings=None,
|
||||
style='',
|
||||
selected_style='',
|
||||
):
|
||||
self.borders = borders
|
||||
self.column_width = column_width
|
||||
self.column_widths = column_widths
|
||||
@ -165,7 +193,10 @@ class Table(HSplit):
|
||||
if not isinstance(table, list):
|
||||
table = [table]
|
||||
|
||||
children = [_Row(row=row, table=self, borders=borders, height=1) for row in table]
|
||||
children = [
|
||||
_Row(row=row, table=self, borders=borders, height=1)
|
||||
for row in table
|
||||
]
|
||||
|
||||
super().__init__(
|
||||
children=children,
|
||||
@ -179,7 +210,7 @@ class Table(HSplit):
|
||||
z_index=z_index,
|
||||
modal=modal,
|
||||
key_bindings=key_bindings,
|
||||
style=style
|
||||
style=style,
|
||||
)
|
||||
self.row_cache = SimpleCache(maxsize=30)
|
||||
|
||||
@ -188,7 +219,16 @@ class Table(HSplit):
|
||||
# self.children.extend(_Row(row=row, table=self, borders=self.borders, height=1, style="#000000 bg:#ffffff") for row in rows)
|
||||
|
||||
def add_row(self, row, style, cache_id):
|
||||
r = self.row_cache.get(cache_id, lambda: _Row(row=row, table=self, borders=self.borders, height=1, style=style))
|
||||
r = self.row_cache.get(
|
||||
cache_id,
|
||||
lambda: _Row(
|
||||
row=row,
|
||||
table=self,
|
||||
borders=self.borders,
|
||||
height=1,
|
||||
style=style,
|
||||
),
|
||||
)
|
||||
self.children.append(r)
|
||||
|
||||
@property
|
||||
@ -200,6 +240,7 @@ class Table(HSplit):
|
||||
"""
|
||||
List of child objects, including padding & borders.
|
||||
"""
|
||||
|
||||
def get():
|
||||
result = []
|
||||
|
||||
@ -306,7 +347,8 @@ class _BaseRow(VSplit):
|
||||
|
||||
child_generator = take_using_weights(
|
||||
items=list(range(len(dimensions))),
|
||||
weights=[d.weight for d in dimensions])
|
||||
weights=[d.weight for d in dimensions],
|
||||
)
|
||||
|
||||
i = next(child_generator)
|
||||
|
||||
@ -334,7 +376,7 @@ class _BaseRow(VSplit):
|
||||
for c in children:
|
||||
if isinstance(c, _Cell):
|
||||
inc = (c.merge * 2) - 1
|
||||
tmp.append(sum(sizes[i:i + inc]))
|
||||
tmp.append(sum(sizes[i : i + inc]))
|
||||
else:
|
||||
inc = 1
|
||||
tmp.append(sizes[i])
|
||||
@ -345,11 +387,23 @@ class _BaseRow(VSplit):
|
||||
|
||||
|
||||
class _Row(_BaseRow):
|
||||
def __init__(self, row, table, borders,
|
||||
window_too_small=None, align=HorizontalAlign.JUSTIFY,
|
||||
padding=D.exact(0), padding_char=None, padding_style='',
|
||||
width=None, height=None, z_index=None,
|
||||
modal=False, key_bindings=None, style=''):
|
||||
def __init__(
|
||||
self,
|
||||
row,
|
||||
table,
|
||||
borders,
|
||||
window_too_small=None,
|
||||
align=HorizontalAlign.JUSTIFY,
|
||||
padding=D.exact(0),
|
||||
padding_char=None,
|
||||
padding_style='',
|
||||
width=None,
|
||||
height=None,
|
||||
z_index=None,
|
||||
modal=False,
|
||||
key_bindings=None,
|
||||
style='',
|
||||
):
|
||||
self.table = table
|
||||
self.borders = borders
|
||||
|
||||
@ -377,7 +431,8 @@ class _Row(_BaseRow):
|
||||
z_index=z_index,
|
||||
modal=modal,
|
||||
key_bindings=key_bindings,
|
||||
style=style)
|
||||
style=style,
|
||||
)
|
||||
|
||||
@property
|
||||
def raw_columns(self):
|
||||
@ -388,6 +443,7 @@ class _Row(_BaseRow):
|
||||
"""
|
||||
List of child objects, including padding & borders.
|
||||
"""
|
||||
|
||||
def get():
|
||||
result = []
|
||||
|
||||
@ -421,11 +477,24 @@ class _Row(_BaseRow):
|
||||
|
||||
|
||||
class _Border(_BaseRow):
|
||||
def __init__(self, prev, next, table, borders,
|
||||
window_too_small=None, align=HorizontalAlign.JUSTIFY,
|
||||
padding=D.exact(0), padding_char=None, padding_style='',
|
||||
width=None, height=None, z_index=None,
|
||||
modal=False, key_bindings=None, style=''):
|
||||
def __init__(
|
||||
self,
|
||||
prev,
|
||||
next,
|
||||
table,
|
||||
borders,
|
||||
window_too_small=None,
|
||||
align=HorizontalAlign.JUSTIFY,
|
||||
padding=D.exact(0),
|
||||
padding_char=None,
|
||||
padding_style='',
|
||||
width=None,
|
||||
height=None,
|
||||
z_index=None,
|
||||
modal=False,
|
||||
key_bindings=None,
|
||||
style='',
|
||||
):
|
||||
assert prev or next
|
||||
self.prev = prev
|
||||
self.next = next
|
||||
@ -446,7 +515,8 @@ class _Border(_BaseRow):
|
||||
z_index=z_index,
|
||||
modal=modal,
|
||||
key_bindings=key_bindings,
|
||||
style=style)
|
||||
style=style,
|
||||
)
|
||||
|
||||
def has_borders(self, row):
|
||||
yield None # first (outer) border
|
||||
@ -470,6 +540,7 @@ class _Border(_BaseRow):
|
||||
"""
|
||||
List of child objects, including padding & borders.
|
||||
"""
|
||||
|
||||
def get():
|
||||
result = []
|
||||
|
||||
@ -525,13 +596,26 @@ class _Border(_BaseRow):
|
||||
|
||||
|
||||
class _Cell(HSplit):
|
||||
def __init__(self, cell, table, row, merge=1,
|
||||
padding=0, char=None,
|
||||
padding_left=None, padding_right=None,
|
||||
padding_top=None, padding_bottom=None,
|
||||
window_too_small=None,
|
||||
width=None, height=None, z_index=None,
|
||||
modal=False, key_bindings=None, style=''):
|
||||
def __init__(
|
||||
self,
|
||||
cell,
|
||||
table,
|
||||
row,
|
||||
merge=1,
|
||||
padding=0,
|
||||
char=None,
|
||||
padding_left=None,
|
||||
padding_right=None,
|
||||
padding_top=None,
|
||||
padding_bottom=None,
|
||||
window_too_small=None,
|
||||
width=None,
|
||||
height=None,
|
||||
z_index=None,
|
||||
modal=False,
|
||||
key_bindings=None,
|
||||
style='',
|
||||
):
|
||||
self.table = table
|
||||
self.row = row
|
||||
self.merge = merge
|
||||
@ -569,7 +653,8 @@ class _Cell(HSplit):
|
||||
z_index=z_index,
|
||||
modal=modal,
|
||||
key_bindings=key_bindings,
|
||||
style=style)
|
||||
style=style,
|
||||
)
|
||||
|
||||
|
||||
def demo():
|
||||
@ -589,9 +674,10 @@ def demo():
|
||||
sht3 = "The quick brown fox jumps over the lazy dog."
|
||||
|
||||
kb = KeyBindings()
|
||||
|
||||
@kb.add('c-c')
|
||||
def _(event):
|
||||
" Abort when Control-C has been pressed. "
|
||||
"Abort when Control-C has been pressed."
|
||||
event.app.exit(exception=KeyboardInterrupt, style='class:aborting')
|
||||
|
||||
table = [
|
||||
@ -610,7 +696,8 @@ def demo():
|
||||
table=table,
|
||||
column_width=D.exact(15),
|
||||
column_widths=[None],
|
||||
borders=DoubleBorder),
|
||||
borders=DoubleBorder,
|
||||
),
|
||||
padding=1,
|
||||
),
|
||||
)
|
||||
@ -618,4 +705,4 @@ def demo():
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
demo().run()
|
||||
demo().run()
|
||||
|
@ -1,3 +1,3 @@
|
||||
def clamp(range_min: int, range_max: int, value: int) -> int:
|
||||
"""Return value if its is within range_min and range_max else return the nearest bound"""
|
||||
return max(min(range_max, value), range_min)
|
||||
return max(min(range_max, value), range_min)
|
||||
|
@ -1,19 +1,27 @@
|
||||
import json
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from prompt_toolkit.filters import Condition
|
||||
from prompt_toolkit.formatted_text import FormattedText
|
||||
from prompt_toolkit.key_binding import KeyBindings
|
||||
from prompt_toolkit.layout import AnyContainer, Dimension, HSplit, FormattedTextControl, Window
|
||||
from prompt_toolkit.layout import (
|
||||
AnyContainer,
|
||||
Dimension,
|
||||
FormattedTextControl,
|
||||
HSplit,
|
||||
Window,
|
||||
)
|
||||
from prompt_toolkit.layout.dimension import AnyDimension
|
||||
|
||||
from androguard.ui.selection import SelectionViewList
|
||||
from androguard.ui.data_types import DisplayTransaction
|
||||
from androguard.ui.selection import SelectionViewList
|
||||
from androguard.ui.widget.frame import SelectableFrame
|
||||
|
||||
|
||||
class DetailsFrame:
|
||||
def __init__(self, transactions: SelectionViewList, max_lines: int) -> None:
|
||||
def __init__(
|
||||
self, transactions: SelectionViewList, max_lines: int
|
||||
) -> None:
|
||||
self.transactions = transactions
|
||||
self.max_lines = max_lines
|
||||
|
||||
@ -25,7 +33,7 @@ class DetailsFrame:
|
||||
title="Details",
|
||||
body=self.get_content,
|
||||
width=Dimension(min=56, preferred=100, max=100),
|
||||
height=Dimension(preferred=max_lines)
|
||||
height=Dimension(preferred=max_lines),
|
||||
)
|
||||
|
||||
@property
|
||||
@ -47,15 +55,19 @@ class DetailsFrame:
|
||||
ignore_content_height=True,
|
||||
content=FormattedTextControl(
|
||||
text=self.get_current_details()
|
||||
)
|
||||
),
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def get_current_details(self):
|
||||
if self.transactions.selection_valid():
|
||||
return json.dumps(self.transactions.selected().params, indent=2) + '\n' + json.dumps(self.transactions.selected().ret_value, indent=2)
|
||||
return (
|
||||
json.dumps(self.transactions.selected().params, indent=2)
|
||||
+ '\n'
|
||||
+ json.dumps(self.transactions.selected().ret_value, indent=2)
|
||||
)
|
||||
return ''
|
||||
|
||||
def __pt_container__(self) -> AnyContainer:
|
||||
return self.container
|
||||
return self.container
|
||||
|
@ -1,7 +1,10 @@
|
||||
from prompt_toolkit.key_binding import KeyBindings
|
||||
from prompt_toolkit.key_binding.bindings.focus import (
|
||||
focus_next,
|
||||
focus_previous,
|
||||
)
|
||||
from prompt_toolkit.layout import AnyContainer, HSplit, VerticalAlign, VSplit
|
||||
from prompt_toolkit.key_binding.bindings.focus import focus_next, focus_previous
|
||||
from prompt_toolkit.widgets import Frame, CheckboxList, Box, TextArea, Label
|
||||
from prompt_toolkit.widgets import Box, CheckboxList, Frame, Label, TextArea
|
||||
|
||||
from androguard.ui.filter import Filter
|
||||
|
||||
@ -9,23 +12,28 @@ from androguard.ui.filter import Filter
|
||||
class TypeCheckboxlist(CheckboxList):
|
||||
|
||||
def __init__(self) -> None:
|
||||
values=[
|
||||
("call", "call"),
|
||||
("return", "return"),
|
||||
("oneway", "oneway"),
|
||||
("error", "error"),
|
||||
("unknown", "unknown"),
|
||||
values = [
|
||||
("call", "call"),
|
||||
("return", "return"),
|
||||
("oneway", "oneway"),
|
||||
("error", "error"),
|
||||
("unknown", "unknown"),
|
||||
]
|
||||
super().__init__(values)
|
||||
self.show_scrollbar = False
|
||||
|
||||
|
||||
class FiltersPanel:
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.visible = False
|
||||
|
||||
self.interface_textarea = TextArea(multiline=False, style="class:dialogger.textarea")
|
||||
self.method_textarea = TextArea(multiline=False, style="class:dialogger.textarea")
|
||||
self.interface_textarea = TextArea(
|
||||
multiline=False, style="class:dialogger.textarea"
|
||||
)
|
||||
self.method_textarea = TextArea(
|
||||
multiline=False, style="class:dialogger.textarea"
|
||||
)
|
||||
|
||||
self.type_filter_checkboxes = TypeCheckboxlist()
|
||||
|
||||
@ -38,20 +46,26 @@ class FiltersPanel:
|
||||
width=50,
|
||||
align=VerticalAlign.TOP,
|
||||
children=[
|
||||
VSplit(children=[
|
||||
Label("Interface", width=10),
|
||||
self.interface_textarea
|
||||
]),
|
||||
VSplit(children=[
|
||||
Label("Method", width=10),
|
||||
self.method_textarea
|
||||
]),
|
||||
VSplit(children=[
|
||||
Label("Type", width=10, dont_extend_height=False),
|
||||
self.type_filter_checkboxes,
|
||||
]),
|
||||
]
|
||||
)
|
||||
VSplit(
|
||||
children=[
|
||||
Label("Interface", width=10),
|
||||
self.interface_textarea,
|
||||
]
|
||||
),
|
||||
VSplit(
|
||||
children=[
|
||||
Label("Method", width=10),
|
||||
self.method_textarea,
|
||||
]
|
||||
),
|
||||
VSplit(
|
||||
children=[
|
||||
Label("Type", width=10, dont_extend_height=False),
|
||||
self.type_filter_checkboxes,
|
||||
]
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
kb = KeyBindings()
|
||||
@ -64,7 +78,7 @@ class FiltersPanel:
|
||||
body=float_frame,
|
||||
style="class:dialogger.background",
|
||||
modal=True,
|
||||
key_bindings=kb
|
||||
key_bindings=kb,
|
||||
)
|
||||
|
||||
def filter(self) -> Filter:
|
||||
@ -72,7 +86,8 @@ class FiltersPanel:
|
||||
return Filter(
|
||||
self.interface_textarea.text,
|
||||
self.method_textarea.text,
|
||||
self.type_filter_checkboxes.current_values)
|
||||
self.type_filter_checkboxes.current_values,
|
||||
)
|
||||
|
||||
def __pt_container__(self) -> AnyContainer:
|
||||
return self.container
|
||||
return self.container
|
||||
|
@ -1,5 +1,3 @@
|
||||
|
||||
|
||||
from functools import partial
|
||||
from typing import Optional
|
||||
|
||||
@ -14,10 +12,11 @@ from prompt_toolkit.layout import (
|
||||
DynamicContainer,
|
||||
HSplit,
|
||||
VSplit,
|
||||
Window
|
||||
Window,
|
||||
)
|
||||
from prompt_toolkit.widgets.base import Border, Label
|
||||
|
||||
|
||||
class SelectableFrame:
|
||||
"""
|
||||
Draw a border around any container, optionally with a title text.
|
||||
@ -88,8 +87,12 @@ class SelectableFrame:
|
||||
|
||||
self.container = HSplit(
|
||||
[
|
||||
ConditionalContainer(content=top_row_with_title, filter=has_title),
|
||||
ConditionalContainer(content=top_row_without_title, filter=~has_title),
|
||||
ConditionalContainer(
|
||||
content=top_row_with_title, filter=has_title
|
||||
),
|
||||
ConditionalContainer(
|
||||
content=top_row_without_title, filter=~has_title
|
||||
),
|
||||
VSplit(
|
||||
[
|
||||
fill(width=1, char=Border.VERTICAL),
|
||||
|
@ -1,17 +1,18 @@
|
||||
from prompt_toolkit.layout import AnyContainer, HSplit
|
||||
from prompt_toolkit.widgets import Frame, Box, Label
|
||||
from prompt_toolkit.widgets import Box, Frame, Label
|
||||
|
||||
|
||||
class HelpPanel:
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.visible = False
|
||||
|
||||
|
||||
float_frame = Box(
|
||||
padding_top=1,
|
||||
padding_left=2,
|
||||
padding_right=2,
|
||||
body=HSplit(children=[
|
||||
body=HSplit(
|
||||
children=[
|
||||
Label("up Move up"),
|
||||
Label("down Move down"),
|
||||
Label("shift + up Page up"),
|
||||
@ -24,7 +25,8 @@ class HelpPanel:
|
||||
Label("f Open filter options"),
|
||||
Label("h Help"),
|
||||
Label("q Quit"),
|
||||
]),
|
||||
]
|
||||
),
|
||||
)
|
||||
|
||||
self.container = Frame(
|
||||
@ -35,4 +37,4 @@ class HelpPanel:
|
||||
)
|
||||
|
||||
def __pt_container__(self) -> AnyContainer:
|
||||
return self.container
|
||||
return self.container
|
||||
|
@ -1,11 +1,12 @@
|
||||
|
||||
from typing import Sequence
|
||||
|
||||
from prompt_toolkit.formatted_text import AnyFormattedText, FormattedText
|
||||
from prompt_toolkit.layout.containers import AnyContainer, DynamicContainer
|
||||
from prompt_toolkit.widgets import FormattedTextToolbar
|
||||
|
||||
from androguard.ui.widget.filters import FiltersPanel
|
||||
|
||||
|
||||
class StatusToolbar:
|
||||
|
||||
def __init__(self, transactions: Sequence, filters: FiltersPanel) -> None:
|
||||
@ -13,7 +14,6 @@ class StatusToolbar:
|
||||
self.filters = filters
|
||||
self.container = DynamicContainer(self.toolbar_container)
|
||||
|
||||
|
||||
def toolbar_container(self) -> AnyContainer:
|
||||
return FormattedTextToolbar(
|
||||
text=self.toolbar_text(),
|
||||
@ -21,9 +21,14 @@ class StatusToolbar:
|
||||
)
|
||||
|
||||
def toolbar_text(self) -> AnyFormattedText:
|
||||
return FormattedText([
|
||||
("class:toolbar.text", f"Transactions: {len(self.transactions)}, Filter: {self.filters.filter()}")
|
||||
])
|
||||
return FormattedText(
|
||||
[
|
||||
(
|
||||
"class:toolbar.text",
|
||||
f"Transactions: {len(self.transactions)}, Filter: {self.filters.filter()}",
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
def __pt_container__(self) -> AnyContainer:
|
||||
return self.container
|
||||
return self.container
|
||||
|
@ -1,21 +1,25 @@
|
||||
import csv
|
||||
import io
|
||||
|
||||
#import pyperclip
|
||||
|
||||
from prompt_toolkit.filters import Condition
|
||||
from prompt_toolkit.key_binding import KeyBindings
|
||||
from prompt_toolkit.layout.dimension import AnyDimension, Dimension
|
||||
from prompt_toolkit.layout import AnyContainer
|
||||
from prompt_toolkit.layout.dimension import AnyDimension, Dimension
|
||||
|
||||
from androguard.ui import table
|
||||
from androguard.ui.selection import SelectionViewList
|
||||
from androguard.ui.widget.frame import SelectableFrame
|
||||
|
||||
# import pyperclip
|
||||
|
||||
|
||||
|
||||
|
||||
class TransactionFrame:
|
||||
|
||||
def __init__(self, transactions: SelectionViewList, height: AnyDimension = None) -> None:
|
||||
def __init__(
|
||||
self, transactions: SelectionViewList, height: AnyDimension = None
|
||||
) -> None:
|
||||
self.transactions = transactions
|
||||
self.transactions.on_update_event += self.update_table
|
||||
|
||||
@ -56,18 +60,28 @@ class TransactionFrame:
|
||||
|
||||
def update_table(self, _):
|
||||
self.table.children.clear()
|
||||
self.table.add_row(self.headings, "class:transactions.heading", id(self.headings))
|
||||
for i in range(self.transactions.view.start, self.transactions.view.end):
|
||||
self.table.add_row(
|
||||
self.headings, "class:transactions.heading", id(self.headings)
|
||||
)
|
||||
for i in range(
|
||||
self.transactions.view.start, self.transactions.view.end
|
||||
):
|
||||
row, style = self._to_row(self.transactions[i])
|
||||
self.table.add_row(
|
||||
row,
|
||||
f"{style} reverse" if i == self.transactions.selection else style,
|
||||
(id(self.transactions[i]), i == self.transactions.selection)
|
||||
(
|
||||
f"{style} reverse"
|
||||
if i == self.transactions.selection
|
||||
else style
|
||||
),
|
||||
(id(self.transactions[i]), i == self.transactions.selection),
|
||||
)
|
||||
self.pad_table()
|
||||
|
||||
def pad_table(self):
|
||||
padding = self.transactions.max_view_size - self.transactions.view.size()
|
||||
padding = (
|
||||
self.transactions.max_view_size - self.transactions.view.size()
|
||||
)
|
||||
empty_row = [
|
||||
table.Label(""),
|
||||
table.Label(""),
|
||||
@ -76,8 +90,9 @@ class TransactionFrame:
|
||||
table.Label(""),
|
||||
]
|
||||
for _ in range(padding):
|
||||
self.table.add_row(empty_row, "class:transaction.default", id(empty_row))
|
||||
|
||||
self.table.add_row(
|
||||
empty_row, "class:transaction.default", id(empty_row)
|
||||
)
|
||||
|
||||
def key_bindings(self) -> KeyBindings:
|
||||
kb = KeyBindings()
|
||||
@ -104,7 +119,9 @@ class TransactionFrame:
|
||||
|
||||
@kb.add('end', filter=Condition(lambda: self.activated))
|
||||
def _(event):
|
||||
self.transactions.move_selection(len(self.transactions) - self.transactions.selection)
|
||||
self.transactions.move_selection(
|
||||
len(self.transactions) - self.transactions.selection
|
||||
)
|
||||
|
||||
return kb
|
||||
|
||||
@ -117,8 +134,7 @@ class TransactionFrame:
|
||||
def activated(self, value):
|
||||
self.container.activated = value
|
||||
|
||||
|
||||
#def copy_to_clipboard(self):
|
||||
# def copy_to_clipboard(self):
|
||||
# if self.transactions.selection_valid():
|
||||
# output = io.StringIO()
|
||||
# writer = csv.writer(output, quoting=csv.QUOTE_NONE)
|
||||
@ -131,15 +147,14 @@ class TransactionFrame:
|
||||
# ])
|
||||
# pyperclip.copy(output.getvalue())
|
||||
|
||||
|
||||
def _to_row(self, transaction):
|
||||
# TODO: Cache the rows so we don't need to recreate them.
|
||||
return [
|
||||
table.Label(transaction.direction_indicator),
|
||||
table.Label(str(transaction.index)),
|
||||
table.Label(transaction.from_method),
|
||||
table.Label(transaction.to_method)
|
||||
table.Label(transaction.to_method),
|
||||
], transaction.style()
|
||||
|
||||
def __pt_container__(self) -> AnyContainer:
|
||||
return self.container
|
||||
return self.container
|
||||
|
@ -1,17 +1,18 @@
|
||||
|
||||
import sys
|
||||
from typing import Union, BinaryIO
|
||||
from asn1crypto import keys, x509
|
||||
import hashlib
|
||||
import binascii
|
||||
import hashlib
|
||||
import sys
|
||||
from typing import BinaryIO, Union
|
||||
|
||||
from asn1crypto import keys, x509
|
||||
|
||||
# External dependencies
|
||||
# import asn1crypto
|
||||
from asn1crypto.x509 import Name
|
||||
from loguru import logger
|
||||
|
||||
|
||||
class MyFilter:
|
||||
def __init__(self, level:int) -> None:
|
||||
def __init__(self, level: int) -> None:
|
||||
self.level = level
|
||||
|
||||
def __call__(self, record):
|
||||
@ -19,7 +20,7 @@ class MyFilter:
|
||||
return record["level"].no >= levelno
|
||||
|
||||
|
||||
def set_log(level:int) -> None:
|
||||
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
|
||||
@ -31,7 +32,8 @@ def set_log(level:int) -> None:
|
||||
|
||||
# Stuff that might be useful
|
||||
|
||||
def read_at(buff: BinaryIO, offset:int, size:int=-1) -> bytes:
|
||||
|
||||
def read_at(buff: BinaryIO, offset: int, size: int = -1) -> bytes:
|
||||
idx = buff.tell()
|
||||
buff.seek(offset)
|
||||
d = buff.read(size)
|
||||
@ -39,7 +41,7 @@ def read_at(buff: BinaryIO, offset:int, size:int=-1) -> bytes:
|
||||
return d
|
||||
|
||||
|
||||
def readFile(filename: str, binary:bool=True) -> bytes:
|
||||
def readFile(filename: str, binary: bool = True) -> bytes:
|
||||
"""
|
||||
Open and read a file
|
||||
:param filename: filename to open and read
|
||||
@ -50,7 +52,9 @@ def readFile(filename: str, binary:bool=True) -> bytes:
|
||||
return f.read()
|
||||
|
||||
|
||||
def get_certificate_name_string(name:Union[dict,Name], short:bool=False, delimiter:str=', ') -> str:
|
||||
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.
|
||||
|
||||
@ -64,7 +68,7 @@ def get_certificate_name_string(name:Union[dict,Name], short:bool=False, delimit
|
||||
|
||||
:rtype: str
|
||||
"""
|
||||
if isinstance(name, Name):#asn1crypto.x509.Name):
|
||||
if isinstance(name, Name): # asn1crypto.x509.Name):
|
||||
name = name.native
|
||||
|
||||
# For the shortform, we have a lookup table
|
||||
@ -92,13 +96,24 @@ def get_certificate_name_string(name:Union[dict,Name], short:bool=False, delimit
|
||||
'email_address': ("E", "emailAddress"),
|
||||
'domain_component': ("DC", "domainComponent"),
|
||||
'name_distinguisher': ("nameDistinguisher", "nameDistinguisher"),
|
||||
'organization_identifier': ("organizationIdentifier", "organizationIdentifier"),
|
||||
'organization_identifier': (
|
||||
"organizationIdentifier",
|
||||
"organizationIdentifier",
|
||||
),
|
||||
}
|
||||
return delimiter.join(["{}={}".format(_.get(attr, (attr, attr))[0 if short else 1], name[attr]) for attr in name])
|
||||
return delimiter.join(
|
||||
[
|
||||
"{}={}".format(
|
||||
_.get(attr, (attr, attr))[0 if short else 1], name[attr]
|
||||
)
|
||||
for attr in name
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def parse_public(data):
|
||||
from asn1crypto import pem, keys, x509
|
||||
from asn1crypto import keys, pem, x509
|
||||
|
||||
"""
|
||||
Loads a public key from a DER or PEM-formatted input.
|
||||
Supports RSA, DSA, EC public keys, and X.509 certificates.
|
||||
@ -112,7 +127,9 @@ def parse_public(data):
|
||||
if pem.detect(data):
|
||||
type_name, _, der_bytes = pem.unarmor(data)
|
||||
if type_name in ['PRIVATE KEY', 'RSA PRIVATE KEY']:
|
||||
raise ValueError("The data specified appears to be a private key, not a public key.")
|
||||
raise ValueError(
|
||||
"The data specified appears to be a private key, not a public key."
|
||||
)
|
||||
else:
|
||||
# If not PEM, assume it's DER-encoded
|
||||
der_bytes = data
|
||||
@ -128,7 +145,9 @@ def parse_public(data):
|
||||
# Try to parse the data as an X.509 certificate
|
||||
try:
|
||||
certificate = x509.Certificate.load(der_bytes)
|
||||
public_key_info = certificate['tbs_certificate']['subject_public_key_info']
|
||||
public_key_info = certificate['tbs_certificate'][
|
||||
'subject_public_key_info'
|
||||
]
|
||||
public_key_info.native # Fully parse the object
|
||||
return public_key_info
|
||||
except ValueError:
|
||||
@ -143,7 +162,9 @@ def parse_public(data):
|
||||
except ValueError:
|
||||
pass # Not an RSAPublicKey structure
|
||||
|
||||
raise ValueError("The data specified does not appear to be a known public key or certificate format.")
|
||||
raise ValueError(
|
||||
"The data specified does not appear to be a known public key or certificate format."
|
||||
)
|
||||
|
||||
|
||||
def calculate_fingerprint(key_object):
|
||||
@ -160,7 +181,10 @@ def calculate_fingerprint(key_object):
|
||||
if key_object.algorithm == 'rsa':
|
||||
key = key_object['public_key'].parsed
|
||||
# Prepare string with modulus and public exponent
|
||||
to_hash = '%d:%d' % (key['modulus'].native, key['public_exponent'].native)
|
||||
to_hash = '%d:%d' % (
|
||||
key['modulus'].native,
|
||||
key['public_exponent'].native,
|
||||
)
|
||||
|
||||
# DSA Public Key
|
||||
elif key_object.algorithm == 'dsa':
|
||||
@ -186,4 +210,4 @@ def calculate_fingerprint(key_object):
|
||||
to_hash = to_hash.encode('utf-8')
|
||||
|
||||
# Return the SHA-256 hash of the formatted key data
|
||||
return hashlib.sha256(to_hash).digest()
|
||||
return hashlib.sha256(to_hash).digest()
|
||||
|
@ -7,9 +7,7 @@ license = "Apache Licence, Version 2.0"
|
||||
readme = "README.md"
|
||||
homepage = "https://github.com/androguard/androguard"
|
||||
|
||||
packages = [
|
||||
{ include = "androguard" },
|
||||
]
|
||||
packages = [{ include = "androguard" }]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.9"
|
||||
@ -28,9 +26,13 @@ apkInspector = ">=1.1.7"
|
||||
matplotlib = "*"
|
||||
networkx = "*"
|
||||
pyyaml = "*"
|
||||
cryptography = "*"
|
||||
|
||||
[tool.setuptools.package_data]
|
||||
"androguard.core.api_specific_resources" = ["aosp_permissions/*.json", "api_permission_mappings/*.json"]
|
||||
"androguard.core.api_specific_resources" = [
|
||||
"aosp_permissions/*.json",
|
||||
"api_permission_mappings/*.json",
|
||||
]
|
||||
"androguard.core.resources" = ["public.xml"]
|
||||
|
||||
[tool.poetry.scripts]
|
||||
@ -39,3 +41,11 @@ androguard = "androguard.cli.cli:entry_point"
|
||||
[build-system]
|
||||
requires = ["poetry-core"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
||||
[tool.black]
|
||||
line-length = 79
|
||||
skip-string-normalization = true
|
||||
|
||||
[tool.isort]
|
||||
profile = "black"
|
||||
line_length = 79
|
||||
|
34
setup.py
34
setup.py
@ -1,6 +1,6 @@
|
||||
from androguard import __version__
|
||||
from setuptools import setup, find_packages
|
||||
from setuptools import find_packages, setup
|
||||
|
||||
from androguard import __version__
|
||||
|
||||
with open('requirements.txt', 'r') as fp:
|
||||
install_requires = fp.read().splitlines()
|
||||
@ -14,24 +14,26 @@ setup(
|
||||
url="https://github.com/androguard/androguard",
|
||||
install_requires=install_requires,
|
||||
package_data={
|
||||
"androguard.core.api_specific_resources": ["aosp_permissions/*.json",
|
||||
"api_permission_mappings/*.json"],
|
||||
"androguard.core.api_specific_resources": [
|
||||
"aosp_permissions/*.json",
|
||||
"api_permission_mappings/*.json",
|
||||
],
|
||||
"androguard.core.resources": ["public.xml"],
|
||||
},
|
||||
entry_points={
|
||||
'console_scripts': [
|
||||
'androguard = androguard.cli.cli:entry_point'] },
|
||||
'console_scripts': ['androguard = androguard.cli.cli:entry_point']
|
||||
},
|
||||
setup_requires=['setuptools'],
|
||||
python_requires='>=3.9',
|
||||
classifiers=[
|
||||
'License :: OSI Approved :: Apache Software License',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
'Programming Language :: Python :: 3.10',
|
||||
'Programming Language :: Python :: 3 :: Only',
|
||||
'Topic :: Security',
|
||||
'Topic :: Software Development',
|
||||
'Topic :: Utilities',
|
||||
],
|
||||
'License :: OSI Approved :: Apache Software License',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
'Programming Language :: Python :: 3.10',
|
||||
'Programming Language :: Python :: 3 :: Only',
|
||||
'Topic :: Security',
|
||||
'Topic :: Software Development',
|
||||
'Topic :: Utilities',
|
||||
],
|
||||
)
|
||||
|
@ -1,14 +1,13 @@
|
||||
from struct import pack, unpack
|
||||
from binascii import hexlify
|
||||
|
||||
# This is a very simple DEX parser, to get the bytecodes for each method
|
||||
# Output format will be:
|
||||
# <class name> <method name> <bytecode as hex string>
|
||||
import sys
|
||||
from binascii import hexlify
|
||||
from struct import pack, unpack
|
||||
|
||||
sys.path.append('.')
|
||||
|
||||
from androguard.core.dex import readuleb128, readsleb128, DalvikPacker
|
||||
from androguard.core.dex import DalvikPacker, readsleb128, readuleb128
|
||||
|
||||
|
||||
def read_null_terminated(f):
|
||||
@ -21,7 +20,7 @@ def read_null_terminated(f):
|
||||
x.append(ord(z))
|
||||
|
||||
|
||||
class MockClassManager():
|
||||
class MockClassManager:
|
||||
@property
|
||||
def packer(self):
|
||||
return DalvikPacker(0x12345678)
|
||||
@ -36,18 +35,46 @@ class read_dex:
|
||||
methods = [] # Stores method_idx, code_off
|
||||
|
||||
with open(fname, "rb") as f:
|
||||
magic, checksum, signature, file_size, header_size, endian_tag, link_size, \
|
||||
link_off, map_off, self.string_ids_size, string_ids_off, type_ids_size, \
|
||||
type_ids_off, proto_ids_size, proto_ids_off, field_ids_size, field_ids_off, \
|
||||
method_ids_size, method_ids_off, class_defs_size, class_defs_off, data_size, \
|
||||
data_off = unpack("<8sI20s20I", f.read(112))
|
||||
(
|
||||
magic,
|
||||
checksum,
|
||||
signature,
|
||||
file_size,
|
||||
header_size,
|
||||
endian_tag,
|
||||
link_size,
|
||||
link_off,
|
||||
map_off,
|
||||
self.string_ids_size,
|
||||
string_ids_off,
|
||||
type_ids_size,
|
||||
type_ids_off,
|
||||
proto_ids_size,
|
||||
proto_ids_off,
|
||||
field_ids_size,
|
||||
field_ids_off,
|
||||
method_ids_size,
|
||||
method_ids_off,
|
||||
class_defs_size,
|
||||
class_defs_off,
|
||||
data_size,
|
||||
data_off,
|
||||
) = unpack("<8sI20s20I", f.read(112))
|
||||
|
||||
# print("class_defs_size", class_defs_size, "class_defs_off", class_defs_off)
|
||||
for i in range(class_defs_size):
|
||||
# class_def_item
|
||||
f.seek(class_defs_off + i * 8 * 4)
|
||||
class_idx, access_flags, superclass_idx, interfaces_off, source_file_idx, \
|
||||
annotations_off, class_data_off, static_values_off = unpack("<8I", f.read(8 * 4))
|
||||
(
|
||||
class_idx,
|
||||
access_flags,
|
||||
superclass_idx,
|
||||
interfaces_off,
|
||||
source_file_idx,
|
||||
annotations_off,
|
||||
class_data_off,
|
||||
static_values_off,
|
||||
) = unpack("<8I", f.read(8 * 4))
|
||||
|
||||
# Now parse the class_data_item
|
||||
if class_data_off == 0:
|
||||
@ -92,7 +119,7 @@ class read_dex:
|
||||
self.str_raw = dict()
|
||||
for i in range(self.string_ids_size):
|
||||
f.seek(string_ids_off + i * 4)
|
||||
string_data_off, = unpack("<I", f.read(4))
|
||||
(string_data_off,) = unpack("<I", f.read(4))
|
||||
|
||||
f.seek(string_data_off)
|
||||
utf16_size = readuleb128(cm, f)
|
||||
@ -105,7 +132,7 @@ class read_dex:
|
||||
self.types = dict()
|
||||
for i in range(type_ids_size):
|
||||
f.seek(type_ids_off + i * 4)
|
||||
descriptor_idx, = unpack("<I", f.read(4))
|
||||
(descriptor_idx,) = unpack("<I", f.read(4))
|
||||
self.types[i] = descriptor_idx
|
||||
|
||||
method_ids = {}
|
||||
@ -113,7 +140,10 @@ class read_dex:
|
||||
for i in range(method_ids_size):
|
||||
f.seek(method_ids_off + i * 8)
|
||||
class_idx, proto_idx, name_idx = unpack("<HHI", f.read(8))
|
||||
method_ids[i] = [strings[self.types[class_idx]], strings[name_idx]]
|
||||
method_ids[i] = [
|
||||
strings[self.types[class_idx]],
|
||||
strings[name_idx],
|
||||
]
|
||||
|
||||
# Now parse the found methods and print to stdout
|
||||
mres = dict()
|
||||
@ -123,10 +153,18 @@ class read_dex:
|
||||
# We just parse everything manually to get the length, then we save the
|
||||
# complete code block
|
||||
f.seek(code_off)
|
||||
registers_size, ins_size, outs_size, tries_size, debug_info_off, insns_size \
|
||||
= unpack("<4HII", f.read(4 * 2 + 2 * 4))
|
||||
(
|
||||
registers_size,
|
||||
ins_size,
|
||||
outs_size,
|
||||
tries_size,
|
||||
debug_info_off,
|
||||
insns_size,
|
||||
) = unpack("<4HII", f.read(4 * 2 + 2 * 4))
|
||||
|
||||
insns = unpack("<{}H".format(insns_size), f.read(2 * insns_size))
|
||||
insns = unpack(
|
||||
"<{}H".format(insns_size), f.read(2 * insns_size)
|
||||
)
|
||||
|
||||
if tries_size > 0 and insns_size % 2 == 1:
|
||||
padding = unpack("<H", f.read(2))
|
||||
@ -134,7 +172,10 @@ class read_dex:
|
||||
if tries_size > 0:
|
||||
|
||||
# try_item[tries_size]
|
||||
tries = unpack("<{}".format("".join(["IHH"] * tries_size)), f.read(8 * tries_size))
|
||||
tries = unpack(
|
||||
"<{}".format("".join(["IHH"] * tries_size)),
|
||||
f.read(8 * tries_size),
|
||||
)
|
||||
|
||||
# encoded_catch_handler_list
|
||||
size = readuleb128(cm, f)
|
||||
|
@ -1,6 +1,5 @@
|
||||
import os
|
||||
import unittest
|
||||
|
||||
from operator import itemgetter
|
||||
|
||||
from androguard.core.analysis import analysis
|
||||
@ -19,13 +18,23 @@ class AnalysisTest(unittest.TestCase):
|
||||
self.assertIsInstance(dx, analysis.Analysis)
|
||||
|
||||
def testAPK(self):
|
||||
a, d, dx = AnalyzeAPK(os.path.join(test_dir, "data/APK/a2dp.Vol_137.apk"))
|
||||
a, d, dx = AnalyzeAPK(
|
||||
os.path.join(test_dir, "data/APK/a2dp.Vol_137.apk")
|
||||
)
|
||||
|
||||
self.assertEqual(len(list(dx.get_internal_classes())), 1353) # dex header header->headerItem->classDefsSize
|
||||
self.assertEqual(len(list(dx.get_external_classes())), 388) # difficult to check, cannot find using JADX
|
||||
self.assertEqual(len(list(dx.get_classes())), 1741) # sum of internal and external classes
|
||||
self.assertEqual(
|
||||
len(list(dx.get_internal_classes())), 1353
|
||||
) # dex header header->headerItem->classDefsSize
|
||||
self.assertEqual(
|
||||
len(list(dx.get_external_classes())), 388
|
||||
) # difficult to check, cannot find using JADX
|
||||
self.assertEqual(
|
||||
len(list(dx.get_classes())), 1741
|
||||
) # sum of internal and external classes
|
||||
|
||||
self.assertEqual(len(dx.get_strings()), 13523) # dex header header->headerItem->stringsIdsSize
|
||||
self.assertEqual(
|
||||
len(dx.get_strings()), 13523
|
||||
) # dex header header->headerItem->stringsIdsSize
|
||||
|
||||
# don't have a way to discern external vs internal fields currently,
|
||||
# header->headerItemFieldIdsSize is 4005, but there must be 573 more external added
|
||||
@ -33,42 +42,88 @@ class AnalysisTest(unittest.TestCase):
|
||||
self.assertEqual(len(list(dx.get_fields())), 4005 + 573)
|
||||
|
||||
# internal+external methods should sum up to header->headerItem->methodIdsSize
|
||||
self.assertEqual(len(list(dx.get_internal_methods())), 9676) # difficult to check, can use jadx-gui and see summary
|
||||
self.assertEqual(len(list(dx.get_external_methods())), 3116) # difficult to check
|
||||
self.assertEqual(
|
||||
len(list(dx.get_internal_methods())), 9676
|
||||
) # difficult to check, can use jadx-gui and see summary
|
||||
self.assertEqual(
|
||||
len(list(dx.get_external_methods())), 3116
|
||||
) # difficult to check
|
||||
|
||||
# TODO: the DEX header says 12795 here, but 9676 + 3116 adds up to 12792
|
||||
# JADX corroborates 9676, so I think 3116 is off, and a few unnecessary
|
||||
# ExternalMethods are added somewhere
|
||||
self.assertEqual(len(list(dx.get_methods())), 12792) # dex header header->headerItem->methodIdsSize
|
||||
|
||||
|
||||
self.assertEqual(
|
||||
len(list(dx.get_methods())), 12792
|
||||
) # dex header header->headerItem->methodIdsSize
|
||||
|
||||
for cls in dx.get_external_classes():
|
||||
self.assertEqual(cls.name[0], 'L')
|
||||
self.assertEqual(cls.name[-1], ';')
|
||||
|
||||
# Filter all support libraries
|
||||
self.assertEqual(len(list(dx.find_classes("^(?!Landroid/support).*;$"))), 512)
|
||||
self.assertEqual(len(list(dx.find_classes("^(?!Landroid/support).*;$", no_external=True))), 124)
|
||||
self.assertEqual(
|
||||
len(list(dx.find_classes("^(?!Landroid/support).*;$"))), 512
|
||||
)
|
||||
self.assertEqual(
|
||||
len(
|
||||
list(
|
||||
dx.find_classes(
|
||||
"^(?!Landroid/support).*;$", no_external=True
|
||||
)
|
||||
)
|
||||
),
|
||||
124,
|
||||
)
|
||||
|
||||
# Find all constructors by method name
|
||||
self.assertEqual(
|
||||
len(list(dx.find_methods(classname="^(?!Landroid).*;$", methodname="<init>", descriptor=r"^\(.+\).*$"))),
|
||||
138)
|
||||
self.assertEqual(len(list(
|
||||
dx.find_methods(classname="^(?!Landroid).*;$", methodname="<init>", descriptor=r"^\(.+\).*$",
|
||||
no_external=True))), 94)
|
||||
len(
|
||||
list(
|
||||
dx.find_methods(
|
||||
classname="^(?!Landroid).*;$",
|
||||
methodname="<init>",
|
||||
descriptor=r"^\(.+\).*$",
|
||||
)
|
||||
)
|
||||
),
|
||||
138,
|
||||
)
|
||||
self.assertEqual(
|
||||
len(
|
||||
list(
|
||||
dx.find_methods(
|
||||
classname="^(?!Landroid).*;$",
|
||||
methodname="<init>",
|
||||
descriptor=r"^\(.+\).*$",
|
||||
no_external=True,
|
||||
)
|
||||
)
|
||||
),
|
||||
94,
|
||||
)
|
||||
|
||||
# Find url like strings
|
||||
self.assertEqual(len(list(dx.find_strings(r".*:\/\/.*"))), 16)
|
||||
|
||||
# find String fields
|
||||
self.assertEqual(len(list(dx.find_fields(classname="^(?!Landroid).*;$", fieldtype=r"Ljava\/lang\/String;"))),
|
||||
95)#63)
|
||||
self.assertEqual(
|
||||
len(
|
||||
list(
|
||||
dx.find_fields(
|
||||
classname="^(?!Landroid).*;$",
|
||||
fieldtype=r"Ljava\/lang\/String;",
|
||||
)
|
||||
)
|
||||
),
|
||||
95,
|
||||
) # 63)
|
||||
|
||||
def testAnalysis(self):
|
||||
import sys
|
||||
h, d, dx = AnalyzeDex(os.path.join(test_dir, "data/APK/AnalysisTest.dex"))
|
||||
|
||||
h, d, dx = AnalyzeDex(
|
||||
os.path.join(test_dir, "data/APK/AnalysisTest.dex")
|
||||
)
|
||||
|
||||
self.assertEqual(len(list(dx.get_internal_classes())), 1)
|
||||
self.assertEqual(len(list(dx.get_internal_methods())), 4)
|
||||
@ -77,21 +132,35 @@ class AnalysisTest(unittest.TestCase):
|
||||
self.assertEqual(len(dx.get_strings()), 21)
|
||||
self.assertEqual(len(list(dx.get_fields())), 0)
|
||||
|
||||
self.assertEqual(h, "4595fc25104f3fcd709163eb70ca476edf116753607ec18f09548968c71910dc")
|
||||
self.assertEqual(
|
||||
h,
|
||||
"4595fc25104f3fcd709163eb70ca476edf116753607ec18f09548968c71910dc",
|
||||
)
|
||||
self.assertIsInstance(d, DEX)
|
||||
self.assertIsInstance(dx, analysis.Analysis)
|
||||
|
||||
cls = ["Ljava/io/PrintStream;", "Ljava/lang/Object;",
|
||||
"Ljava/math/BigDecimal;", "Ljava/math/BigInteger;"]
|
||||
cls = [
|
||||
"Ljava/io/PrintStream;",
|
||||
"Ljava/lang/Object;",
|
||||
"Ljava/math/BigDecimal;",
|
||||
"Ljava/math/BigInteger;",
|
||||
]
|
||||
|
||||
for c in cls:
|
||||
self.assertIn(c, map(lambda x: x.orig_class.get_name(),
|
||||
dx.get_external_classes()))
|
||||
self.assertIn(
|
||||
c,
|
||||
map(
|
||||
lambda x: x.orig_class.get_name(),
|
||||
dx.get_external_classes(),
|
||||
),
|
||||
)
|
||||
|
||||
def testMultidex(self):
|
||||
a, d, dx = AnalyzeAPK(os.path.join(test_dir, "data/APK/multidex.apk"))
|
||||
|
||||
cls = list(map(lambda x: x.get_vm_class().get_name(), dx.get_classes()))
|
||||
cls = list(
|
||||
map(lambda x: x.get_vm_class().get_name(), dx.get_classes())
|
||||
)
|
||||
self.assertIn('Lcom/foobar/foo/Foobar;', cls)
|
||||
self.assertIn('Lcom/blafoo/bar/Blafoo;', cls)
|
||||
|
||||
@ -138,14 +207,18 @@ class AnalysisTest(unittest.TestCase):
|
||||
self.assertFalse(dx.classes["Lcom/foobar/foo/Foobar;"].is_external())
|
||||
|
||||
def testInterfaces(self):
|
||||
h, d, dx = AnalyzeDex(os.path.join(test_dir, "data/APK/InterfaceCls.dex"))
|
||||
h, d, dx = AnalyzeDex(
|
||||
os.path.join(test_dir, "data/APK/InterfaceCls.dex")
|
||||
)
|
||||
|
||||
cls = dx.classes['LInterfaceCls;']
|
||||
self.assertIn('Ljavax/net/ssl/X509TrustManager;', cls.implements)
|
||||
self.assertEqual(cls.name, 'LInterfaceCls;')
|
||||
|
||||
def testExtends(self):
|
||||
h, d, dx = AnalyzeDex(os.path.join(test_dir, "data/APK/ExceptionHandling.dex"))
|
||||
h, d, dx = AnalyzeDex(
|
||||
os.path.join(test_dir, "data/APK/ExceptionHandling.dex")
|
||||
)
|
||||
|
||||
cls = dx.classes['LSomeException;']
|
||||
self.assertEqual(cls.extends, 'Ljava/lang/Exception;')
|
||||
@ -169,29 +242,52 @@ class AnalysisTest(unittest.TestCase):
|
||||
testcls = dx.classes['Ltests/androguard/TestActivity;']
|
||||
self.assertIsInstance(testcls, analysis.ClassAnalysis)
|
||||
|
||||
testmeth = list(filter(lambda x: x.name == 'onCreate', testcls.get_methods()))[0]
|
||||
testmeth = list(
|
||||
filter(lambda x: x.name == 'onCreate', testcls.get_methods())
|
||||
)[0]
|
||||
|
||||
self.assertEqual(len(list(dx.find_methods(testcls.name, '^onCreate$'))), 1)
|
||||
self.assertEqual(list(dx.find_methods(testcls.name, '^onCreate$'))[0], testmeth)
|
||||
self.assertEqual(
|
||||
len(list(dx.find_methods(testcls.name, '^onCreate$'))), 1
|
||||
)
|
||||
self.assertEqual(
|
||||
list(dx.find_methods(testcls.name, '^onCreate$'))[0], testmeth
|
||||
)
|
||||
|
||||
self.assertIsInstance(testmeth, analysis.MethodAnalysis)
|
||||
self.assertFalse(testmeth.is_external())
|
||||
self.assertIsInstance(testmeth.method, EncodedMethod)
|
||||
self.assertEqual(testmeth.name, 'onCreate')
|
||||
|
||||
xrefs = list(map(lambda x: x.full_name, map(itemgetter(1), sorted(testmeth.get_xref_to(), key=itemgetter(2)))))
|
||||
xrefs = list(
|
||||
map(
|
||||
lambda x: x.full_name,
|
||||
map(
|
||||
itemgetter(1),
|
||||
sorted(testmeth.get_xref_to(), key=itemgetter(2)),
|
||||
),
|
||||
)
|
||||
)
|
||||
self.assertEqual(len(xrefs), 5)
|
||||
|
||||
# First, super is called:
|
||||
self.assertEqual(xrefs.pop(0), 'Landroid/app/Activity; onCreate (Landroid/os/Bundle;)V')
|
||||
self.assertEqual(
|
||||
xrefs.pop(0),
|
||||
'Landroid/app/Activity; onCreate (Landroid/os/Bundle;)V',
|
||||
)
|
||||
# then setContentView (which is in the current class but the method is external)
|
||||
self.assertEqual(xrefs.pop(0), 'Ltests/androguard/TestActivity; setContentView (I)V')
|
||||
self.assertEqual(
|
||||
xrefs.pop(0), 'Ltests/androguard/TestActivity; setContentView (I)V'
|
||||
)
|
||||
# then getApplicationContext (inside the Toast)
|
||||
self.assertEqual(xrefs.pop(0),
|
||||
'Ltests/androguard/TestActivity; getApplicationContext ()Landroid/content/Context;')
|
||||
self.assertEqual(
|
||||
xrefs.pop(0),
|
||||
'Ltests/androguard/TestActivity; getApplicationContext ()Landroid/content/Context;',
|
||||
)
|
||||
# then Toast.makeText
|
||||
self.assertEqual(xrefs.pop(0),
|
||||
'Landroid/widget/Toast; makeText (Landroid/content/Context; Ljava/lang/CharSequence; I)Landroid/widget/Toast;')
|
||||
self.assertEqual(
|
||||
xrefs.pop(0),
|
||||
'Landroid/widget/Toast; makeText (Landroid/content/Context; Ljava/lang/CharSequence; I)Landroid/widget/Toast;',
|
||||
)
|
||||
# then show()
|
||||
self.assertEqual(xrefs.pop(0), 'Landroid/widget/Toast; show ()V')
|
||||
|
||||
@ -204,7 +300,11 @@ class AnalysisTest(unittest.TestCase):
|
||||
# We have MethodAnalysis now stored in the xref!
|
||||
self.assertIn(testmeth, map(itemgetter(1), other[0].get_xref_from()))
|
||||
|
||||
other = list(dx.find_methods('^Ltests/androguard/TestActivity;$', '^setContentView$'))
|
||||
other = list(
|
||||
dx.find_methods(
|
||||
'^Ltests/androguard/TestActivity;$', '^setContentView$'
|
||||
)
|
||||
)
|
||||
# External because not overwritten in class:
|
||||
self.assertEqual(len(other), 1)
|
||||
self.assertIsInstance(other[0], analysis.MethodAnalysis)
|
||||
@ -212,7 +312,11 @@ class AnalysisTest(unittest.TestCase):
|
||||
self.assertFalse(other[0].is_android_api())
|
||||
self.assertIn(testmeth, map(itemgetter(1), other[0].get_xref_from()))
|
||||
|
||||
other = list(dx.find_methods('^Ltests/androguard/TestActivity;$', '^getApplicationContext$'))
|
||||
other = list(
|
||||
dx.find_methods(
|
||||
'^Ltests/androguard/TestActivity;$', '^getApplicationContext$'
|
||||
)
|
||||
)
|
||||
# External because not overwritten in class:
|
||||
self.assertEqual(len(other), 1)
|
||||
self.assertIsInstance(other[0], analysis.MethodAnalysis)
|
||||
@ -235,32 +339,58 @@ class AnalysisTest(unittest.TestCase):
|
||||
self.assertIn(testmeth, map(itemgetter(1), other[0].get_xref_from()))
|
||||
|
||||
# Next test internal calls
|
||||
testmeth = list(filter(lambda x: x.name == 'testCalls', testcls.get_methods()))[0]
|
||||
testmeth = list(
|
||||
filter(lambda x: x.name == 'testCalls', testcls.get_methods())
|
||||
)[0]
|
||||
|
||||
self.assertEqual(len(list(dx.find_methods(testcls.name, '^testCalls$'))), 1)
|
||||
self.assertEqual(list(dx.find_methods(testcls.name, '^testCalls$'))[0], testmeth)
|
||||
self.assertEqual(
|
||||
len(list(dx.find_methods(testcls.name, '^testCalls$'))), 1
|
||||
)
|
||||
self.assertEqual(
|
||||
list(dx.find_methods(testcls.name, '^testCalls$'))[0], testmeth
|
||||
)
|
||||
|
||||
self.assertIsInstance(testmeth, analysis.MethodAnalysis)
|
||||
self.assertFalse(testmeth.is_external())
|
||||
self.assertIsInstance(testmeth.method, EncodedMethod)
|
||||
self.assertEqual(testmeth.name, 'testCalls')
|
||||
|
||||
xrefs = list(map(lambda x: x.full_name, map(itemgetter(1), sorted(testmeth.get_xref_to(), key=itemgetter(2)))))
|
||||
xrefs = list(
|
||||
map(
|
||||
lambda x: x.full_name,
|
||||
map(
|
||||
itemgetter(1),
|
||||
sorted(testmeth.get_xref_to(), key=itemgetter(2)),
|
||||
),
|
||||
)
|
||||
)
|
||||
self.assertEqual(len(xrefs), 4)
|
||||
|
||||
self.assertEqual(xrefs.pop(0), 'Ltests/androguard/TestActivity; testCall2 (J)V')
|
||||
self.assertEqual(xrefs.pop(0), 'Ltests/androguard/TestIfs; testIF (I)I')
|
||||
self.assertEqual(xrefs.pop(0), 'Ljava/lang/Object; getClass ()Ljava/lang/Class;')
|
||||
self.assertEqual(xrefs.pop(0), 'Ljava/io/PrintStream; println (Ljava/lang/Object;)V')
|
||||
self.assertEqual(
|
||||
xrefs.pop(0), 'Ltests/androguard/TestActivity; testCall2 (J)V'
|
||||
)
|
||||
self.assertEqual(
|
||||
xrefs.pop(0), 'Ltests/androguard/TestIfs; testIF (I)I'
|
||||
)
|
||||
self.assertEqual(
|
||||
xrefs.pop(0), 'Ljava/lang/Object; getClass ()Ljava/lang/Class;'
|
||||
)
|
||||
self.assertEqual(
|
||||
xrefs.pop(0), 'Ljava/io/PrintStream; println (Ljava/lang/Object;)V'
|
||||
)
|
||||
|
||||
other = list(dx.find_methods('^Ltests/androguard/TestActivity;$', '^testCall2$'))
|
||||
other = list(
|
||||
dx.find_methods('^Ltests/androguard/TestActivity;$', '^testCall2$')
|
||||
)
|
||||
self.assertEqual(len(other), 1)
|
||||
self.assertIsInstance(other[0], analysis.MethodAnalysis)
|
||||
self.assertFalse(other[0].is_external())
|
||||
self.assertFalse(other[0].is_android_api())
|
||||
self.assertIn(testmeth, map(itemgetter(1), other[0].get_xref_from()))
|
||||
|
||||
other = list(dx.find_methods('^Ltests/androguard/TestIfs;$', '^testIF$'))
|
||||
other = list(
|
||||
dx.find_methods('^Ltests/androguard/TestIfs;$', '^testIF$')
|
||||
)
|
||||
self.assertEqual(len(other), 1)
|
||||
self.assertIsInstance(other[0], analysis.MethodAnalysis)
|
||||
self.assertFalse(other[0].is_external())
|
||||
@ -275,7 +405,9 @@ class AnalysisTest(unittest.TestCase):
|
||||
self.assertIn(testmeth, map(itemgetter(1), other[0].get_xref_from()))
|
||||
|
||||
# Testing new_instance
|
||||
testmeth = list(filter(lambda x: x.name == 'testString', testcls.get_methods()))[0]
|
||||
testmeth = list(
|
||||
filter(lambda x: x.name == 'testString', testcls.get_methods())
|
||||
)[0]
|
||||
self.assertIsInstance(testmeth, analysis.MethodAnalysis)
|
||||
self.assertFalse(testmeth.is_external())
|
||||
self.assertIsInstance(testmeth.method, EncodedMethod)
|
||||
@ -284,27 +416,39 @@ class AnalysisTest(unittest.TestCase):
|
||||
stringcls = dx.classes['Ljava/lang/String;']
|
||||
self.assertIsInstance(stringcls, analysis.ClassAnalysis)
|
||||
|
||||
self.assertIn(stringcls, map(itemgetter(0), testmeth.get_xref_new_instance()))
|
||||
self.assertIn(testmeth, map(itemgetter(0), stringcls.get_xref_new_instance()))
|
||||
self.assertIn(
|
||||
stringcls, map(itemgetter(0), testmeth.get_xref_new_instance())
|
||||
)
|
||||
self.assertIn(
|
||||
testmeth, map(itemgetter(0), stringcls.get_xref_new_instance())
|
||||
)
|
||||
|
||||
# Not testing println, as it has too many variants...
|
||||
|
||||
def testXrefOffsets(self):
|
||||
"""Tests if String offsets in bytecode are correctly stored"""
|
||||
_, _, dx = AnalyzeDex(os.path.join(test_dir, "data/APK/AnalysisTest.dex"))
|
||||
_, _, dx = AnalyzeDex(
|
||||
os.path.join(test_dir, "data/APK/AnalysisTest.dex")
|
||||
)
|
||||
|
||||
self.assertEqual(len(dx.get_strings()), 21)
|
||||
self.assertIsInstance(dx.strings['Hello world'], analysis.StringAnalysis)
|
||||
self.assertIsInstance(
|
||||
dx.strings['Hello world'], analysis.StringAnalysis
|
||||
)
|
||||
|
||||
sa = dx.strings['Hello world']
|
||||
|
||||
self.assertEqual(len(sa.get_xref_from()), 1)
|
||||
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
|
||||
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"""
|
||||
_, _, dx = AnalyzeDex(os.path.join(test_dir, "data/APK/FieldsTest.dex"))
|
||||
_, _, dx = AnalyzeDex(
|
||||
os.path.join(test_dir, "data/APK/FieldsTest.dex")
|
||||
)
|
||||
|
||||
self.assertEqual(len(dx.get_strings()), 20)
|
||||
self.assertIn('hello world', dx.strings.keys())
|
||||
@ -316,63 +460,163 @@ class AnalysisTest(unittest.TestCase):
|
||||
|
||||
self.assertEqual(len(afield.get_xref_read()), 1) # always same method
|
||||
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(with_offset=True)))),
|
||||
["foonbar", "foonbar"])
|
||||
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(with_offset=True)),
|
||||
)
|
||||
),
|
||||
["foonbar", "foonbar"],
|
||||
)
|
||||
|
||||
self.assertEqual(len(afield.get_xref_write()), 2)
|
||||
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(with_offset=True))))),
|
||||
sorted(["<init>", "foonbar"]))
|
||||
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(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(with_offset=True))))),
|
||||
sorted(["<clinit>"]))
|
||||
self.assertListEqual(list(sorted(map(lambda x: x.name, map(itemgetter(1),
|
||||
cfield.get_xref_read(with_offset=True))))),
|
||||
sorted(["foonbar"]))
|
||||
self.assertListEqual(
|
||||
list(
|
||||
sorted(
|
||||
map(
|
||||
lambda x: x.name,
|
||||
map(
|
||||
itemgetter(1),
|
||||
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(with_offset=True),
|
||||
),
|
||||
)
|
||||
)
|
||||
),
|
||||
sorted(["foonbar"]),
|
||||
)
|
||||
|
||||
def testPermissions(self):
|
||||
"""Test the get_permissions and get_permission_usage methods"""
|
||||
a, _, dx = AnalyzeAPK(os.path.join(test_dir, "data/APK/TestActivity.apk"))
|
||||
a, _, dx = AnalyzeAPK(
|
||||
os.path.join(test_dir, "data/APK/TestActivity.apk")
|
||||
)
|
||||
|
||||
api_level = a.get_effective_target_sdk_version()
|
||||
used_permissions = ['android.permission.BROADCAST_STICKY', 'android.permission.ACCESS_NETWORK_STATE']
|
||||
used_permissions = [
|
||||
'android.permission.BROADCAST_STICKY',
|
||||
'android.permission.ACCESS_NETWORK_STATE',
|
||||
]
|
||||
sticky_meths = ['onMenuItemSelected', 'navigateUpTo']
|
||||
network_meths = ['getNetworkInfo', 'getActiveNetworkInfo', 'isActiveNetworkMetered']
|
||||
network_meths = [
|
||||
'getNetworkInfo',
|
||||
'getActiveNetworkInfo',
|
||||
'isActiveNetworkMetered',
|
||||
]
|
||||
|
||||
for _, perm in dx.get_permissions(api_level):
|
||||
for p in perm:
|
||||
self.assertIn(p, used_permissions)
|
||||
meths = [x.name for x in dx.get_permission_usage('android.permission.BROADCAST_STICKY', api_level)]
|
||||
meths = [
|
||||
x.name
|
||||
for x in dx.get_permission_usage(
|
||||
'android.permission.BROADCAST_STICKY', api_level
|
||||
)
|
||||
]
|
||||
self.assertListEqual(sorted(meths), sorted(sticky_meths))
|
||||
meths = [x.name for x in dx.get_permission_usage('android.permission.ACCESS_NETWORK_STATE', api_level)]
|
||||
meths = [
|
||||
x.name
|
||||
for x in dx.get_permission_usage(
|
||||
'android.permission.ACCESS_NETWORK_STATE', api_level
|
||||
)
|
||||
]
|
||||
self.assertListEqual(sorted(meths), sorted(network_meths))
|
||||
|
||||
# Should give same result if no API level is given
|
||||
for _, perm in dx.get_permissions():
|
||||
for p in perm:
|
||||
self.assertIn(p, used_permissions)
|
||||
meths = [x.name for x in dx.get_permission_usage('android.permission.BROADCAST_STICKY')]
|
||||
meths = [
|
||||
x.name
|
||||
for x in dx.get_permission_usage(
|
||||
'android.permission.BROADCAST_STICKY'
|
||||
)
|
||||
]
|
||||
self.assertListEqual(sorted(meths), sorted(sticky_meths))
|
||||
meths = [x.name for x in dx.get_permission_usage('android.permission.ACCESS_NETWORK_STATE')]
|
||||
meths = [
|
||||
x.name
|
||||
for x in dx.get_permission_usage(
|
||||
'android.permission.ACCESS_NETWORK_STATE'
|
||||
)
|
||||
]
|
||||
self.assertListEqual(sorted(meths), sorted(network_meths))
|
||||
|
||||
def testHiddenAnnotation(self):
|
||||
a, d, dx = AnalyzeAPK(os.path.join(test_dir, "data/APK/OPCommonTelephony.jar"))
|
||||
a, d, dx = AnalyzeAPK(
|
||||
os.path.join(test_dir, "data/APK/OPCommonTelephony.jar")
|
||||
)
|
||||
|
||||
class1 = dx.classes["Lvendor/mediatek/hardware/radio_op/V1_2/IRadioIndicationOp$Stub;"]
|
||||
self.assertEqual(class1.restriction_flag, HiddenApiClassDataItem.RestrictionApiFlag.WHITELIST)
|
||||
self.assertEqual(class1.domain_flag, HiddenApiClassDataItem.DomapiApiFlag.CORE_PLATFORM_API)
|
||||
class1 = dx.classes[
|
||||
"Lvendor/mediatek/hardware/radio_op/V1_2/IRadioIndicationOp$Stub;"
|
||||
]
|
||||
self.assertEqual(
|
||||
class1.restriction_flag,
|
||||
HiddenApiClassDataItem.RestrictionApiFlag.WHITELIST,
|
||||
)
|
||||
self.assertEqual(
|
||||
class1.domain_flag,
|
||||
HiddenApiClassDataItem.DomapiApiFlag.CORE_PLATFORM_API,
|
||||
)
|
||||
|
||||
class1 = dx.classes[
|
||||
"Lcom/mediatek/opcommon/telephony/MtkRILConstantsOp;"
|
||||
]
|
||||
self.assertEqual(
|
||||
class1.restriction_flag,
|
||||
HiddenApiClassDataItem.RestrictionApiFlag.BLACKLIST,
|
||||
)
|
||||
self.assertEqual(
|
||||
class1.domain_flag, HiddenApiClassDataItem.DomapiApiFlag.NONE
|
||||
)
|
||||
|
||||
class1 = dx.classes["Lcom/mediatek/opcommon/telephony/MtkRILConstantsOp;"]
|
||||
self.assertEqual(class1.restriction_flag, HiddenApiClassDataItem.RestrictionApiFlag.BLACKLIST)
|
||||
self.assertEqual(class1.domain_flag, HiddenApiClassDataItem.DomapiApiFlag.NONE)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
@ -8,12 +8,23 @@ test_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
class AnnotationTest(unittest.TestCase):
|
||||
def testAnnotation(self):
|
||||
with open(os.path.join(test_dir, 'data/APK/Annotation_classes.dex'), "rb") as f:
|
||||
with open(
|
||||
os.path.join(test_dir, 'data/APK/Annotation_classes.dex'), "rb"
|
||||
) as f:
|
||||
d = DEX(f.read())
|
||||
|
||||
clazz = d.get_class('Landroid/support/v4/widget/SlidingPaneLayout$SlidingPanelLayoutImplJB;')
|
||||
clazz = d.get_class(
|
||||
'Landroid/support/v4/widget/SlidingPaneLayout$SlidingPanelLayoutImplJB;'
|
||||
)
|
||||
annotations = clazz._get_annotation_type_ids()
|
||||
self.assertIn('Landroid/support/annotation/RequiresApi;',
|
||||
[clazz.CM.get_type(annotation.type_idx) for annotation in annotations])
|
||||
self.assertIn(
|
||||
'Landroid/support/annotation/RequiresApi;',
|
||||
[
|
||||
clazz.CM.get_type(annotation.type_idx)
|
||||
for annotation in annotations
|
||||
],
|
||||
)
|
||||
|
||||
self.assertIn('Landroid/support/annotation/RequiresApi;', clazz.get_annotations())
|
||||
self.assertIn(
|
||||
'Landroid/support/annotation/RequiresApi;', clazz.get_annotations()
|
||||
)
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,19 +1,21 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import unittest
|
||||
|
||||
from lxml import etree
|
||||
|
||||
test_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
from androguard.core import apk, axml
|
||||
from operator import itemgetter
|
||||
|
||||
from androguard.core import apk, axml
|
||||
|
||||
TEST_APP_NAME = "TestsAndroguardApplication"
|
||||
TEST_ICONS = {
|
||||
120: "res/drawable-ldpi/icon.png",
|
||||
160: "res/drawable-mdpi/icon.png",
|
||||
240: "res/drawable-hdpi/icon.png",
|
||||
65536: "res/drawable-hdpi/icon.png"
|
||||
65536: "res/drawable-hdpi/icon.png",
|
||||
}
|
||||
TEST_CONFIGS = {
|
||||
"layout": [axml.ARSCResTableConfig.default_config()],
|
||||
@ -21,15 +23,17 @@ TEST_CONFIGS = {
|
||||
"drawable": [
|
||||
axml.ARSCResTableConfig(sdkVersion=4, density=120),
|
||||
axml.ARSCResTableConfig(sdkVersion=4, density=160),
|
||||
axml.ARSCResTableConfig(sdkVersion=4, density=240)
|
||||
]
|
||||
axml.ARSCResTableConfig(sdkVersion=4, density=240),
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
class ARSCTest(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
with open(os.path.join(test_dir, 'data/APK/TestActivity.apk'), "rb") as fd:
|
||||
with open(
|
||||
os.path.join(test_dir, 'data/APK/TestActivity.apk'), "rb"
|
||||
) as fd:
|
||||
cls.apk = apk.APK(fd.read(), True)
|
||||
|
||||
def testARSC(self):
|
||||
@ -38,13 +42,20 @@ class ARSCTest(unittest.TestCase):
|
||||
|
||||
def testAppName(self):
|
||||
app_name = self.apk.get_app_name()
|
||||
self.assertEqual(app_name, TEST_APP_NAME, "Couldn't deduce application/activity label")
|
||||
self.assertEqual(
|
||||
app_name,
|
||||
TEST_APP_NAME,
|
||||
"Couldn't deduce application/activity label",
|
||||
)
|
||||
|
||||
def testAppIcon(self):
|
||||
for wanted_density, correct_path in TEST_ICONS.items():
|
||||
app_icon_path = self.apk.get_app_icon(wanted_density)
|
||||
self.assertEqual(app_icon_path, correct_path,
|
||||
"Incorrect icon path for requested density")
|
||||
self.assertEqual(
|
||||
app_icon_path,
|
||||
correct_path,
|
||||
"Incorrect icon path for requested density",
|
||||
)
|
||||
|
||||
def testStrings(self):
|
||||
arsc = self.apk.get_android_resources()
|
||||
@ -54,8 +65,14 @@ class ARSCTest(unittest.TestCase):
|
||||
|
||||
e = etree.fromstring(arsc.get_string_resources(p, l))
|
||||
|
||||
self.assertEqual(e.find("string[@name='hello']").text, 'Hello World, TestActivity! kikoololmodif')
|
||||
self.assertEqual(e.find("string[@name='app_name']").text, 'TestsAndroguardApplication')
|
||||
self.assertEqual(
|
||||
e.find("string[@name='hello']").text,
|
||||
'Hello World, TestActivity! kikoololmodif',
|
||||
)
|
||||
self.assertEqual(
|
||||
e.find("string[@name='app_name']").text,
|
||||
'TestsAndroguardApplication',
|
||||
)
|
||||
|
||||
def testResourceNames(self):
|
||||
"""
|
||||
@ -63,16 +80,30 @@ class ARSCTest(unittest.TestCase):
|
||||
"""
|
||||
arsc = self.apk.get_android_resources()
|
||||
|
||||
self.assertEqual(arsc.get_resource_xml_name(0x7F040001), "@tests.androguard:string/app_name")
|
||||
self.assertEqual(arsc.get_resource_xml_name(0x7F020000), "@tests.androguard:drawable/icon")
|
||||
self.assertEqual(
|
||||
arsc.get_resource_xml_name(0x7F040001),
|
||||
"@tests.androguard:string/app_name",
|
||||
)
|
||||
self.assertEqual(
|
||||
arsc.get_resource_xml_name(0x7F020000),
|
||||
"@tests.androguard:drawable/icon",
|
||||
)
|
||||
|
||||
self.assertEqual(arsc.get_resource_xml_name(0x7F040001, 'tests.androguard'), "@string/app_name")
|
||||
self.assertEqual(arsc.get_resource_xml_name(0x7F020000, 'tests.androguard'), "@drawable/icon")
|
||||
self.assertEqual(
|
||||
arsc.get_resource_xml_name(0x7F040001, 'tests.androguard'),
|
||||
"@string/app_name",
|
||||
)
|
||||
self.assertEqual(
|
||||
arsc.get_resource_xml_name(0x7F020000, 'tests.androguard'),
|
||||
"@drawable/icon",
|
||||
)
|
||||
|
||||
# Also test non existing resources
|
||||
self.assertIsNone(arsc.get_resource_xml_name(0xFFFFFFFF))
|
||||
self.assertEqual(arsc.get_id('sdf', 0x7F040001), (None, None, None))
|
||||
self.assertEqual(arsc.get_id('tests.androguard', 0xFFFFFFFF), (None, None, None))
|
||||
self.assertEqual(
|
||||
arsc.get_id('tests.androguard', 0xFFFFFFFF), (None, None, None)
|
||||
)
|
||||
|
||||
def testDifferentStringLocales(self):
|
||||
"""
|
||||
@ -84,18 +115,21 @@ class ARSCTest(unittest.TestCase):
|
||||
|
||||
p = arsc.get_packages_names()[0]
|
||||
|
||||
self.assertEqual(sorted(["\x00\x00", "da", "de", "el", "fr", "ja", "ru"]),
|
||||
sorted(arsc.get_locales(p)))
|
||||
self.assertEqual(
|
||||
sorted(["\x00\x00", "da", "de", "el", "fr", "ja", "ru"]),
|
||||
sorted(arsc.get_locales(p)),
|
||||
)
|
||||
|
||||
item = "SMSDelayText"
|
||||
strings = {"\x00\x00": "Delay for reading text message",
|
||||
"da": "Forsinkelse for læsning af tekst besked",
|
||||
"de": "Verzögerung vor dem Lesen einer SMS",
|
||||
"el": "Χρονοκαθυστέρηση ανάγνωσης μηνυμάτων SMS",
|
||||
"fr": "Délai pour lire un SMS",
|
||||
"ja": "テキストメッセージ読み上げの遅延",
|
||||
"ru": "Задержка зачитывания SMS",
|
||||
}
|
||||
strings = {
|
||||
"\x00\x00": "Delay for reading text message",
|
||||
"da": "Forsinkelse for læsning af tekst besked",
|
||||
"de": "Verzögerung vor dem Lesen einer SMS",
|
||||
"el": "Χρονοκαθυστέρηση ανάγνωσης μηνυμάτων SMS",
|
||||
"fr": "Délai pour lire un SMS",
|
||||
"ja": "テキストメッセージ読み上げの遅延",
|
||||
"ru": "Задержка зачитывания SMS",
|
||||
}
|
||||
for k, v in strings.items():
|
||||
e = etree.fromstring(arsc.get_string_resources(p, k))
|
||||
self.assertEqual(e.find("string[@name='{}']".format(item)).text, v)
|
||||
@ -106,23 +140,31 @@ class ARSCTest(unittest.TestCase):
|
||||
|
||||
for res_type, test_configs in list(TEST_CONFIGS.items()):
|
||||
config_set = set(test_configs)
|
||||
self.assertIn(res_type, configs,
|
||||
"resource type %s was not found" % res_type)
|
||||
self.assertIn(
|
||||
res_type, configs, "resource type %s was not found" % res_type
|
||||
)
|
||||
for config in configs[res_type]:
|
||||
print(config.get_config_name_friendly())
|
||||
self.assertIn(config, config_set,
|
||||
"config %r was not expected" % config)
|
||||
self.assertIn(
|
||||
config, config_set, "config %r was not expected" % config
|
||||
)
|
||||
config_set.remove(config)
|
||||
|
||||
self.assertEqual(len(config_set), 0,
|
||||
"configs were not found: %s" % config_set)
|
||||
self.assertEqual(
|
||||
len(config_set), 0, "configs were not found: %s" % config_set
|
||||
)
|
||||
|
||||
unexpected_types = set(TEST_CONFIGS.keys()) - set(configs.keys())
|
||||
self.assertEqual(len(unexpected_types), 0,
|
||||
"received unexpected resource types: %s" % unexpected_types)
|
||||
self.assertEqual(
|
||||
len(unexpected_types),
|
||||
0,
|
||||
"received unexpected resource types: %s" % unexpected_types,
|
||||
)
|
||||
|
||||
def testFallback(self):
|
||||
a = apk.APK(os.path.join(test_dir, 'data/APK/com.teleca.jamendo_35.apk'))
|
||||
a = apk.APK(
|
||||
os.path.join(test_dir, 'data/APK/com.teleca.jamendo_35.apk')
|
||||
)
|
||||
|
||||
# Should use the fallback
|
||||
self.assertEqual(a.get_app_name(), "Jamendo")
|
||||
@ -133,17 +175,44 @@ class ARSCTest(unittest.TestCase):
|
||||
# Default Mode, no config
|
||||
self.assertEqual(len(res_parser.get_res_configs(res_id)), 2)
|
||||
# With default config, but fallback
|
||||
self.assertEqual(len(res_parser.get_res_configs(res_id, axml.ARSCResTableConfig.default_config())), 1)
|
||||
self.assertEqual(
|
||||
len(
|
||||
res_parser.get_res_configs(
|
||||
res_id, axml.ARSCResTableConfig.default_config()
|
||||
)
|
||||
),
|
||||
1,
|
||||
)
|
||||
# With default config but no fallback
|
||||
self.assertEqual(
|
||||
len(res_parser.get_res_configs(res_id, axml.ARSCResTableConfig.default_config(), fallback=False)), 0)
|
||||
len(
|
||||
res_parser.get_res_configs(
|
||||
res_id,
|
||||
axml.ARSCResTableConfig.default_config(),
|
||||
fallback=False,
|
||||
)
|
||||
),
|
||||
0,
|
||||
)
|
||||
|
||||
# Also test on resolver:
|
||||
self.assertListEqual(list(map(itemgetter(1), res_parser.get_resolved_res_configs(res_id))),
|
||||
["Jamendo", "Jamendo"])
|
||||
self.assertListEqual(list(
|
||||
map(itemgetter(1), res_parser.get_resolved_res_configs(res_id, axml.ARSCResTableConfig.default_config()))),
|
||||
["Jamendo"])
|
||||
self.assertListEqual(
|
||||
list(
|
||||
map(itemgetter(1), res_parser.get_resolved_res_configs(res_id))
|
||||
),
|
||||
["Jamendo", "Jamendo"],
|
||||
)
|
||||
self.assertListEqual(
|
||||
list(
|
||||
map(
|
||||
itemgetter(1),
|
||||
res_parser.get_resolved_res_configs(
|
||||
res_id, axml.ARSCResTableConfig.default_config()
|
||||
),
|
||||
)
|
||||
),
|
||||
["Jamendo"],
|
||||
)
|
||||
|
||||
def testIDParsing(self):
|
||||
parser = axml.ARSCParser.parse_id
|
||||
|
@ -1,8 +1,7 @@
|
||||
import io
|
||||
import os
|
||||
import unittest
|
||||
|
||||
from xml.dom import minidom
|
||||
import io
|
||||
|
||||
from androguard.core import axml
|
||||
from androguard.core.apk import APK
|
||||
@ -10,6 +9,7 @@ from androguard.util import set_log
|
||||
|
||||
test_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
|
||||
def is_valid_manifest(tree):
|
||||
# We can not really check much more...
|
||||
print(tree.tag, tree.attrib)
|
||||
@ -44,18 +44,23 @@ def xml_compare(x1, x2, reporter=None):
|
||||
pass
|
||||
elif x2.attrib.get(name) != value:
|
||||
if reporter:
|
||||
reporter('Attributes do not match: %s=%r, %s=%r'
|
||||
% (name, value, name, x2.attrib.get(name)))
|
||||
reporter(
|
||||
'Attributes do not match: %s=%r, %s=%r'
|
||||
% (name, value, name, x2.attrib.get(name))
|
||||
)
|
||||
return False
|
||||
for name in x2.attrib.keys():
|
||||
if name not in x1.attrib:
|
||||
if x2.tag == "application" and name == "{http://schemas.android.com/apk/res/android}debuggable":
|
||||
if (
|
||||
x2.tag == "application"
|
||||
and name
|
||||
== "{http://schemas.android.com/apk/res/android}debuggable"
|
||||
):
|
||||
# Debug attribute might be added by aapt
|
||||
pass
|
||||
else:
|
||||
if reporter:
|
||||
reporter('x2 has an attribute x1 is missing: %s'
|
||||
% name)
|
||||
reporter('x2 has an attribute x1 is missing: %s' % name)
|
||||
return False
|
||||
if not text_compare(x1.text, x2.text):
|
||||
if reporter:
|
||||
@ -69,16 +74,16 @@ def xml_compare(x1, x2, reporter=None):
|
||||
cl2 = x2.getchildren()
|
||||
if len(cl1) != len(cl2):
|
||||
if reporter:
|
||||
reporter('children length differs, %i != %i'
|
||||
% (len(cl1), len(cl2)))
|
||||
reporter(
|
||||
'children length differs, %i != %i' % (len(cl1), len(cl2))
|
||||
)
|
||||
return False
|
||||
i = 0
|
||||
for c1, c2 in zip(cl1, cl2):
|
||||
i += 1
|
||||
if not xml_compare(c1, c2, reporter=reporter):
|
||||
if reporter:
|
||||
reporter('children %i do not match: %s'
|
||||
% (i, c1.tag))
|
||||
reporter('children %i do not match: %s' % (i, c1.tag))
|
||||
return False
|
||||
return True
|
||||
|
||||
@ -90,73 +95,170 @@ class AXMLTest(unittest.TestCase):
|
||||
:return:
|
||||
"""
|
||||
# Fake, Empty AXML file
|
||||
a = axml.AXMLPrinter(b"\x03\x00\x08\x00\x24\x00\x00\x00"
|
||||
b"\x01\x00\x1c\x00\x1c\x00\x00\x00"
|
||||
b"\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
b"\x00\x00\x00\x00"
|
||||
b"\x00\x00\x00\x00\x00\x00\x00\x00")
|
||||
a = axml.AXMLPrinter(
|
||||
b"\x03\x00\x08\x00\x24\x00\x00\x00"
|
||||
b"\x01\x00\x1c\x00\x1c\x00\x00\x00"
|
||||
b"\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
b"\x00\x00\x00\x00"
|
||||
b"\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
)
|
||||
|
||||
self.assertIsNotNone(a)
|
||||
|
||||
self.assertEqual(a._fix_value("hello world"), "hello world")
|
||||
self.assertEqual(a._fix_value("Foobar \u000a\u000d\u0b12"), "Foobar \u000a\u000d\u0b12")
|
||||
self.assertEqual(
|
||||
a._fix_value("Foobar \u000a\u000d\u0b12"),
|
||||
"Foobar \u000a\u000d\u0b12",
|
||||
)
|
||||
self.assertEqual(a._fix_value("hello \U00011234"), "hello \U00011234")
|
||||
self.assertEqual(a._fix_value("\uFFFF"), "_")
|
||||
self.assertEqual(a._fix_value("hello\x00world"), "hello")
|
||||
|
||||
self.assertEqual(a._fix_name('', 'foobar'), ('', 'foobar'))
|
||||
self.assertEqual(a._fix_name('', '5foobar'), ('', '_5foobar'))
|
||||
self.assertEqual(a._fix_name('', 'android:foobar'), ('', 'android_foobar'))
|
||||
self.assertEqual(a._fix_name('', 'androiddd:foobar'), ('', 'androiddd_foobar'))
|
||||
self.assertEqual(
|
||||
a._fix_name('', 'android:foobar'), ('', 'android_foobar')
|
||||
)
|
||||
self.assertEqual(
|
||||
a._fix_name('', 'androiddd:foobar'), ('', 'androiddd_foobar')
|
||||
)
|
||||
self.assertEqual(a._fix_name('', 'sdf:foobar'), ('', 'sdf_foobar'))
|
||||
self.assertEqual(a._fix_name('', 'android:sdf:foobar'), ('', 'android_sdf_foobar'))
|
||||
self.assertEqual(
|
||||
a._fix_name('', 'android:sdf:foobar'), ('', 'android_sdf_foobar')
|
||||
)
|
||||
self.assertEqual(a._fix_name('', '5:foobar'), ('', '_5_foobar'))
|
||||
|
||||
self.assertEqual(a._fix_name('{http://schemas.android.com/apk/res/android}', 'foobar'),
|
||||
('{http://schemas.android.com/apk/res/android}', 'foobar'))
|
||||
self.assertEqual(a._fix_name('{http://schemas.android.com/apk/res/android}', '5foobar'),
|
||||
('{http://schemas.android.com/apk/res/android}', '_5foobar'))
|
||||
self.assertEqual(a._fix_name('{http://schemas.android.com/apk/res/android}', 'android:foobar'),
|
||||
('{http://schemas.android.com/apk/res/android}', 'android_foobar'))
|
||||
self.assertEqual(a._fix_name('{http://schemas.android.com/apk/res/android}', 'androiddd:foobar'),
|
||||
('{http://schemas.android.com/apk/res/android}', 'androiddd_foobar'))
|
||||
self.assertEqual(a._fix_name('{http://schemas.android.com/apk/res/android}', 'sdf:foobar'),
|
||||
('{http://schemas.android.com/apk/res/android}', 'sdf_foobar'))
|
||||
self.assertEqual(a._fix_name('{http://schemas.android.com/apk/res/android}', 'android:sdf:foobar'),
|
||||
('{http://schemas.android.com/apk/res/android}', 'android_sdf_foobar'))
|
||||
self.assertEqual(a._fix_name('{http://schemas.android.com/apk/res/android}', '5:foobar'),
|
||||
('{http://schemas.android.com/apk/res/android}', '_5_foobar'))
|
||||
self.assertEqual(
|
||||
a._fix_name(
|
||||
'{http://schemas.android.com/apk/res/android}', 'foobar'
|
||||
),
|
||||
('{http://schemas.android.com/apk/res/android}', 'foobar'),
|
||||
)
|
||||
self.assertEqual(
|
||||
a._fix_name(
|
||||
'{http://schemas.android.com/apk/res/android}', '5foobar'
|
||||
),
|
||||
('{http://schemas.android.com/apk/res/android}', '_5foobar'),
|
||||
)
|
||||
self.assertEqual(
|
||||
a._fix_name(
|
||||
'{http://schemas.android.com/apk/res/android}',
|
||||
'android:foobar',
|
||||
),
|
||||
('{http://schemas.android.com/apk/res/android}', 'android_foobar'),
|
||||
)
|
||||
self.assertEqual(
|
||||
a._fix_name(
|
||||
'{http://schemas.android.com/apk/res/android}',
|
||||
'androiddd:foobar',
|
||||
),
|
||||
(
|
||||
'{http://schemas.android.com/apk/res/android}',
|
||||
'androiddd_foobar',
|
||||
),
|
||||
)
|
||||
self.assertEqual(
|
||||
a._fix_name(
|
||||
'{http://schemas.android.com/apk/res/android}', 'sdf:foobar'
|
||||
),
|
||||
('{http://schemas.android.com/apk/res/android}', 'sdf_foobar'),
|
||||
)
|
||||
self.assertEqual(
|
||||
a._fix_name(
|
||||
'{http://schemas.android.com/apk/res/android}',
|
||||
'android:sdf:foobar',
|
||||
),
|
||||
(
|
||||
'{http://schemas.android.com/apk/res/android}',
|
||||
'android_sdf_foobar',
|
||||
),
|
||||
)
|
||||
self.assertEqual(
|
||||
a._fix_name(
|
||||
'{http://schemas.android.com/apk/res/android}', '5:foobar'
|
||||
),
|
||||
('{http://schemas.android.com/apk/res/android}', '_5_foobar'),
|
||||
)
|
||||
|
||||
# Add a namespace mapping and try again
|
||||
def new_nsmap(self):
|
||||
return {"android": "http://schemas.android.com/apk/res/android",
|
||||
"something": "http://example/url"}
|
||||
return {
|
||||
"android": "http://schemas.android.com/apk/res/android",
|
||||
"something": "http://example/url",
|
||||
}
|
||||
|
||||
setattr(axml.AXMLParser, 'nsmap', property(new_nsmap))
|
||||
|
||||
self.assertEqual(a._fix_name('', 'foobar'), ('', 'foobar'))
|
||||
self.assertEqual(a._fix_name('', '5foobar'), ('', '_5foobar'))
|
||||
self.assertEqual(a._fix_name('', 'android:foobar'), ('{http://schemas.android.com/apk/res/android}', 'foobar'))
|
||||
self.assertEqual(a._fix_name('', 'something:foobar'), ('{http://example/url}', 'foobar'))
|
||||
self.assertEqual(a._fix_name('', 'androiddd:foobar'), ('', 'androiddd_foobar'))
|
||||
self.assertEqual(
|
||||
a._fix_name('', 'android:foobar'),
|
||||
('{http://schemas.android.com/apk/res/android}', 'foobar'),
|
||||
)
|
||||
self.assertEqual(
|
||||
a._fix_name('', 'something:foobar'),
|
||||
('{http://example/url}', 'foobar'),
|
||||
)
|
||||
self.assertEqual(
|
||||
a._fix_name('', 'androiddd:foobar'), ('', 'androiddd_foobar')
|
||||
)
|
||||
self.assertEqual(a._fix_name('', 'sdf:foobar'), ('', 'sdf_foobar'))
|
||||
self.assertEqual(a._fix_name('', 'android:sdf:foobar'),
|
||||
('{http://schemas.android.com/apk/res/android}', 'sdf_foobar'))
|
||||
self.assertEqual(
|
||||
a._fix_name('', 'android:sdf:foobar'),
|
||||
('{http://schemas.android.com/apk/res/android}', 'sdf_foobar'),
|
||||
)
|
||||
self.assertEqual(a._fix_name('', '5:foobar'), ('', '_5_foobar'))
|
||||
self.assertEqual(a._fix_name('{http://schemas.android.com/apk/res/android}', 'foobar'),
|
||||
('{http://schemas.android.com/apk/res/android}', 'foobar'))
|
||||
self.assertEqual(a._fix_name('{http://schemas.android.com/apk/res/android}', '5foobar'),
|
||||
('{http://schemas.android.com/apk/res/android}', '_5foobar'))
|
||||
self.assertEqual(a._fix_name('{http://schemas.android.com/apk/res/android}', 'android:foobar'),
|
||||
('{http://schemas.android.com/apk/res/android}', 'android_foobar'))
|
||||
self.assertEqual(a._fix_name('{http://schemas.android.com/apk/res/android}', 'androiddd:foobar'),
|
||||
('{http://schemas.android.com/apk/res/android}', 'androiddd_foobar'))
|
||||
self.assertEqual(a._fix_name('{http://schemas.android.com/apk/res/android}', 'sdf:foobar'),
|
||||
('{http://schemas.android.com/apk/res/android}', 'sdf_foobar'))
|
||||
self.assertEqual(a._fix_name('{http://schemas.android.com/apk/res/android}', 'android:sdf:foobar'),
|
||||
('{http://schemas.android.com/apk/res/android}', 'android_sdf_foobar'))
|
||||
self.assertEqual(a._fix_name('{http://schemas.android.com/apk/res/android}', '5:foobar'),
|
||||
('{http://schemas.android.com/apk/res/android}', '_5_foobar'))
|
||||
self.assertEqual(
|
||||
a._fix_name(
|
||||
'{http://schemas.android.com/apk/res/android}', 'foobar'
|
||||
),
|
||||
('{http://schemas.android.com/apk/res/android}', 'foobar'),
|
||||
)
|
||||
self.assertEqual(
|
||||
a._fix_name(
|
||||
'{http://schemas.android.com/apk/res/android}', '5foobar'
|
||||
),
|
||||
('{http://schemas.android.com/apk/res/android}', '_5foobar'),
|
||||
)
|
||||
self.assertEqual(
|
||||
a._fix_name(
|
||||
'{http://schemas.android.com/apk/res/android}',
|
||||
'android:foobar',
|
||||
),
|
||||
('{http://schemas.android.com/apk/res/android}', 'android_foobar'),
|
||||
)
|
||||
self.assertEqual(
|
||||
a._fix_name(
|
||||
'{http://schemas.android.com/apk/res/android}',
|
||||
'androiddd:foobar',
|
||||
),
|
||||
(
|
||||
'{http://schemas.android.com/apk/res/android}',
|
||||
'androiddd_foobar',
|
||||
),
|
||||
)
|
||||
self.assertEqual(
|
||||
a._fix_name(
|
||||
'{http://schemas.android.com/apk/res/android}', 'sdf:foobar'
|
||||
),
|
||||
('{http://schemas.android.com/apk/res/android}', 'sdf_foobar'),
|
||||
)
|
||||
self.assertEqual(
|
||||
a._fix_name(
|
||||
'{http://schemas.android.com/apk/res/android}',
|
||||
'android:sdf:foobar',
|
||||
),
|
||||
(
|
||||
'{http://schemas.android.com/apk/res/android}',
|
||||
'android_sdf_foobar',
|
||||
),
|
||||
)
|
||||
self.assertEqual(
|
||||
a._fix_name(
|
||||
'{http://schemas.android.com/apk/res/android}', '5:foobar'
|
||||
),
|
||||
('{http://schemas.android.com/apk/res/android}', '_5_foobar'),
|
||||
)
|
||||
|
||||
def testNoStringPool(self):
|
||||
"""Test if a single header without string pool is rejected"""
|
||||
@ -172,30 +274,37 @@ class AXMLTest(unittest.TestCase):
|
||||
|
||||
def testWrongHeaderSize(self):
|
||||
"""Test if a wrong header size is rejected"""
|
||||
a = axml.AXMLPrinter(b"\x03\x00\x10\x00\x2c\x00\x00\x00"
|
||||
b"\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
b"\x01\x00\x1c\x00\x1c\x00\x00\x00"
|
||||
b"\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
b"\x00\x00\x00\x00"
|
||||
b"\x00\x00\x00\x00\x00\x00\x00\x00")
|
||||
a = axml.AXMLPrinter(
|
||||
b"\x03\x00\x10\x00\x2c\x00\x00\x00"
|
||||
b"\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
b"\x01\x00\x1c\x00\x1c\x00\x00\x00"
|
||||
b"\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
b"\x00\x00\x00\x00"
|
||||
b"\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
)
|
||||
self.assertFalse(a.is_valid())
|
||||
|
||||
def testWrongStringPoolHeader(self):
|
||||
"""Test if a wrong header type is rejected"""
|
||||
a = axml.AXMLPrinter(b"\x03\x00\x08\x00\x24\x00\x00\x00" b"\xDE\xAD\x1c\x00\x1c\x00\x00\x00"
|
||||
b"\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
b"\x00\x00\x00\x00"
|
||||
b"\x00\x00\x00\x00\x00\x00\x00\x00")
|
||||
a = axml.AXMLPrinter(
|
||||
b"\x03\x00\x08\x00\x24\x00\x00\x00"
|
||||
b"\xDE\xAD\x1c\x00\x1c\x00\x00\x00"
|
||||
b"\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
b"\x00\x00\x00\x00"
|
||||
b"\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
)
|
||||
self.assertFalse(a.is_valid())
|
||||
|
||||
def testWrongStringPoolSize(self):
|
||||
"""Test if a wrong string pool header size is rejected"""
|
||||
a = axml.AXMLPrinter(b"\x03\x00\x08\x00\x2c\x00\x00\x00"
|
||||
b"\x01\x00\x24\x00\x24\x00\x00\x00"
|
||||
b"\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
b"\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
b"\x00\x00\x00\x00"
|
||||
b"\x00\x00\x00\x00\x00\x00\x00\x00")
|
||||
a = axml.AXMLPrinter(
|
||||
b"\x03\x00\x08\x00\x2c\x00\x00\x00"
|
||||
b"\x01\x00\x24\x00\x24\x00\x00\x00"
|
||||
b"\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
b"\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
b"\x00\x00\x00\x00"
|
||||
b"\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
)
|
||||
self.assertFalse(a.is_valid())
|
||||
|
||||
def testArscHeader(self):
|
||||
@ -205,26 +314,53 @@ class AXMLTest(unittest.TestCase):
|
||||
self.assertIn("Can not read over the buffer size", str(cnx.exception))
|
||||
|
||||
with self.assertRaises(axml.ResParserError) as cnx:
|
||||
axml.ARSCHeader(io.BufferedReader(io.BytesIO(b"\x02\x01\xFF\xFF\x08\x00\x00\x00")))
|
||||
axml.ARSCHeader(
|
||||
io.BufferedReader(
|
||||
io.BytesIO(b"\x02\x01\xFF\xFF\x08\x00\x00\x00")
|
||||
)
|
||||
)
|
||||
self.assertIn("smaller than header size", str(cnx.exception))
|
||||
|
||||
with self.assertRaises(axml.ResParserError) as cnx:
|
||||
axml.ARSCHeader(io.BufferedReader(io.BytesIO(b"\x02\x01\x01\x00\x08\x00\x00\x00")))
|
||||
self.assertIn("declared header size is smaller than required size", str(cnx.exception))
|
||||
axml.ARSCHeader(
|
||||
io.BufferedReader(
|
||||
io.BytesIO(b"\x02\x01\x01\x00\x08\x00\x00\x00")
|
||||
)
|
||||
)
|
||||
self.assertIn(
|
||||
"declared header size is smaller than required size",
|
||||
str(cnx.exception),
|
||||
)
|
||||
|
||||
with self.assertRaises(axml.ResParserError) as cnx:
|
||||
axml.ARSCHeader(io.BufferedReader(io.BytesIO(b"\x02\x01\x08\x00\x04\x00\x00\x00")))
|
||||
self.assertIn("declared chunk size is smaller than required size", str(cnx.exception))
|
||||
axml.ARSCHeader(
|
||||
io.BufferedReader(
|
||||
io.BytesIO(b"\x02\x01\x08\x00\x04\x00\x00\x00")
|
||||
)
|
||||
)
|
||||
self.assertIn(
|
||||
"declared chunk size is smaller than required size",
|
||||
str(cnx.exception),
|
||||
)
|
||||
|
||||
a = axml.ARSCHeader(io.BufferedReader(io.BytesIO(b"\xCA\xFE\x08\x00\x10\x00\x00\x00"
|
||||
b"\xDE\xEA\xBE\xEF\x42\x42\x42\x42")))
|
||||
a = axml.ARSCHeader(
|
||||
io.BufferedReader(
|
||||
io.BytesIO(
|
||||
b"\xCA\xFE\x08\x00\x10\x00\x00\x00"
|
||||
b"\xDE\xEA\xBE\xEF\x42\x42\x42\x42"
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
self.assertEqual(a.type, 0xFECA)
|
||||
self.assertEqual(a.header_size, 8)
|
||||
self.assertEqual(a.size, 16)
|
||||
self.assertEqual(a.start, 0)
|
||||
self.assertEqual(a.end, 16)
|
||||
self.assertEqual(repr(a), "<ARSCHeader idx='0x00000000' type='65226' header_size='8' size='16'>")
|
||||
self.assertEqual(
|
||||
repr(a),
|
||||
"<ARSCHeader idx='0x00000000' type='65226' header_size='8' size='16'>",
|
||||
)
|
||||
|
||||
def testAndroidManifest(self):
|
||||
filenames = [
|
||||
|
@ -1,11 +1,12 @@
|
||||
from androguard.misc import AnalyzeAPK
|
||||
from androguard.core.analysis.analysis import ExternalMethod
|
||||
|
||||
import os
|
||||
import unittest
|
||||
|
||||
from androguard.core.analysis.analysis import ExternalMethod
|
||||
from androguard.misc import AnalyzeAPK
|
||||
|
||||
test_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
|
||||
class TestCallgraph(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
@ -18,9 +19,9 @@ class TestCallgraph(unittest.TestCase):
|
||||
|
||||
for n in callgraph:
|
||||
if isinstance(n, ExternalMethod):
|
||||
num_external+=1
|
||||
num_external += 1
|
||||
else:
|
||||
num_internal+=1
|
||||
num_internal += 1
|
||||
|
||||
return callgraph.number_of_nodes(), num_external, num_internal
|
||||
|
||||
@ -28,10 +29,14 @@ class TestCallgraph(unittest.TestCase):
|
||||
"""test callgraph generated with default parameter values"""
|
||||
callgraph = self.dx.get_call_graph()
|
||||
|
||||
total_nodes, total_external_nodes, total_internal_nodes = self._get_num_nodes(callgraph)
|
||||
total_nodes, total_external_nodes, total_internal_nodes = (
|
||||
self._get_num_nodes(callgraph)
|
||||
)
|
||||
|
||||
# ensure total of internal and external nodes equals the total nodes
|
||||
self.assertEqual(total_nodes, total_external_nodes + total_internal_nodes)
|
||||
self.assertEqual(
|
||||
total_nodes, total_external_nodes + total_internal_nodes
|
||||
)
|
||||
|
||||
# total num nodes
|
||||
self.assertEqual(total_nodes, 3600)
|
||||
@ -45,13 +50,14 @@ class TestCallgraph(unittest.TestCase):
|
||||
# total num edges
|
||||
self.assertEqual(callgraph.number_of_edges(), 4490)
|
||||
|
||||
|
||||
def testCallgraphFilterClassname(self):
|
||||
"""test callgraph with classname filter parameter"""
|
||||
callgraph = self.dx.get_call_graph(classname='Ltests/androguard/*')
|
||||
|
||||
total_nodes, total_external_nodes, total_internal_nodes = self._get_num_nodes(callgraph)
|
||||
|
||||
total_nodes, total_external_nodes, total_internal_nodes = (
|
||||
self._get_num_nodes(callgraph)
|
||||
)
|
||||
|
||||
self.assertEqual(total_nodes, 165)
|
||||
self.assertEqual(total_external_nodes, 41)
|
||||
self.assertEqual(total_internal_nodes, 124)
|
||||
@ -62,8 +68,10 @@ class TestCallgraph(unittest.TestCase):
|
||||
"""test callgraph with methodname filter parameter"""
|
||||
callgraph = self.dx.get_call_graph(methodname='Test*')
|
||||
|
||||
total_nodes, total_external_nodes, total_internal_nodes = self._get_num_nodes(callgraph)
|
||||
|
||||
total_nodes, total_external_nodes, total_internal_nodes = (
|
||||
self._get_num_nodes(callgraph)
|
||||
)
|
||||
|
||||
self.assertEqual(total_nodes, 36)
|
||||
self.assertEqual(total_external_nodes, 12)
|
||||
self.assertEqual(total_internal_nodes, 24)
|
||||
@ -71,10 +79,14 @@ class TestCallgraph(unittest.TestCase):
|
||||
|
||||
def testCallgraphFilterDescriptor(self):
|
||||
"""test callgraph with descriptor filter parameter"""
|
||||
callgraph = self.dx.get_call_graph(descriptor='\(LTestDefaultPackage;\sI\sI\sLTestDefaultPackage\$TestInnerClass;\)V')
|
||||
callgraph = self.dx.get_call_graph(
|
||||
descriptor='\(LTestDefaultPackage;\sI\sI\sLTestDefaultPackage\$TestInnerClass;\)V'
|
||||
)
|
||||
|
||||
total_nodes, total_external_nodes, total_internal_nodes = (
|
||||
self._get_num_nodes(callgraph)
|
||||
)
|
||||
|
||||
total_nodes, total_external_nodes, total_internal_nodes = self._get_num_nodes(callgraph)
|
||||
|
||||
self.assertEqual(total_nodes, 2)
|
||||
self.assertEqual(total_external_nodes, 0)
|
||||
self.assertEqual(total_internal_nodes, 2)
|
||||
@ -86,16 +98,30 @@ class TestCallgraph(unittest.TestCase):
|
||||
src_node, dst_node = list(callgraph.edges)[0]
|
||||
|
||||
# check source node
|
||||
self.assertEqual(src_node.get_class_name(), 'LTestDefaultPackage$TestInnerClass;')
|
||||
self.assertEqual(
|
||||
src_node.get_class_name(), 'LTestDefaultPackage$TestInnerClass;'
|
||||
)
|
||||
self.assertEqual(src_node.get_name(), '<init>')
|
||||
self.assertEqual(src_node.get_access_flags_string(), 'synthetic constructor')
|
||||
self.assertEqual(src_node.get_descriptor(), '(LTestDefaultPackage; I I LTestDefaultPackage$TestInnerClass;)V')
|
||||
self.assertEqual(
|
||||
src_node.get_access_flags_string(), 'synthetic constructor'
|
||||
)
|
||||
self.assertEqual(
|
||||
src_node.get_descriptor(),
|
||||
'(LTestDefaultPackage; I I LTestDefaultPackage$TestInnerClass;)V',
|
||||
)
|
||||
|
||||
# check dest node
|
||||
self.assertEqual(dst_node.get_class_name(), 'LTestDefaultPackage$TestInnerClass;')
|
||||
self.assertEqual(
|
||||
dst_node.get_class_name(), 'LTestDefaultPackage$TestInnerClass;'
|
||||
)
|
||||
self.assertEqual(dst_node.get_name(), '<init>')
|
||||
self.assertEqual(dst_node.get_access_flags_string(), 'private constructor')
|
||||
self.assertEqual(dst_node.get_descriptor(), '(LTestDefaultPackage; I I)V')
|
||||
self.assertEqual(
|
||||
dst_node.get_access_flags_string(), 'private constructor'
|
||||
)
|
||||
self.assertEqual(
|
||||
dst_node.get_descriptor(), '(LTestDefaultPackage; I I)V'
|
||||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
unittest.main()
|
||||
|
@ -1,16 +1,19 @@
|
||||
from androguard.cli.main import export_apps_to_format
|
||||
from androguard import session
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import unittest
|
||||
|
||||
from androguard import session
|
||||
from androguard.cli.main import export_apps_to_format
|
||||
|
||||
test_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
|
||||
class TestCLIDecompile(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
test_testactivity_apk_path = os.path.join(test_dir, 'data/APK/TestActivity.apk')
|
||||
test_testactivity_apk_path = os.path.join(
|
||||
test_dir, 'data/APK/TestActivity.apk'
|
||||
)
|
||||
cls.s = session.Session()
|
||||
with open(test_testactivity_apk_path, "rb") as fd:
|
||||
cls.s.add(test_testactivity_apk_path, fd.read())
|
||||
@ -20,18 +23,19 @@ class TestCLIDecompile(unittest.TestCase):
|
||||
export_apps_to_format(
|
||||
None,
|
||||
self.s,
|
||||
os.path.join(test_dir,'tmp_TestActivity_decompilation'),
|
||||
os.path.join(test_dir, 'tmp_TestActivity_decompilation'),
|
||||
None,
|
||||
False,
|
||||
None,
|
||||
None)
|
||||
|
||||
None,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
decomp_dir = os.path.join(test_dir, 'tmp_TestActivity_decompilation')
|
||||
if os.path.exists(decomp_dir):
|
||||
shutil.rmtree(decomp_dir)
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
unittest.main()
|
||||
|
@ -1,10 +1,9 @@
|
||||
import unittest
|
||||
|
||||
from androguard.misc import AnalyzeDex
|
||||
import os
|
||||
import re
|
||||
from androguard.misc import AnalyzeAPK
|
||||
from androguard.decompiler.decompile import DvMethod, DvClass
|
||||
import unittest
|
||||
|
||||
from androguard.decompiler.decompile import DvClass, DvMethod
|
||||
from androguard.misc import AnalyzeAPK, AnalyzeDex
|
||||
|
||||
test_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
@ -13,14 +12,16 @@ class DecompilerTest(unittest.TestCase):
|
||||
def testSimplification(self):
|
||||
h, d, dx = AnalyzeDex(os.path.join(test_dir, 'data/APK/Test.dex'))
|
||||
|
||||
z, = d.get_classes()
|
||||
(z,) = d.get_classes()
|
||||
|
||||
self.assertIn("return ((23 - p3) | ((p3 + 66) & 26));", z.get_source())
|
||||
|
||||
def testArrays(self):
|
||||
h, d, dx = AnalyzeDex(os.path.join(test_dir, 'data/APK/FillArrays.dex'))
|
||||
h, d, dx = AnalyzeDex(
|
||||
os.path.join(test_dir, 'data/APK/FillArrays.dex')
|
||||
)
|
||||
|
||||
z, = d.get_classes()
|
||||
(z,) = d.get_classes()
|
||||
|
||||
self.assertIn("{20, 30, 40, 50};", z.get_source())
|
||||
self.assertIn("{1, 2, 3, 4, 5, 999, 10324234};", z.get_source())
|
||||
@ -33,7 +34,9 @@ class DecompilerTest(unittest.TestCase):
|
||||
|
||||
def test_all_decompiler(self):
|
||||
# Generate test cases for this APK:
|
||||
a, d, dx = AnalyzeAPK(os.path.join(test_dir, 'data/APK/hello-world.apk'))
|
||||
a, d, dx = AnalyzeAPK(
|
||||
os.path.join(test_dir, 'data/APK/hello-world.apk')
|
||||
)
|
||||
for c in d[0].get_classes():
|
||||
test_name = re.sub("[^a-zA-Z0-9_]", "_", str(c.get_name())[1:-1])
|
||||
# Test the decompilation of a single class
|
||||
|
@ -1,15 +1,11 @@
|
||||
"""Tests for def_use."""
|
||||
|
||||
import collections
|
||||
import os
|
||||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
import collections
|
||||
import unittest
|
||||
|
||||
from androguard.decompiler import dataflow
|
||||
from androguard.decompiler import graph
|
||||
from androguard.decompiler import instruction
|
||||
from androguard.decompiler import basic_blocks
|
||||
from androguard.decompiler import basic_blocks, dataflow, graph, instruction
|
||||
|
||||
test_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
@ -23,8 +19,8 @@ class DataflowTest(unittest.TestCase):
|
||||
|
||||
def _CreateMockNode(self, node_name, start_ins_idx, lins):
|
||||
mock_node = mock.create_autospec(
|
||||
basic_blocks.BasicBlock,
|
||||
_name=node_name)
|
||||
basic_blocks.BasicBlock, _name=node_name
|
||||
)
|
||||
mock_node.__repr__ = mock.Mock(return_value=node_name)
|
||||
loc_ins = []
|
||||
ins_idx = start_ins_idx
|
||||
@ -92,7 +88,7 @@ class DataflowTest(unittest.TestCase):
|
||||
n5: [n6, n7],
|
||||
n6: [n8],
|
||||
n7: [n8],
|
||||
n8: [n9]
|
||||
n8: [n9],
|
||||
}
|
||||
preds = collections.defaultdict(list)
|
||||
for pred, lsucs in sucs.items():
|
||||
@ -129,7 +125,7 @@ class DataflowTest(unittest.TestCase):
|
||||
n7: {-2, -1, 0, 7},
|
||||
n8: {-2, -1, 0, 1, 6, 7, 8},
|
||||
n9: {-2, -1, 0, 1, 3, 6, 7, 8},
|
||||
dummy_exit_mock: {-2, -1, 0, 1, 3, 6, 7, 8}
|
||||
dummy_exit_mock: {-2, -1, 0, 1, 3, 6, 7, 8},
|
||||
}
|
||||
expected_R = {
|
||||
n1: {-2, -1},
|
||||
@ -141,14 +137,14 @@ class DataflowTest(unittest.TestCase):
|
||||
n7: {-2, -1, 0, 1},
|
||||
n8: {-2, -1, 0, 1, 6, 7},
|
||||
n9: {-2, -1, 0, 1, 3, 6, 7, 8},
|
||||
dummy_exit_mock: {-2, -1, 0, 1, 3, 6, 7, 8}
|
||||
dummy_exit_mock: {-2, -1, 0, 1, 3, 6, 7, 8},
|
||||
}
|
||||
expected_def_to_loc = {
|
||||
'a': {-1},
|
||||
'b': {-2},
|
||||
'c': {0, 6},
|
||||
'd': {1, 7},
|
||||
'ret': {3, 8}
|
||||
'ret': {3, 8},
|
||||
}
|
||||
self.assertDictEqual(analysis.A, expected_A)
|
||||
self.assertDictEqual(analysis.R, expected_R)
|
||||
@ -176,11 +172,10 @@ class DataflowTest(unittest.TestCase):
|
||||
'b': {-2},
|
||||
'c': {0, 6},
|
||||
'd': {1, 7},
|
||||
'ret': {3, 8}
|
||||
'ret': {3, 8},
|
||||
}
|
||||
mock_analysis.defs = {
|
||||
n1: {'c': {0},
|
||||
'd': {1}},
|
||||
n1: {'c': {0}, 'd': {1}},
|
||||
n2: {},
|
||||
n3: {'ret': {3}},
|
||||
n4: {},
|
||||
@ -188,7 +183,7 @@ class DataflowTest(unittest.TestCase):
|
||||
n6: {'c': {6}},
|
||||
n7: {'d': {7}},
|
||||
n8: {'ret': {8}},
|
||||
n9: {}
|
||||
n9: {},
|
||||
}
|
||||
mock_analysis.R = {
|
||||
n1: {-2, -1},
|
||||
@ -199,7 +194,7 @@ class DataflowTest(unittest.TestCase):
|
||||
n6: {-2, -1, 0, 1},
|
||||
n7: {-2, -1, 0, 1},
|
||||
n8: {-2, -1, 0, 1, 6, 7},
|
||||
n9: {-2, -1, 0, 1, 3, 6, 7, 8}
|
||||
n9: {-2, -1, 0, 1, 3, 6, 7, 8},
|
||||
}
|
||||
expected_du = {
|
||||
('a', -1): [0],
|
||||
@ -208,7 +203,7 @@ class DataflowTest(unittest.TestCase):
|
||||
('c', 6): [8],
|
||||
('d', 1): [3, 4, 5, 6, 7],
|
||||
('ret', 3): [9],
|
||||
('ret', 8): [9]
|
||||
('ret', 8): [9],
|
||||
}
|
||||
expected_ud = {
|
||||
('a', 0): [-1],
|
||||
@ -223,7 +218,7 @@ class DataflowTest(unittest.TestCase):
|
||||
('d', 5): [1],
|
||||
('d', 6): [1],
|
||||
('d', 7): [1],
|
||||
('ret', 9): [3, 8]
|
||||
('ret', 9): [3, 8],
|
||||
}
|
||||
|
||||
ud, du = dataflow.build_def_use(graph_mock, mock.sentinel)
|
||||
@ -248,21 +243,15 @@ class DataflowTest(unittest.TestCase):
|
||||
graph_mock.rpo = [n1, n2, n3, n4, n5, n6, n7]
|
||||
|
||||
mock_analysis = mock_reach_def.return_value
|
||||
mock_analysis.def_to_loc = {
|
||||
0: {0, 4, 5, 7},
|
||||
1: {6},
|
||||
2: {-1},
|
||||
3: {-2}
|
||||
}
|
||||
mock_analysis.def_to_loc = {0: {0, 4, 5, 7}, 1: {6}, 2: {-1}, 3: {-2}}
|
||||
mock_analysis.defs = {
|
||||
n1: {0: {0}},
|
||||
n2: {},
|
||||
n3: {},
|
||||
n4: {0: {4}},
|
||||
n5: {0: {5}},
|
||||
n6: {0: {7},
|
||||
1: {6}},
|
||||
n7: {}
|
||||
n6: {0: {7}, 1: {6}},
|
||||
n7: {},
|
||||
}
|
||||
|
||||
mock_analysis.R = {
|
||||
@ -272,7 +261,7 @@ class DataflowTest(unittest.TestCase):
|
||||
n4: {0, -2, -1},
|
||||
n5: {0, -2, -1},
|
||||
n6: {0, -1, -2},
|
||||
n7: {4, -1, 6, 7, -2, 5}
|
||||
n7: {4, -1, 6, 7, -2, 5},
|
||||
}
|
||||
|
||||
expected_du = {
|
||||
@ -282,7 +271,7 @@ class DataflowTest(unittest.TestCase):
|
||||
(0, 7): [8],
|
||||
(1, 6): [7],
|
||||
(2, -1): [6, 1],
|
||||
(3, -2): [2, 3]
|
||||
(3, -2): [2, 3],
|
||||
}
|
||||
expected_ud = {
|
||||
(0, 5): [0],
|
||||
@ -292,7 +281,7 @@ class DataflowTest(unittest.TestCase):
|
||||
(2, 1): [-1],
|
||||
(2, 6): [-1],
|
||||
(3, 2): [-2],
|
||||
(3, 3): [-2]
|
||||
(3, 3): [-2],
|
||||
}
|
||||
|
||||
ud, du = dataflow.build_def_use(graph_mock, mock.sentinel)
|
||||
@ -307,7 +296,7 @@ class DataflowTest(unittest.TestCase):
|
||||
('c', 6): [8],
|
||||
('d', 1): [3, 4, 5, 6, 7],
|
||||
('ret', 3): [9],
|
||||
('ret', 8): [9]
|
||||
('ret', 8): [9],
|
||||
}
|
||||
ud = {
|
||||
('a', 0): [-1],
|
||||
@ -322,14 +311,14 @@ class DataflowTest(unittest.TestCase):
|
||||
('d', 5): [1],
|
||||
('d', 6): [1],
|
||||
('d', 7): [1],
|
||||
('ret', 9): [3, 8]
|
||||
('ret', 9): [3, 8],
|
||||
}
|
||||
expected_groups = {
|
||||
'a': [([-1], [0])],
|
||||
'b': [([-2], [1])],
|
||||
'c': [([0, 6], [8, 2, 5, 6, 7])],
|
||||
'd': [([1], [3, 4, 5, 6, 7])],
|
||||
'ret': [([3, 8], [9])]
|
||||
'ret': [([3, 8], [9])],
|
||||
}
|
||||
groups = dataflow.group_variables(['a', 'b', 'c', 'd', 'ret'], du, ud)
|
||||
self.assertCountEqual(groups, expected_groups)
|
||||
@ -342,7 +331,7 @@ class DataflowTest(unittest.TestCase):
|
||||
(0, 7): [8],
|
||||
(1, 6): [7],
|
||||
(2, -1): [6, 1],
|
||||
(3, -2): [2, 3]
|
||||
(3, -2): [2, 3],
|
||||
}
|
||||
ud = {
|
||||
(0, 5): [0],
|
||||
@ -352,14 +341,14 @@ class DataflowTest(unittest.TestCase):
|
||||
(2, 1): [-1],
|
||||
(2, 6): [-1],
|
||||
(3, 2): [-2],
|
||||
(3, 3): [-2]
|
||||
(3, 3): [-2],
|
||||
}
|
||||
groups = dataflow.group_variables([0, 1, 2, 3], du, ud)
|
||||
expected_groups = {
|
||||
0: [([0], [5, 7]), ([4, 5, 7], [8])],
|
||||
1: [([6], [7])],
|
||||
2: [([-1], [1, 6])],
|
||||
3: [([-2], [2, 3])]
|
||||
3: [([-2], [2, 3])],
|
||||
}
|
||||
self.assertCountEqual(groups, expected_groups)
|
||||
for entry in groups:
|
||||
@ -372,11 +361,12 @@ class DataflowTest(unittest.TestCase):
|
||||
'b': [([-2], [1])],
|
||||
'c': [([0, 6], [2, 5, 6, 7, 8])],
|
||||
'd': [([1], [3, 4, 5, 6, 7])],
|
||||
'ret': [([3, 8], [9])]
|
||||
'ret': [([3, 8], [9])],
|
||||
}
|
||||
group_variables_mock.return_value = group
|
||||
dataflow.split_variables(
|
||||
mock.sentinel, [0, 1, 2, 3, 4], mock.sentinel, mock.sentinel)
|
||||
mock.sentinel, [0, 1, 2, 3, 4], mock.sentinel, mock.sentinel
|
||||
)
|
||||
|
||||
@mock.patch.object(dataflow, 'group_variables')
|
||||
def testSplitVariablesIfBool(self, group_variables_mock):
|
||||
@ -384,7 +374,7 @@ class DataflowTest(unittest.TestCase):
|
||||
0: [([0], [5, 7]), ([4, 5, 7], [8])],
|
||||
1: [([6], [7])],
|
||||
2: [([-1], [1, 6])],
|
||||
3: [([-2], [2, 3])]
|
||||
3: [([-2], [2, 3])],
|
||||
}
|
||||
group_variables_mock.return_value = group
|
||||
param1_mock = mock.Mock()
|
||||
@ -399,7 +389,7 @@ class DataflowTest(unittest.TestCase):
|
||||
(0, 7): [8],
|
||||
(1, 6): [7],
|
||||
(2, -1): [6, 1],
|
||||
(3, -2): [2, 3]
|
||||
(3, -2): [2, 3],
|
||||
}
|
||||
ud = {
|
||||
(0, 5): [0],
|
||||
@ -409,7 +399,7 @@ class DataflowTest(unittest.TestCase):
|
||||
(2, 1): [-1],
|
||||
(2, 6): [-1],
|
||||
(3, 2): [-2],
|
||||
(3, 3): [-2]
|
||||
(3, 3): [-2],
|
||||
}
|
||||
graph_mock = mock.Mock()
|
||||
dataflow.split_variables(graph_mock, lvars, du, ud)
|
||||
@ -421,7 +411,7 @@ class DataflowTest(unittest.TestCase):
|
||||
(4, 0): [7, 5],
|
||||
(5, 4): [8],
|
||||
(5, 5): [8],
|
||||
(5, 7): [8]
|
||||
(5, 7): [8],
|
||||
}
|
||||
expected_ud = {
|
||||
(1, 7): [6],
|
||||
@ -431,7 +421,7 @@ class DataflowTest(unittest.TestCase):
|
||||
(3, 3): [-2],
|
||||
(4, 5): [0],
|
||||
(4, 7): [0],
|
||||
(5, 8): [4, 5, 7]
|
||||
(5, 8): [4, 5, 7],
|
||||
}
|
||||
self.assertEqual(du, expected_du)
|
||||
self.assertEqual(ud, expected_ud)
|
||||
|
@ -1,6 +1,7 @@
|
||||
"""Tests for graph."""
|
||||
|
||||
import unittest
|
||||
|
||||
from androguard.decompiler import graph
|
||||
|
||||
|
||||
@ -25,7 +26,7 @@ class DominatorTest(unittest.TestCase):
|
||||
'i': ['k'],
|
||||
'j': ['i'],
|
||||
'k': ['i', 'r'],
|
||||
'l': ['h']
|
||||
'l': ['h'],
|
||||
}
|
||||
expected_dominators = {
|
||||
'r': None,
|
||||
@ -40,12 +41,14 @@ class DominatorTest(unittest.TestCase):
|
||||
'i': 'r',
|
||||
'j': 'g',
|
||||
'k': 'r',
|
||||
'l': 'd'
|
||||
'l': 'd',
|
||||
}
|
||||
self.graph.entry = 'r'
|
||||
self.graph.edges = edges
|
||||
self.graph.nodes = list(expected_dominators.keys())
|
||||
self.assertEqual(expected_dominators, self.graph.immediate_dominators())
|
||||
self.assertEqual(
|
||||
expected_dominators, self.graph.immediate_dominators()
|
||||
)
|
||||
|
||||
def testFirstGraph(self):
|
||||
edges = {
|
||||
@ -68,7 +71,7 @@ class DominatorTest(unittest.TestCase):
|
||||
'z2': ['z1', 'z3'],
|
||||
'z3': ['z2', 'z4'],
|
||||
'z4': ['z3', 'z5'],
|
||||
'z5': ['z4']
|
||||
'z5': ['z4'],
|
||||
}
|
||||
expected_dominators = {
|
||||
'r': None,
|
||||
@ -91,12 +94,14 @@ class DominatorTest(unittest.TestCase):
|
||||
'z2': 'r',
|
||||
'z3': 'r',
|
||||
'z4': 'r',
|
||||
'z5': 'r'
|
||||
'z5': 'r',
|
||||
}
|
||||
self.graph.entry = 'r'
|
||||
self.graph.edges = edges
|
||||
self.graph.nodes = list(expected_dominators.keys())
|
||||
self.assertEqual(expected_dominators, self.graph.immediate_dominators())
|
||||
self.assertEqual(
|
||||
expected_dominators, self.graph.immediate_dominators()
|
||||
)
|
||||
|
||||
def testSecondGraph(self):
|
||||
edges = {
|
||||
@ -106,7 +111,7 @@ class DominatorTest(unittest.TestCase):
|
||||
'x11': ['x12', 'x22'],
|
||||
'x12': ['x11'],
|
||||
'x21': ['x22'],
|
||||
'x22': ['x21']
|
||||
'x22': ['x21'],
|
||||
}
|
||||
expected_dominators = {
|
||||
'r': None,
|
||||
@ -115,12 +120,14 @@ class DominatorTest(unittest.TestCase):
|
||||
'x11': 'r',
|
||||
'x12': 'r',
|
||||
'x21': 'r',
|
||||
'x22': 'r'
|
||||
'x22': 'r',
|
||||
}
|
||||
self.graph.entry = 'r'
|
||||
self.graph.edges = edges
|
||||
self.graph.nodes = list(expected_dominators.keys())
|
||||
self.assertEqual(expected_dominators, self.graph.immediate_dominators())
|
||||
self.assertEqual(
|
||||
expected_dominators, self.graph.immediate_dominators()
|
||||
)
|
||||
|
||||
def testThirdGraph(self):
|
||||
edges = {
|
||||
@ -128,7 +135,7 @@ class DominatorTest(unittest.TestCase):
|
||||
'w': ['x1', 'x2'],
|
||||
'y1': ['y2'],
|
||||
'y2': ['x2'],
|
||||
'x2': ['x1']
|
||||
'x2': ['x1'],
|
||||
}
|
||||
expected_dominators = {
|
||||
'r': None,
|
||||
@ -137,12 +144,14 @@ class DominatorTest(unittest.TestCase):
|
||||
'y1': 'r',
|
||||
'y2': 'y1',
|
||||
'x1': 'r',
|
||||
'x2': 'r'
|
||||
'x2': 'r',
|
||||
}
|
||||
self.graph.entry = 'r'
|
||||
self.graph.edges = edges
|
||||
self.graph.nodes = list(expected_dominators.keys())
|
||||
self.assertEqual(expected_dominators, self.graph.immediate_dominators())
|
||||
self.assertEqual(
|
||||
expected_dominators, self.graph.immediate_dominators()
|
||||
)
|
||||
|
||||
def testFourthGraph(self):
|
||||
edges = {'r': ['x1', 'y1', 'y2'], 'x1': ['x2'], 'x2': ['y1', 'y2']}
|
||||
@ -151,12 +160,14 @@ class DominatorTest(unittest.TestCase):
|
||||
'x1': 'r',
|
||||
'x2': 'x1',
|
||||
'y1': 'r',
|
||||
'y2': 'r'
|
||||
'y2': 'r',
|
||||
}
|
||||
self.graph.entry = 'r'
|
||||
self.graph.edges = edges
|
||||
self.graph.nodes = list(expected_dominators.keys())
|
||||
self.assertEqual(expected_dominators, self.graph.immediate_dominators())
|
||||
self.assertEqual(
|
||||
expected_dominators, self.graph.immediate_dominators()
|
||||
)
|
||||
|
||||
def testFifthGraph(self):
|
||||
edges = {
|
||||
@ -168,7 +179,7 @@ class DominatorTest(unittest.TestCase):
|
||||
'e': ['c', 'f'],
|
||||
'f': ['i'],
|
||||
'g': ['h'],
|
||||
'h': ['d', 'f', 'i']
|
||||
'h': ['d', 'f', 'i'],
|
||||
}
|
||||
expected_dominators = {
|
||||
'r': None,
|
||||
@ -180,12 +191,14 @@ class DominatorTest(unittest.TestCase):
|
||||
'f': 'b',
|
||||
'g': 'b',
|
||||
'h': 'g',
|
||||
'i': 'r'
|
||||
'i': 'r',
|
||||
}
|
||||
self.graph.entry = 'r'
|
||||
self.graph.edges = edges
|
||||
self.graph.nodes = list(expected_dominators.keys())
|
||||
self.assertEqual(expected_dominators, self.graph.immediate_dominators())
|
||||
self.assertEqual(
|
||||
expected_dominators, self.graph.immediate_dominators()
|
||||
)
|
||||
|
||||
def testLinearVitGraph(self):
|
||||
edges = {
|
||||
@ -198,7 +211,7 @@ class DominatorTest(unittest.TestCase):
|
||||
'x4': ['x3', 'x5'],
|
||||
'x5': ['x4', 'x6'],
|
||||
'x6': ['x5', 'x7'],
|
||||
'x7': ['x6']
|
||||
'x7': ['x6'],
|
||||
}
|
||||
expected_dominators = {
|
||||
'r': None,
|
||||
@ -210,12 +223,14 @@ class DominatorTest(unittest.TestCase):
|
||||
'x4': 'r',
|
||||
'x5': 'r',
|
||||
'x6': 'r',
|
||||
'x7': 'r'
|
||||
'x7': 'r',
|
||||
}
|
||||
self.graph.entry = 'r'
|
||||
self.graph.edges = edges
|
||||
self.graph.nodes = list(expected_dominators.keys())
|
||||
self.assertEqual(expected_dominators, self.graph.immediate_dominators())
|
||||
self.assertEqual(
|
||||
expected_dominators, self.graph.immediate_dominators()
|
||||
)
|
||||
|
||||
def testCrossGraph(self):
|
||||
edges = {
|
||||
@ -225,7 +240,7 @@ class DominatorTest(unittest.TestCase):
|
||||
'c': ['a', 'd', 'g'],
|
||||
'd': ['e'],
|
||||
'e': ['f'],
|
||||
'f': ['a', 'd', 'g']
|
||||
'f': ['a', 'd', 'g'],
|
||||
}
|
||||
expected_dominators = {
|
||||
'r': None,
|
||||
@ -235,12 +250,14 @@ class DominatorTest(unittest.TestCase):
|
||||
'd': 'r',
|
||||
'e': 'd',
|
||||
'f': 'e',
|
||||
'g': 'r'
|
||||
'g': 'r',
|
||||
}
|
||||
self.graph.entry = 'r'
|
||||
self.graph.edges = edges
|
||||
self.graph.nodes = list(expected_dominators.keys())
|
||||
self.assertEqual(expected_dominators, self.graph.immediate_dominators())
|
||||
self.assertEqual(
|
||||
expected_dominators, self.graph.immediate_dominators()
|
||||
)
|
||||
|
||||
def testTVerifyGraph(self):
|
||||
edges = {
|
||||
@ -255,7 +272,7 @@ class DominatorTest(unittest.TestCase):
|
||||
'n9': ['n10', 'n11', 'n12'],
|
||||
'n10': ['n11'],
|
||||
'n11': ['n7'],
|
||||
'n12': ['n10']
|
||||
'n12': ['n10'],
|
||||
}
|
||||
expected_dominators = {
|
||||
'n1': None,
|
||||
@ -269,12 +286,14 @@ class DominatorTest(unittest.TestCase):
|
||||
'n9': 'n1',
|
||||
'n10': 'n1',
|
||||
'n11': 'n1',
|
||||
'n12': 'n1'
|
||||
'n12': 'n1',
|
||||
}
|
||||
self.graph.entry = 'n1'
|
||||
self.graph.edges = edges
|
||||
self.graph.nodes = list(expected_dominators.keys())
|
||||
self.assertEqual(expected_dominators, self.graph.immediate_dominators())
|
||||
self.assertEqual(
|
||||
expected_dominators, self.graph.immediate_dominators()
|
||||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
@ -1,8 +1,8 @@
|
||||
"""Tests for rpo."""
|
||||
|
||||
import unittest
|
||||
from androguard.decompiler import graph
|
||||
from androguard.decompiler import node
|
||||
|
||||
from androguard.decompiler import graph, node
|
||||
|
||||
|
||||
class NodeTest(node.Node):
|
||||
@ -58,7 +58,7 @@ class RpoTest(unittest.TestCase):
|
||||
'i': ['k'],
|
||||
'j': ['i'],
|
||||
'k': ['i', 'r'],
|
||||
'l': ['h']
|
||||
'l': ['h'],
|
||||
}
|
||||
n_map = self._createGraphFrom(edges)
|
||||
self.graph.compute_rpo()
|
||||
@ -76,7 +76,7 @@ class RpoTest(unittest.TestCase):
|
||||
'i': 12,
|
||||
'j': 4,
|
||||
'k': 11,
|
||||
'l': 9
|
||||
'l': 9,
|
||||
}
|
||||
self._verifyRpo(n_map, expected_rpo)
|
||||
|
||||
@ -102,7 +102,7 @@ class RpoTest(unittest.TestCase):
|
||||
'z2': ['z1', 'z3'],
|
||||
'z3': ['z2', 'z4'],
|
||||
'z4': ['z3', 'z5'],
|
||||
'z5': ['z4']
|
||||
'z5': ['z4'],
|
||||
}
|
||||
n_map = self._createGraphFrom(edges)
|
||||
self.graph.compute_rpo()
|
||||
@ -128,7 +128,7 @@ class RpoTest(unittest.TestCase):
|
||||
'z2': 13,
|
||||
'z3': 14,
|
||||
'z4': 15,
|
||||
'z5': 16
|
||||
'z5': 16,
|
||||
}
|
||||
self._verifyRpo(n_map, expected_rpo)
|
||||
|
||||
@ -141,7 +141,7 @@ class RpoTest(unittest.TestCase):
|
||||
'x21': ['x22'],
|
||||
'x22': ['x21'],
|
||||
'y1': ['y2', 'x11'],
|
||||
'y2': ['x21']
|
||||
'y2': ['x21'],
|
||||
}
|
||||
n_map = self._createGraphFrom(edges)
|
||||
self.graph.compute_rpo()
|
||||
@ -153,7 +153,7 @@ class RpoTest(unittest.TestCase):
|
||||
'x21': 6,
|
||||
'x22': 7,
|
||||
'y1': 2,
|
||||
'y2': 5
|
||||
'y2': 5,
|
||||
}
|
||||
self._verifyRpo(n_map, expected_rpo)
|
||||
|
||||
@ -164,7 +164,7 @@ class RpoTest(unittest.TestCase):
|
||||
'w': ['x1', 'x2'],
|
||||
'x2': ['x1'],
|
||||
'y1': ['y2'],
|
||||
'y2': ['x2']
|
||||
'y2': ['x2'],
|
||||
}
|
||||
n_map = self._createGraphFrom(edges)
|
||||
self.graph.compute_rpo()
|
||||
@ -177,7 +177,7 @@ class RpoTest(unittest.TestCase):
|
||||
None: 'r',
|
||||
'r': ['x1', 'y1', 'y2'],
|
||||
'x1': ['x2'],
|
||||
'x2': ['y1', 'y2']
|
||||
'x2': ['y1', 'y2'],
|
||||
}
|
||||
n_map = self._createGraphFrom(edges)
|
||||
self.graph.compute_rpo()
|
||||
@ -196,7 +196,7 @@ class RpoTest(unittest.TestCase):
|
||||
'e': ['c', 'f'],
|
||||
'f': ['i'],
|
||||
'g': ['h'],
|
||||
'h': ['d', 'f', 'i']
|
||||
'h': ['d', 'f', 'i'],
|
||||
}
|
||||
n_map = self._createGraphFrom(edges)
|
||||
self.graph.compute_rpo()
|
||||
@ -211,7 +211,7 @@ class RpoTest(unittest.TestCase):
|
||||
'f': 7,
|
||||
'g': 4,
|
||||
'h': 5,
|
||||
'i': 10
|
||||
'i': 10,
|
||||
}
|
||||
self._verifyRpo(n_map, expected_rpo)
|
||||
|
||||
@ -227,7 +227,7 @@ class RpoTest(unittest.TestCase):
|
||||
'x4': ['x3', 'x5'],
|
||||
'x5': ['x4', 'x6'],
|
||||
'x6': ['x5', 'x7'],
|
||||
'x7': ['x6']
|
||||
'x7': ['x6'],
|
||||
}
|
||||
n_map = self._createGraphFrom(edges)
|
||||
self.graph.compute_rpo()
|
||||
@ -242,7 +242,7 @@ class RpoTest(unittest.TestCase):
|
||||
'x5': 8,
|
||||
'x6': 9,
|
||||
'x7': 10,
|
||||
'y': 2
|
||||
'y': 2,
|
||||
}
|
||||
self._verifyRpo(n_map, expected_rpo)
|
||||
|
||||
@ -255,7 +255,7 @@ class RpoTest(unittest.TestCase):
|
||||
'c': ['a', 'd', 'g'],
|
||||
'd': ['e'],
|
||||
'e': ['f'],
|
||||
'f': ['a', 'd', 'g']
|
||||
'f': ['a', 'd', 'g'],
|
||||
}
|
||||
n_map = self._createGraphFrom(edges)
|
||||
self.graph.compute_rpo()
|
||||
@ -268,7 +268,7 @@ class RpoTest(unittest.TestCase):
|
||||
'd': 5,
|
||||
'e': 6,
|
||||
'f': 7,
|
||||
'g': 8
|
||||
'g': 8,
|
||||
}
|
||||
self._verifyRpo(n_map, expected_rpo)
|
||||
|
||||
@ -286,7 +286,7 @@ class RpoTest(unittest.TestCase):
|
||||
'n9': ['n10', 'n11', 'n12'],
|
||||
'n10': ['n11'],
|
||||
'n11': ['n7'],
|
||||
'n12': ['n10']
|
||||
'n12': ['n10'],
|
||||
}
|
||||
n_map = self._createGraphFrom(edges)
|
||||
self.graph.compute_rpo()
|
||||
@ -303,7 +303,7 @@ class RpoTest(unittest.TestCase):
|
||||
'n9': 5,
|
||||
'n10': 7,
|
||||
'n11': 8,
|
||||
'n12': 6
|
||||
'n12': 6,
|
||||
}
|
||||
self._verifyRpo(n_map, expected_rpo)
|
||||
|
||||
|
@ -1,15 +1,15 @@
|
||||
import unittest
|
||||
|
||||
import binascii
|
||||
import os
|
||||
import random
|
||||
import binascii
|
||||
import unittest
|
||||
|
||||
from androguard.core import dex
|
||||
from androguard.misc import AnalyzeAPK
|
||||
|
||||
test_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
class MockClassManager():
|
||||
|
||||
class MockClassManager:
|
||||
@property
|
||||
def packer(self):
|
||||
return dex.DalvikPacker(0x12345678)
|
||||
@ -17,12 +17,13 @@ class MockClassManager():
|
||||
def get_odex_format(self):
|
||||
return False
|
||||
|
||||
|
||||
class VMClassTest(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
test_apk_path = os.path.join(test_dir, 'data/APK/TestActivity.apk')
|
||||
cls.a, cls.d, cls.dx = AnalyzeAPK(test_apk_path)
|
||||
|
||||
|
||||
def testVMClass(self):
|
||||
"""test number of ClassDefItems, StringDataItems, FieldIdItems, and MethodIdItems"""
|
||||
|
||||
@ -46,10 +47,10 @@ class VMClassTest(unittest.TestCase):
|
||||
# field ids, type ids, and method ids references
|
||||
# are not required to be defined in the dex since they can be resolved at runtime via shared library
|
||||
for vm in self.dx.vms:
|
||||
num_class_def_items += vm.get_len_classes() # ClassDefItems
|
||||
num_strings_data_items += vm.get_len_strings() # StringDataItems
|
||||
num_field_id_items += vm.get_len_fields() # FieldIdItems
|
||||
num_method_id_items += vm.get_len_methods() # MethodIdItems
|
||||
num_class_def_items += vm.get_len_classes() # ClassDefItems
|
||||
num_strings_data_items += vm.get_len_strings() # StringDataItems
|
||||
num_field_id_items += vm.get_len_fields() # FieldIdItems
|
||||
num_method_id_items += vm.get_len_methods() # MethodIdItems
|
||||
|
||||
self.assertEqual(len(self.dx.vms), 1)
|
||||
self.assertEqual(num_class_def_items, 340)
|
||||
@ -60,68 +61,62 @@ class VMClassTest(unittest.TestCase):
|
||||
def testAccessflags(self):
|
||||
class_name_accessflag_map = {
|
||||
'Ltests/androguard/TestLoops;': {
|
||||
'access_flag': 0x1, # public
|
||||
'access_flag': 0x1, # public
|
||||
'methods': {
|
||||
'<init>': 0x1 | 0x10000, # public | constructor
|
||||
'testBreak': 0x1, # public
|
||||
'testBreak2': 0x1,
|
||||
'testBreak3': 0x1,
|
||||
'testBreak4': 0x1,
|
||||
'testBreakDoWhile': 0x1,
|
||||
'testBreakMid': 0x1,
|
||||
'testBreakbis': 0x1,
|
||||
'<init>': 0x1 | 0x10000, # public | constructor
|
||||
'testBreak': 0x1, # public
|
||||
'testBreak2': 0x1,
|
||||
'testBreak3': 0x1,
|
||||
'testBreak4': 0x1,
|
||||
'testBreakDoWhile': 0x1,
|
||||
'testBreakMid': 0x1,
|
||||
'testBreakbis': 0x1,
|
||||
'testDiffWhileDoWhile': 0x1,
|
||||
'testDoWhile': 0x1,
|
||||
'testDoWhileTrue': 0x1,
|
||||
'testFor': 0x1,
|
||||
'testIrreducible': 0x1,
|
||||
'testMultipleLoops': 0x1,
|
||||
'testNestedLoops': 0x1,
|
||||
'testReducible': 0x1,
|
||||
'testWhile': 0x1,
|
||||
'testWhile2': 0x1,
|
||||
'testWhile3': 0x1,
|
||||
'testWhile4': 0x1,
|
||||
'testWhile5': 0x1,
|
||||
'testWhileTrue': 0x1,
|
||||
'testDoWhile': 0x1,
|
||||
'testDoWhileTrue': 0x1,
|
||||
'testFor': 0x1,
|
||||
'testIrreducible': 0x1,
|
||||
'testMultipleLoops': 0x1,
|
||||
'testNestedLoops': 0x1,
|
||||
'testReducible': 0x1,
|
||||
'testWhile': 0x1,
|
||||
'testWhile2': 0x1,
|
||||
'testWhile3': 0x1,
|
||||
'testWhile4': 0x1,
|
||||
'testWhile5': 0x1,
|
||||
'testWhileTrue': 0x1,
|
||||
},
|
||||
'fields': {
|
||||
}
|
||||
'fields': {},
|
||||
},
|
||||
'Ltests/androguard/TestLoops$Loop;': {
|
||||
'access_flag': 0x1, # public
|
||||
'access_flag': 0x1, # public
|
||||
'methods': {
|
||||
'<init>': 0x4 | 0x10000 # protected | constructor
|
||||
'<init>': 0x4 | 0x10000 # protected | constructor
|
||||
},
|
||||
'fields': {
|
||||
'i': 0x1 | 0x8, # public | static
|
||||
'j': 0x1 | 0x8 # public | static
|
||||
}
|
||||
'i': 0x1 | 0x8, # public | static
|
||||
'j': 0x1 | 0x8, # public | static
|
||||
},
|
||||
},
|
||||
'Ltests/androguard/TestIfs;': {
|
||||
'access_flag': 0x1, # public
|
||||
'Ltests/androguard/TestIfs;': {
|
||||
'access_flag': 0x1, # public
|
||||
'methods': {
|
||||
'<init>': 0x1 | 0x10000, # public | constructor
|
||||
'testIF': 0x1 | 0x8, # public | static
|
||||
'testIF2': 0x1 | 0x8,
|
||||
'testIF3': 0x1 | 0x8,
|
||||
'testIF4': 0x1 | 0x8,
|
||||
'testIF5': 0x1 | 0x8,
|
||||
'testIfBool': 0x1 | 0x8,
|
||||
'testShortCircuit': 0x1 | 0x8,
|
||||
'testShortCircuit2': 0x1 | 0x8,
|
||||
'testShortCircuit3': 0x1 | 0x8,
|
||||
'testShortCircuit4': 0x1 | 0x8,
|
||||
'testCFG': 0x1, # public
|
||||
'testCFG2': 0x1
|
||||
'<init>': 0x1 | 0x10000, # public | constructor
|
||||
'testIF': 0x1 | 0x8, # public | static
|
||||
'testIF2': 0x1 | 0x8,
|
||||
'testIF3': 0x1 | 0x8,
|
||||
'testIF4': 0x1 | 0x8,
|
||||
'testIF5': 0x1 | 0x8,
|
||||
'testIfBool': 0x1 | 0x8,
|
||||
'testShortCircuit': 0x1 | 0x8,
|
||||
'testShortCircuit2': 0x1 | 0x8,
|
||||
'testShortCircuit3': 0x1 | 0x8,
|
||||
'testShortCircuit4': 0x1 | 0x8,
|
||||
'testCFG': 0x1, # public
|
||||
'testCFG2': 0x1,
|
||||
},
|
||||
'fields': {
|
||||
'P': 0x2, # private
|
||||
'Q': 0x2,
|
||||
'R': 0x2,
|
||||
'S': 0x2
|
||||
}
|
||||
}
|
||||
'fields': {'P': 0x2, 'Q': 0x2, 'R': 0x2, 'S': 0x2}, # private
|
||||
},
|
||||
}
|
||||
|
||||
# ensure these classes exist in the Analysis
|
||||
@ -129,32 +124,52 @@ class VMClassTest(unittest.TestCase):
|
||||
self.assertTrue(self.dx.is_class_present(expected_class_name))
|
||||
|
||||
# test access flags for classes
|
||||
for expected_class_name, class_data in class_name_accessflag_map.items():
|
||||
for (
|
||||
expected_class_name,
|
||||
class_data,
|
||||
) in class_name_accessflag_map.items():
|
||||
class_analysis = self.dx.get_class_analysis(expected_class_name)
|
||||
class_access_flags = class_analysis.get_vm_class().get_access_flags()
|
||||
class_access_flags = (
|
||||
class_analysis.get_vm_class().get_access_flags()
|
||||
)
|
||||
self.assertEqual(class_access_flags, class_data['access_flag'])
|
||||
|
||||
# test access flags for methods
|
||||
encoded_methods = class_analysis.get_vm_class().get_methods()
|
||||
for expected_method_name, expected_access_flags in class_data['methods'].items():
|
||||
for expected_method_name, expected_access_flags in class_data[
|
||||
'methods'
|
||||
].items():
|
||||
# ensure this method is in the class
|
||||
self.assertIn(expected_method_name, [method.name for method in encoded_methods])
|
||||
|
||||
self.assertIn(
|
||||
expected_method_name,
|
||||
[method.name for method in encoded_methods],
|
||||
)
|
||||
|
||||
# ensure access flags match
|
||||
for method in encoded_methods:
|
||||
if method.name == expected_method_name:
|
||||
self.assertEqual(method.get_access_flags(), expected_access_flags)
|
||||
self.assertEqual(
|
||||
method.get_access_flags(), expected_access_flags
|
||||
)
|
||||
|
||||
# test access flags for fields
|
||||
encoded_fields = class_analysis.get_vm_class().get_fields()
|
||||
for expected_field_name, expected_access_flags in class_data['fields'].items():
|
||||
for expected_field_name, expected_access_flags in class_data[
|
||||
'fields'
|
||||
].items():
|
||||
# ensure this field is in the class
|
||||
self.assertIn(expected_field_name, [field.name for field in encoded_fields])
|
||||
self.assertIn(
|
||||
expected_field_name,
|
||||
[field.name for field in encoded_fields],
|
||||
)
|
||||
|
||||
# ensure access flags match
|
||||
for field in encoded_fields:
|
||||
if field.name == expected_field_name:
|
||||
self.assertEqual(field.get_access_flags(), expected_access_flags)
|
||||
self.assertEqual(
|
||||
field.get_access_flags(), expected_access_flags
|
||||
)
|
||||
|
||||
|
||||
class InstructionTest(unittest.TestCase):
|
||||
def testInstructions(self):
|
||||
@ -188,14 +203,25 @@ class InstructionTest(unittest.TestCase):
|
||||
self.assertEqual(instruction.get_raw(), bytecode)
|
||||
|
||||
# Test with some pseudorandom stuff
|
||||
if ins.__name__ in ['Instruction10x', 'Instruction20t', 'Instruction30t', 'Instruction32x',
|
||||
'Instruction45cc']:
|
||||
if ins.__name__ in [
|
||||
'Instruction10x',
|
||||
'Instruction20t',
|
||||
'Instruction30t',
|
||||
'Instruction32x',
|
||||
'Instruction45cc',
|
||||
]:
|
||||
# note this only works for certain opcode (which are not forced to 0 in certain places)
|
||||
# Thus we need to make sure these places are zero.
|
||||
# Instruction45cc: Has constrained regarding the parameter AA
|
||||
bytecode = bytearray([op_value, 0] + [random.randint(0x00, 0xff) for _ in range(length - 2)])
|
||||
bytecode = bytearray(
|
||||
[op_value, 0]
|
||||
+ [random.randint(0x00, 0xFF) for _ in range(length - 2)]
|
||||
)
|
||||
else:
|
||||
bytecode = bytearray([op_value] + [random.randint(0x00, 0xff) for _ in range(length - 1)])
|
||||
bytecode = bytearray(
|
||||
[op_value]
|
||||
+ [random.randint(0x00, 0xFF) for _ in range(length - 1)]
|
||||
)
|
||||
instruction = ins(MockClassManager(), bytecode)
|
||||
self.assertIsInstance(instruction, dex.Instruction)
|
||||
self.assertEqual(instruction.get_op_value(), op_value)
|
||||
@ -203,7 +229,9 @@ class InstructionTest(unittest.TestCase):
|
||||
|
||||
def testNOP(self):
|
||||
"""test if NOP instructions are parsed"""
|
||||
instruction = dex.Instruction10x(MockClassManager(), bytearray(b"\x00\x00"))
|
||||
instruction = dex.Instruction10x(
|
||||
MockClassManager(), bytearray(b"\x00\x00")
|
||||
)
|
||||
self.assertEqual(instruction.get_name(), "nop")
|
||||
|
||||
def testLinearSweep(self):
|
||||
@ -212,7 +240,9 @@ class InstructionTest(unittest.TestCase):
|
||||
instructions = ['nop', 'nop', 'nop', 'return-void']
|
||||
l = 0
|
||||
|
||||
for ins in dex.LinearSweepAlgorithm.get_instructions(MockClassManager(), 4, bytecode, 0):
|
||||
for ins in dex.LinearSweepAlgorithm.get_instructions(
|
||||
MockClassManager(), 4, bytecode, 0
|
||||
):
|
||||
self.assertIsInstance(ins, dex.Instruction10x)
|
||||
self.assertEqual(ins.get_length(), 2)
|
||||
self.assertEqual(ins.get_name(), instructions.pop(0))
|
||||
@ -223,32 +253,53 @@ class InstructionTest(unittest.TestCase):
|
||||
|
||||
def testLinearSweepStrings(self):
|
||||
# very basic function, strings and invokes
|
||||
bytecode = bytearray(binascii.unhexlify('1A000F001A0100001A0214001A0311001A0415001A0413001A0508001A061200'
|
||||
'1A0716001A081000620900006E2002000900620000006E200200100062000000'
|
||||
'6E2002002000620000006E2002003000620000006E2002003000620000006E20'
|
||||
'02004000620000006E2002005000620000006E2002006000620000006E200200'
|
||||
'7000620000006E20020080000E00'))
|
||||
bytecode = bytearray(
|
||||
binascii.unhexlify(
|
||||
'1A000F001A0100001A0214001A0311001A0415001A0413001A0508001A061200'
|
||||
'1A0716001A081000620900006E2002000900620000006E200200100062000000'
|
||||
'6E2002002000620000006E2002003000620000006E2002003000620000006E20'
|
||||
'02004000620000006E2002005000620000006E2002006000620000006E200200'
|
||||
'7000620000006E20020080000E00'
|
||||
)
|
||||
)
|
||||
instructions = [
|
||||
'const-string', 'const-string',
|
||||
'const-string', 'const-string',
|
||||
'const-string', 'const-string',
|
||||
'const-string', 'const-string',
|
||||
'const-string', 'const-string',
|
||||
'sget-object', 'invoke-virtual',
|
||||
'sget-object', 'invoke-virtual',
|
||||
'sget-object', 'invoke-virtual',
|
||||
'sget-object', 'invoke-virtual',
|
||||
'sget-object', 'invoke-virtual',
|
||||
'sget-object', 'invoke-virtual',
|
||||
'sget-object', 'invoke-virtual',
|
||||
'sget-object', 'invoke-virtual',
|
||||
'sget-object', 'invoke-virtual',
|
||||
'sget-object', 'invoke-virtual',
|
||||
'const-string',
|
||||
'const-string',
|
||||
'const-string',
|
||||
'const-string',
|
||||
'const-string',
|
||||
'const-string',
|
||||
'const-string',
|
||||
'const-string',
|
||||
'const-string',
|
||||
'const-string',
|
||||
'sget-object',
|
||||
'invoke-virtual',
|
||||
'sget-object',
|
||||
'invoke-virtual',
|
||||
'sget-object',
|
||||
'invoke-virtual',
|
||||
'sget-object',
|
||||
'invoke-virtual',
|
||||
'sget-object',
|
||||
'invoke-virtual',
|
||||
'sget-object',
|
||||
'invoke-virtual',
|
||||
'sget-object',
|
||||
'invoke-virtual',
|
||||
'sget-object',
|
||||
'invoke-virtual',
|
||||
'sget-object',
|
||||
'invoke-virtual',
|
||||
'sget-object',
|
||||
'invoke-virtual',
|
||||
'return-void',
|
||||
]
|
||||
l = 0
|
||||
|
||||
for ins in dex.LinearSweepAlgorithm.get_instructions(MockClassManager(), 71, bytecode, 0):
|
||||
for ins in dex.LinearSweepAlgorithm.get_instructions(
|
||||
MockClassManager(), 71, bytecode, 0
|
||||
):
|
||||
self.assertIsInstance(ins, dex.Instruction)
|
||||
self.assertEqual(ins.get_name(), instructions.pop(0))
|
||||
l += ins.get_length()
|
||||
@ -259,8 +310,12 @@ class InstructionTest(unittest.TestCase):
|
||||
|
||||
def testLinearSweepSwitch(self):
|
||||
"""test if switch payloads are unpacked correctly"""
|
||||
bytecode = bytearray(binascii.unhexlify('2B02140000001300110038030400130063000F001300170028F913002A0028F6'
|
||||
'1300480028F3000000010300010000000A0000000D00000010000000'))
|
||||
bytecode = bytearray(
|
||||
binascii.unhexlify(
|
||||
'2B02140000001300110038030400130063000F001300170028F913002A0028F6'
|
||||
'1300480028F3000000010300010000000A0000000D00000010000000'
|
||||
)
|
||||
)
|
||||
|
||||
instructions = [
|
||||
'packed-switch',
|
||||
@ -279,7 +334,9 @@ class InstructionTest(unittest.TestCase):
|
||||
]
|
||||
l = 0
|
||||
|
||||
for ins in dex.LinearSweepAlgorithm.get_instructions(MockClassManager(), 30, bytecode, 0):
|
||||
for ins in dex.LinearSweepAlgorithm.get_instructions(
|
||||
MockClassManager(), 30, bytecode, 0
|
||||
):
|
||||
if len(instructions) > 1:
|
||||
self.assertIsInstance(ins, dex.Instruction)
|
||||
else:
|
||||
@ -292,21 +349,41 @@ class InstructionTest(unittest.TestCase):
|
||||
|
||||
def testLSAArrays(self):
|
||||
"""Test if fill-array-data-payload is parsed"""
|
||||
bytecode = bytearray(binascii.unhexlify('12412310030026002D0000005B30000012702300050026002B0000005B300300'
|
||||
'1250230004002600350000005B300100231007002600380000005B3002001220'
|
||||
'2300060012011A020D004D02000112111A0211004D0200015B3004000E000000'
|
||||
'0003010004000000141E28320003040007000000010000000200000003000000'
|
||||
'0400000005000000E70300000A899D0000030200050000006100620078007A00'
|
||||
'63000000000302000400000005000A000F001400'))
|
||||
bytecode = bytearray(
|
||||
binascii.unhexlify(
|
||||
'12412310030026002D0000005B30000012702300050026002B0000005B300300'
|
||||
'1250230004002600350000005B300100231007002600380000005B3002001220'
|
||||
'2300060012011A020D004D02000112111A0211004D0200015B3004000E000000'
|
||||
'0003010004000000141E28320003040007000000010000000200000003000000'
|
||||
'0400000005000000E70300000A899D0000030200050000006100620078007A00'
|
||||
'63000000000302000400000005000A000F001400'
|
||||
)
|
||||
)
|
||||
|
||||
instructions = [
|
||||
'const/4', 'new-array', 'fill-array-data', 'iput-object',
|
||||
'const/4', 'new-array', 'fill-array-data', 'iput-object',
|
||||
'const/4', 'new-array', 'fill-array-data', 'iput-object',
|
||||
'new-array', 'fill-array-data', 'iput-object',
|
||||
'const/4', 'new-array',
|
||||
'const/4', 'const-string', 'aput-object',
|
||||
'const/4', 'const-string', 'aput-object',
|
||||
'const/4',
|
||||
'new-array',
|
||||
'fill-array-data',
|
||||
'iput-object',
|
||||
'const/4',
|
||||
'new-array',
|
||||
'fill-array-data',
|
||||
'iput-object',
|
||||
'const/4',
|
||||
'new-array',
|
||||
'fill-array-data',
|
||||
'iput-object',
|
||||
'new-array',
|
||||
'fill-array-data',
|
||||
'iput-object',
|
||||
'const/4',
|
||||
'new-array',
|
||||
'const/4',
|
||||
'const-string',
|
||||
'aput-object',
|
||||
'const/4',
|
||||
'const-string',
|
||||
'aput-object',
|
||||
'iput-object',
|
||||
'return-void',
|
||||
'nop', # alignment
|
||||
@ -321,7 +398,9 @@ class InstructionTest(unittest.TestCase):
|
||||
# array information: (element_width, size)
|
||||
arrays = [(1, 4), (4, 7), (2, 5), (2, 4)]
|
||||
|
||||
for ins in dex.LinearSweepAlgorithm.get_instructions(MockClassManager(), 90, bytecode, 0):
|
||||
for ins in dex.LinearSweepAlgorithm.get_instructions(
|
||||
MockClassManager(), 90, bytecode, 0
|
||||
):
|
||||
self.assertEqual(ins.get_name(), instructions.pop(0))
|
||||
if ins.get_name() != 'fill-array-data-payload':
|
||||
self.assertIsInstance(ins, dex.Instruction)
|
||||
@ -340,75 +419,140 @@ class InstructionTest(unittest.TestCase):
|
||||
def testWrongInstructions(self):
|
||||
"""Test if unknown instructions raise an InvalidInstruction error"""
|
||||
with self.assertRaises(dex.InvalidInstruction):
|
||||
ins = list(dex.LinearSweepAlgorithm.get_instructions(MockClassManager(), 1, bytearray(b"\xff\xab"), 0))
|
||||
ins = list(
|
||||
dex.LinearSweepAlgorithm.get_instructions(
|
||||
MockClassManager(), 1, bytearray(b"\xff\xab"), 0
|
||||
)
|
||||
)
|
||||
|
||||
with self.assertRaises(dex.InvalidInstruction):
|
||||
ins = list(dex.LinearSweepAlgorithm.get_instructions(MockClassManager(), 2, bytearray(b"\x00\x00"
|
||||
b"\xff\xab"), 0))
|
||||
ins = list(
|
||||
dex.LinearSweepAlgorithm.get_instructions(
|
||||
MockClassManager(),
|
||||
2,
|
||||
bytearray(b"\x00\x00" b"\xff\xab"),
|
||||
0,
|
||||
)
|
||||
)
|
||||
|
||||
def testIncompleteInstruction(self):
|
||||
"""Test if incomplete bytecode log an error"""
|
||||
# Test if instruction can be parsed
|
||||
self.assertIsInstance(dex.Instruction51l(MockClassManager(),
|
||||
bytearray(b'\x18\x01\x23\x23\x00\xff\x99\x11\x22\x22')),
|
||||
dex.Instruction51l)
|
||||
self.assertIsInstance(
|
||||
dex.Instruction51l(
|
||||
MockClassManager(),
|
||||
bytearray(b'\x18\x01\x23\x23\x00\xff\x99\x11\x22\x22'),
|
||||
),
|
||||
dex.Instruction51l,
|
||||
)
|
||||
|
||||
with self.assertRaises(dex.InvalidInstruction):
|
||||
ins = list(
|
||||
dex.LinearSweepAlgorithm.get_instructions(MockClassManager(), 5, bytearray(b"\x18\x01\xff\xff"), 0))
|
||||
dex.LinearSweepAlgorithm.get_instructions(
|
||||
MockClassManager(), 5, bytearray(b"\x18\x01\xff\xff"), 0
|
||||
)
|
||||
)
|
||||
|
||||
def testInstruction21h(self):
|
||||
"""Test function of Instruction 21h used for const{,-wide}/high16"""
|
||||
ins = dex.Instruction21h(MockClassManager(), bytearray([0x15, 0x00, 0x42, 0x11]))
|
||||
ins = dex.Instruction21h(
|
||||
MockClassManager(), bytearray([0x15, 0x00, 0x42, 0x11])
|
||||
)
|
||||
self.assertEqual(ins.get_op_value(), 0x15)
|
||||
self.assertEqual(ins.get_literals(), [0x11420000])
|
||||
self.assertEqual(ins.get_operands(), [(dex.Operand.REGISTER, 0x00), (dex.Operand.LITERAL, 0x11420000)])
|
||||
self.assertEqual(
|
||||
ins.get_operands(),
|
||||
[(dex.Operand.REGISTER, 0x00), (dex.Operand.LITERAL, 0x11420000)],
|
||||
)
|
||||
self.assertEqual(ins.get_name(), 'const/high16')
|
||||
self.assertEqual(ins.get_output(), 'v0, 289538048')
|
||||
self.assertEqual(ins.get_raw(), bytearray([0x15, 0x00, 0x42, 0x11]))
|
||||
|
||||
ins = dex.Instruction21h(MockClassManager(), bytearray([0x19, 0x00, 0x42, 0x11]))
|
||||
ins = dex.Instruction21h(
|
||||
MockClassManager(), bytearray([0x19, 0x00, 0x42, 0x11])
|
||||
)
|
||||
self.assertEqual(ins.get_op_value(), 0x19)
|
||||
self.assertEqual(ins.get_literals(), [0x1142000000000000])
|
||||
self.assertEqual(ins.get_operands(), [(dex.Operand.REGISTER, 0x00), (dex.Operand.LITERAL, 0x1142000000000000)])
|
||||
self.assertEqual(
|
||||
ins.get_operands(),
|
||||
[
|
||||
(dex.Operand.REGISTER, 0x00),
|
||||
(dex.Operand.LITERAL, 0x1142000000000000),
|
||||
],
|
||||
)
|
||||
self.assertEqual(ins.get_name(), 'const-wide/high16')
|
||||
self.assertEqual(ins.get_output(), 'v0, 1243556447107678208')
|
||||
self.assertEqual(ins.get_raw(), bytearray([0x19, 0x00, 0x42, 0x11]))
|
||||
|
||||
ins = dex.Instruction21h(MockClassManager(), bytearray([0x19, 0x00, 0xbe, 0xff]))
|
||||
ins = dex.Instruction21h(
|
||||
MockClassManager(), bytearray([0x19, 0x00, 0xBE, 0xFF])
|
||||
)
|
||||
self.assertEqual(ins.get_op_value(), 0x19)
|
||||
self.assertEqual(ins.get_literals(), [-18577348462903296])
|
||||
self.assertEqual(ins.get_operands(), [(dex.Operand.REGISTER, 0x00), (dex.Operand.LITERAL, -18577348462903296)])
|
||||
self.assertEqual(
|
||||
ins.get_operands(),
|
||||
[
|
||||
(dex.Operand.REGISTER, 0x00),
|
||||
(dex.Operand.LITERAL, -18577348462903296),
|
||||
],
|
||||
)
|
||||
self.assertEqual(ins.get_name(), 'const-wide/high16')
|
||||
self.assertEqual(ins.get_output(), 'v0, -18577348462903296')
|
||||
self.assertEqual(ins.get_raw(), bytearray([0x19, 0x00, 0xbe, 0xff]))
|
||||
self.assertEqual(ins.get_raw(), bytearray([0x19, 0x00, 0xBE, 0xFF]))
|
||||
|
||||
def testInstruction51l(self):
|
||||
"""test the functionality of const-wide"""
|
||||
ins = dex.Instruction51l(MockClassManager(),
|
||||
bytearray([0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]))
|
||||
ins = dex.Instruction51l(
|
||||
MockClassManager(),
|
||||
bytearray(
|
||||
[0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
|
||||
),
|
||||
)
|
||||
self.assertEqual(ins.get_op_value(), 0x18)
|
||||
self.assertEqual(ins.get_literals(), [0])
|
||||
self.assertEqual(ins.get_operands(), [(dex.Operand.REGISTER, 0x00), (dex.Operand.LITERAL, 0)])
|
||||
self.assertEqual(
|
||||
ins.get_operands(),
|
||||
[(dex.Operand.REGISTER, 0x00), (dex.Operand.LITERAL, 0)],
|
||||
)
|
||||
self.assertEqual(ins.get_name(), 'const-wide')
|
||||
self.assertEqual(ins.get_output(), 'v0, 0')
|
||||
self.assertEqual(ins.get_raw(), bytearray([0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]))
|
||||
self.assertEqual(
|
||||
ins.get_raw(),
|
||||
bytearray(
|
||||
[0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
|
||||
),
|
||||
)
|
||||
|
||||
bytecode = bytearray([0x18, 0x00, 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x70])
|
||||
bytecode = bytearray(
|
||||
[0x18, 0x00, 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x70]
|
||||
)
|
||||
ins = dex.Instruction51l(MockClassManager(), bytecode)
|
||||
self.assertEqual(ins.get_op_value(), 0x18)
|
||||
self.assertEqual(ins.get_literals(), [0x7034129078563412])
|
||||
self.assertEqual(ins.get_operands(), [(dex.Operand.REGISTER, 0x00), (dex.Operand.LITERAL, 0x7034129078563412)])
|
||||
self.assertEqual(
|
||||
ins.get_operands(),
|
||||
[
|
||||
(dex.Operand.REGISTER, 0x00),
|
||||
(dex.Operand.LITERAL, 0x7034129078563412),
|
||||
],
|
||||
)
|
||||
self.assertEqual(ins.get_name(), 'const-wide')
|
||||
self.assertEqual(ins.get_output(), 'v0, 8085107642740388882')
|
||||
self.assertEqual(ins.get_raw(), bytecode)
|
||||
|
||||
bytecode = bytearray([0x18, 0x00, 0xee, 0xcb, 0xa9, 0x87, 0x6f, 0xed, 0xcb, 0x8f])
|
||||
bytecode = bytearray(
|
||||
[0x18, 0x00, 0xEE, 0xCB, 0xA9, 0x87, 0x6F, 0xED, 0xCB, 0x8F]
|
||||
)
|
||||
ins = dex.Instruction51l(MockClassManager(), bytecode)
|
||||
self.assertEqual(ins.get_op_value(), 0x18)
|
||||
self.assertEqual(ins.get_literals(), [-8085107642740388882])
|
||||
self.assertEqual(ins.get_operands(),
|
||||
[(dex.Operand.REGISTER, 0x00), (dex.Operand.LITERAL, -8085107642740388882)])
|
||||
self.assertEqual(
|
||||
ins.get_operands(),
|
||||
[
|
||||
(dex.Operand.REGISTER, 0x00),
|
||||
(dex.Operand.LITERAL, -8085107642740388882),
|
||||
],
|
||||
)
|
||||
self.assertEqual(ins.get_name(), 'const-wide')
|
||||
self.assertEqual(ins.get_output(), 'v0, -8085107642740388882')
|
||||
self.assertEqual(ins.get_raw(), bytecode)
|
||||
@ -427,11 +571,16 @@ class InstructionTest(unittest.TestCase):
|
||||
(0x86, 6, -8),
|
||||
]
|
||||
for args, reg, lit in tests:
|
||||
ins = dex.Instruction11n(MockClassManager(), bytearray([0x12, args]))
|
||||
ins = dex.Instruction11n(
|
||||
MockClassManager(), bytearray([0x12, args])
|
||||
)
|
||||
self.assertEqual(ins.get_name(), 'const/4')
|
||||
self.assertEqual(ins.get_literals(), [lit])
|
||||
self.assertEqual(ins.get_raw(), bytearray([0x12, args]))
|
||||
self.assertEqual(ins.get_operands(), [(dex.Operand.REGISTER, reg), (dex.Operand.LITERAL, lit)])
|
||||
self.assertEqual(
|
||||
ins.get_operands(),
|
||||
[(dex.Operand.REGISTER, reg), (dex.Operand.LITERAL, lit)],
|
||||
)
|
||||
self.assertEqual(ins.get_output(), 'v{}, {}'.format(reg, lit))
|
||||
|
||||
def testInstruction21s(self):
|
||||
@ -457,7 +606,10 @@ class InstructionTest(unittest.TestCase):
|
||||
self.assertEqual(ins.get_name(), 'const/16')
|
||||
self.assertEqual(ins.get_literals(), [lit])
|
||||
self.assertEqual(ins.get_raw(), bytecode)
|
||||
self.assertEqual(ins.get_operands(), [(dex.Operand.REGISTER, reg), (dex.Operand.LITERAL, lit)])
|
||||
self.assertEqual(
|
||||
ins.get_operands(),
|
||||
[(dex.Operand.REGISTER, reg), (dex.Operand.LITERAL, lit)],
|
||||
)
|
||||
self.assertEqual(ins.get_output(), 'v{}, {}'.format(reg, lit))
|
||||
|
||||
# const-wide/16
|
||||
@ -466,7 +618,10 @@ class InstructionTest(unittest.TestCase):
|
||||
self.assertEqual(ins.get_name(), 'const-wide/16')
|
||||
self.assertEqual(ins.get_literals(), [lit])
|
||||
self.assertEqual(ins.get_raw(), bytecode)
|
||||
self.assertEqual(ins.get_operands(), [(dex.Operand.REGISTER, reg), (dex.Operand.LITERAL, lit)])
|
||||
self.assertEqual(
|
||||
ins.get_operands(),
|
||||
[(dex.Operand.REGISTER, reg), (dex.Operand.LITERAL, lit)],
|
||||
)
|
||||
self.assertEqual(ins.get_output(), 'v{}, {}'.format(reg, lit))
|
||||
|
||||
def testInstruction31i(self):
|
||||
@ -486,7 +641,10 @@ class InstructionTest(unittest.TestCase):
|
||||
self.assertEqual(ins.get_name(), 'const')
|
||||
self.assertEqual(ins.get_literals(), [lit])
|
||||
self.assertEqual(ins.get_raw(), bytecode)
|
||||
self.assertEqual(ins.get_operands(), [(dex.Operand.REGISTER, reg), (dex.Operand.LITERAL, lit)])
|
||||
self.assertEqual(
|
||||
ins.get_operands(),
|
||||
[(dex.Operand.REGISTER, reg), (dex.Operand.LITERAL, lit)],
|
||||
)
|
||||
self.assertEqual(ins.get_output(), 'v{}, {}'.format(reg, lit))
|
||||
|
||||
# const-wide/32
|
||||
@ -495,7 +653,10 @@ class InstructionTest(unittest.TestCase):
|
||||
self.assertEqual(ins.get_name(), 'const-wide/32')
|
||||
self.assertEqual(ins.get_literals(), [lit])
|
||||
self.assertEqual(ins.get_raw(), bytecode)
|
||||
self.assertEqual(ins.get_operands(), [(dex.Operand.REGISTER, reg), (dex.Operand.LITERAL, lit)])
|
||||
self.assertEqual(
|
||||
ins.get_operands(),
|
||||
[(dex.Operand.REGISTER, reg), (dex.Operand.LITERAL, lit)],
|
||||
)
|
||||
self.assertEqual(ins.get_output(), 'v{}, {}'.format(reg, lit))
|
||||
|
||||
def testBrokenDex(self):
|
||||
@ -506,10 +667,12 @@ class InstructionTest(unittest.TestCase):
|
||||
self.assertIn('Header too small', str(cnx.exception))
|
||||
|
||||
# Adler32 will not match, zeroed out file
|
||||
data_dex = binascii.unhexlify('6465780A303335001F6C4D5A6ACF889AF588F3237FC9F20B41F56A2408749D1B'
|
||||
'C81E000070000000785634120000000000000000341E00009400000070000000'
|
||||
'2E000000C0020000310000007803000011000000C4050000590000004C060000'
|
||||
'090000001409000094140000340A0000' + ('00' * (7880 - 0x70)))
|
||||
data_dex = binascii.unhexlify(
|
||||
'6465780A303335001F6C4D5A6ACF889AF588F3237FC9F20B41F56A2408749D1B'
|
||||
'C81E000070000000785634120000000000000000341E00009400000070000000'
|
||||
'2E000000C0020000310000007803000011000000C4050000590000004C060000'
|
||||
'090000001409000094140000340A0000' + ('00' * (7880 - 0x70))
|
||||
)
|
||||
|
||||
with self.assertRaises(ValueError) as cnx:
|
||||
dex.DEX(data_dex)
|
||||
@ -517,42 +680,52 @@ class InstructionTest(unittest.TestCase):
|
||||
|
||||
# A very very basic dex file (without a map)
|
||||
# But should parse...
|
||||
data_dex = binascii.unhexlify('6465780A30333500460A4882696E76616C6964696E76616C6964696E76616C69'
|
||||
'7000000070000000785634120000000000000000000000000000000000000000'
|
||||
'0000000000000000000000000000000000000000000000000000000000000000'
|
||||
'00000000000000000000000000000000')
|
||||
data_dex = binascii.unhexlify(
|
||||
'6465780A30333500460A4882696E76616C6964696E76616C6964696E76616C69'
|
||||
'7000000070000000785634120000000000000000000000000000000000000000'
|
||||
'0000000000000000000000000000000000000000000000000000000000000000'
|
||||
'00000000000000000000000000000000'
|
||||
)
|
||||
self.assertIsNotNone(dex.DEX(data_dex))
|
||||
|
||||
# Wrong header size
|
||||
data_dex = binascii.unhexlify('6465780A30333500480A2C8D696E76616C6964696E76616C6964696E76616C69'
|
||||
'7100000071000000785634120000000000000000000000000000000000000000'
|
||||
'0000000000000000000000000000000000000000000000000000000000000000'
|
||||
'0000000000000000000000000000000000')
|
||||
data_dex = binascii.unhexlify(
|
||||
'6465780A30333500480A2C8D696E76616C6964696E76616C6964696E76616C69'
|
||||
'7100000071000000785634120000000000000000000000000000000000000000'
|
||||
'0000000000000000000000000000000000000000000000000000000000000000'
|
||||
'0000000000000000000000000000000000'
|
||||
)
|
||||
with self.assertRaises(ValueError) as cnx:
|
||||
dex.DEX(data_dex)
|
||||
self.assertIn("Wrong header size", str(cnx.exception))
|
||||
|
||||
# Non integer version, but parse it
|
||||
data_dex = binascii.unhexlify('6465780AFF00AB00460A4882696E76616C6964696E76616C6964696E76616C69'
|
||||
'7000000070000000785634120000000000000000000000000000000000000000'
|
||||
'0000000000000000000000000000000000000000000000000000000000000000'
|
||||
'00000000000000000000000000000000')
|
||||
data_dex = binascii.unhexlify(
|
||||
'6465780AFF00AB00460A4882696E76616C6964696E76616C6964696E76616C69'
|
||||
'7000000070000000785634120000000000000000000000000000000000000000'
|
||||
'0000000000000000000000000000000000000000000000000000000000000000'
|
||||
'00000000000000000000000000000000'
|
||||
)
|
||||
self.assertIsNotNone(dex.DEX(data_dex))
|
||||
|
||||
# Big Endian file
|
||||
data_dex = binascii.unhexlify('6465780A30333500460AF480696E76616C6964696E76616C6964696E76616C69'
|
||||
'7000000070000000123456780000000000000000000000000000000000000000'
|
||||
'0000000000000000000000000000000000000000000000000000000000000000'
|
||||
'00000000000000000000000000000000')
|
||||
data_dex = binascii.unhexlify(
|
||||
'6465780A30333500460AF480696E76616C6964696E76616C6964696E76616C69'
|
||||
'7000000070000000123456780000000000000000000000000000000000000000'
|
||||
'0000000000000000000000000000000000000000000000000000000000000000'
|
||||
'00000000000000000000000000000000'
|
||||
)
|
||||
with self.assertRaises(NotImplementedError) as cnx:
|
||||
dex.DEX(data_dex)
|
||||
self.assertIn("swapped endian tag", str(cnx.exception))
|
||||
|
||||
# Weird endian file
|
||||
data_dex = binascii.unhexlify('6465780A30333500AB0BC3E4696E76616C6964696E76616C6964696E76616C69'
|
||||
'7000000070000000ABCDEF120000000000000000000000000000000000000000'
|
||||
'0000000000000000000000000000000000000000000000000000000000000000'
|
||||
'00000000000000000000000000000000')
|
||||
data_dex = binascii.unhexlify(
|
||||
'6465780A30333500AB0BC3E4696E76616C6964696E76616C6964696E76616C69'
|
||||
'7000000070000000ABCDEF120000000000000000000000000000000000000000'
|
||||
'0000000000000000000000000000000000000000000000000000000000000000'
|
||||
'00000000000000000000000000000000'
|
||||
)
|
||||
with self.assertRaises(ValueError) as cnx:
|
||||
dex.DEX(data_dex)
|
||||
self.assertIn("Wrong endian tag", str(cnx.exception))
|
||||
|
@ -1,11 +1,11 @@
|
||||
import os
|
||||
import unittest
|
||||
from binascii import hexlify
|
||||
from difflib import Differ
|
||||
|
||||
from androguard.core import dex
|
||||
import parse_dex
|
||||
|
||||
from binascii import hexlify
|
||||
import unittest
|
||||
from difflib import Differ
|
||||
from androguard.core import dex
|
||||
|
||||
test_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
@ -32,25 +32,25 @@ class TestDexCodeParsing(unittest.TestCase):
|
||||
continue
|
||||
|
||||
code = hexlify(m.get_code().get_raw())
|
||||
self.assertEqual(parsed.methods[m.get_method_idx()],
|
||||
code,
|
||||
"incorrect code for "
|
||||
"[{}]: {} --> {}:\n"
|
||||
"{}\ntries_size: {}, insns_size: {}\nSHOULD BE {}\n{}\n{}".format(m.get_method_idx(),
|
||||
m.get_class_name(),
|
||||
m.get_name(),
|
||||
"".join(dif.compare(
|
||||
parsed.methods[
|
||||
m.get_method_idx()],
|
||||
code)),
|
||||
m.get_code().tries_size,
|
||||
m.get_code().insns_size,
|
||||
hexlify(
|
||||
m.get_code().get_raw()),
|
||||
parsed.methods[
|
||||
m.get_method_idx()],
|
||||
hexlify(
|
||||
m.get_code().code.get_raw())))
|
||||
self.assertEqual(
|
||||
parsed.methods[m.get_method_idx()],
|
||||
code,
|
||||
"incorrect code for "
|
||||
"[{}]: {} --> {}:\n"
|
||||
"{}\ntries_size: {}, insns_size: {}\nSHOULD BE {}\n{}\n{}".format(
|
||||
m.get_method_idx(),
|
||||
m.get_class_name(),
|
||||
m.get_name(),
|
||||
"".join(
|
||||
dif.compare(parsed.methods[m.get_method_idx()], code)
|
||||
),
|
||||
m.get_code().tries_size,
|
||||
m.get_code().insns_size,
|
||||
hexlify(m.get_code().get_raw()),
|
||||
parsed.methods[m.get_method_idx()],
|
||||
hexlify(m.get_code().code.get_raw()),
|
||||
),
|
||||
)
|
||||
|
||||
def testClassManager(self):
|
||||
"""Test if the classmanager has the same items"""
|
||||
@ -74,13 +74,17 @@ class TestDexCodeParsing(unittest.TestCase):
|
||||
for idx in range(parsed.string_ids_size):
|
||||
self.assertNotEqual(cm.get_string(idx), ERR_STR)
|
||||
self.assertNotEqual(cm.get_raw_string(idx), ERR_STR)
|
||||
self.assertEqual(cm.get_raw_string(idx), decode(parsed.str_raw[idx]))
|
||||
self.assertEqual(
|
||||
cm.get_raw_string(idx), decode(parsed.str_raw[idx])
|
||||
)
|
||||
|
||||
self.assertEqual(cm.get_string(parsed.string_ids_size), ERR_STR)
|
||||
self.assertEqual(cm.get_raw_string(parsed.string_ids_size), ERR_STR)
|
||||
|
||||
self.assertEqual(cm.get_string(parsed.string_ids_size + 100), ERR_STR)
|
||||
self.assertEqual(cm.get_raw_string(parsed.string_ids_size + 100), ERR_STR)
|
||||
self.assertEqual(
|
||||
cm.get_raw_string(parsed.string_ids_size + 100), ERR_STR
|
||||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
@ -1,5 +1,3 @@
|
||||
|
||||
|
||||
import unittest
|
||||
|
||||
from androguard.core.dex import TypeMapItem
|
||||
|
@ -1,36 +1,78 @@
|
||||
# -*- coding: utf8- -*-
|
||||
import unittest
|
||||
import os
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
from androguard.misc import clean_file_name
|
||||
import tempfile
|
||||
|
||||
|
||||
class MiscTest(unittest.TestCase):
|
||||
def testCleanPath(self):
|
||||
self.assertEqual("foobarfoo_", clean_file_name("foobarfoo ", unique=False))
|
||||
self.assertEqual("foobarsdf_", clean_file_name("foobarsdf.", unique=False))
|
||||
self.assertEqual(
|
||||
"foobarfoo_", clean_file_name("foobarfoo ", unique=False)
|
||||
)
|
||||
self.assertEqual(
|
||||
"foobarsdf_", clean_file_name("foobarsdf.", unique=False)
|
||||
)
|
||||
self.assertEqual("_init_", clean_file_name("<init>", unique=False))
|
||||
if os.name == "nt":
|
||||
self.assertEqual("C:\\" + "a" * 230, clean_file_name("C:\\" + "a" * 999, unique=False))
|
||||
self.assertEqual("C:\\" + "a" * 226 + ".foo", clean_file_name("C:\\" + "a" * 999 + ".foo", unique=False))
|
||||
self.assertEqual(
|
||||
"C:\\" + "a" * 230,
|
||||
clean_file_name("C:\\" + "a" * 999, unique=False),
|
||||
)
|
||||
self.assertEqual(
|
||||
"C:\\" + "a" * 226 + ".foo",
|
||||
clean_file_name("C:\\" + "a" * 999 + ".foo", unique=False),
|
||||
)
|
||||
else:
|
||||
self.assertEqual("/some/path/" + "a" * 230, clean_file_name("/some/path/" + "a" * 999, unique=False))
|
||||
self.assertEqual("/some/path/" + "a" * 226 + ".foo", clean_file_name("/some/path/" + "a" * 999 + ".foo", unique=False))
|
||||
self.assertEqual(
|
||||
"/some/path/" + "a" * 230,
|
||||
clean_file_name("/some/path/" + "a" * 999, unique=False),
|
||||
)
|
||||
self.assertEqual(
|
||||
"/some/path/" + "a" * 226 + ".foo",
|
||||
clean_file_name(
|
||||
"/some/path/" + "a" * 999 + ".foo", unique=False
|
||||
),
|
||||
)
|
||||
|
||||
with tempfile.NamedTemporaryFile() as fp:
|
||||
self.assertEqual(fp.name + "_0", clean_file_name(fp.name, unique=True))
|
||||
self.assertEqual(
|
||||
fp.name + "_0", clean_file_name(fp.name, unique=True)
|
||||
)
|
||||
|
||||
def testClassNameFormatting(self):
|
||||
from androguard.core.bytecode import get_package_class_name
|
||||
|
||||
self.assertEqual(get_package_class_name('Ljava/lang/Object;'), ('java.lang', 'Object'))
|
||||
self.assertEqual(get_package_class_name('[Ljava/lang/Object;'), ('java.lang', 'Object'))
|
||||
self.assertEqual(get_package_class_name('[[Ljava/lang/Object;'), ('java.lang', 'Object'))
|
||||
self.assertEqual(get_package_class_name('[[[[[[[[[[[[[[[[[[[[[[[Ljava/lang/Object;'), ('java.lang', 'Object'))
|
||||
self.assertEqual(get_package_class_name('[[[[[[[[[[[[[[[[[[[[[[[LObject;'), ('', 'Object'))
|
||||
self.assertEqual(
|
||||
get_package_class_name('Ljava/lang/Object;'),
|
||||
('java.lang', 'Object'),
|
||||
)
|
||||
self.assertEqual(
|
||||
get_package_class_name('[Ljava/lang/Object;'),
|
||||
('java.lang', 'Object'),
|
||||
)
|
||||
self.assertEqual(
|
||||
get_package_class_name('[[Ljava/lang/Object;'),
|
||||
('java.lang', 'Object'),
|
||||
)
|
||||
self.assertEqual(
|
||||
get_package_class_name(
|
||||
'[[[[[[[[[[[[[[[[[[[[[[[Ljava/lang/Object;'
|
||||
),
|
||||
('java.lang', 'Object'),
|
||||
)
|
||||
self.assertEqual(
|
||||
get_package_class_name('[[[[[[[[[[[[[[[[[[[[[[[LObject;'),
|
||||
('', 'Object'),
|
||||
)
|
||||
self.assertEqual(get_package_class_name('LFoobar;'), ('', 'Foobar'))
|
||||
self.assertEqual(get_package_class_name('Lsdflkjdsklfjsdkjfklsdjfkljsdkflsd/shdfjksdhkjfhsdkjfsh;'),
|
||||
('sdflkjdsklfjsdkjfklsdjfkljsdkflsd', 'shdfjksdhkjfhsdkjfsh'))
|
||||
self.assertEqual(
|
||||
get_package_class_name(
|
||||
'Lsdflkjdsklfjsdkjfklsdjfkljsdkflsd/shdfjksdhkjfhsdkjfsh;'
|
||||
),
|
||||
('sdflkjdsklfjsdkjfklsdjfkljsdkflsd', 'shdfjksdhkjfhsdkjfsh'),
|
||||
)
|
||||
self.assertEqual(get_package_class_name('L;'), ('', ''))
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
|
@ -1,6 +1,5 @@
|
||||
import unittest
|
||||
|
||||
import os
|
||||
import unittest
|
||||
|
||||
from androguard.core import dex
|
||||
from androguard.core.analysis import analysis
|
||||
@ -11,14 +10,13 @@ test_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
class RenameTest(unittest.TestCase):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(RenameTest, self).__init__(*args, **kwargs)
|
||||
with open(os.path.join(test_dir, 'data/APK/classes.dex'),
|
||||
"rb") as fd:
|
||||
with open(os.path.join(test_dir, 'data/APK/classes.dex'), "rb") as fd:
|
||||
self.d = dex.DEX(fd.read())
|
||||
self.dx = analysis.Analysis(self.d)
|
||||
# self.d.set_vmanalysis(self.dx)
|
||||
|
||||
def testMethodRename(self):
|
||||
meth, = self.d.get_encoded_method("testDouble")
|
||||
(meth,) = self.d.get_encoded_method("testDouble")
|
||||
clas = self.d.get_class(meth.get_class_name())
|
||||
self.assertEqual(meth.get_name(), "testDouble")
|
||||
self.assertIn(meth.get_name(), [i.name for i in clas.get_methods()])
|
||||
@ -28,10 +26,12 @@ class RenameTest(unittest.TestCase):
|
||||
self.assertNotIn("testDouble", [i.name for i in clas.get_methods()])
|
||||
|
||||
def testFieldRename(self):
|
||||
field, = self.d.get_encoded_field("FLAG_REGISTER_CONTENT_OBSERVER")
|
||||
(field,) = self.d.get_encoded_field("FLAG_REGISTER_CONTENT_OBSERVER")
|
||||
self.assertEqual(field.get_name(), "FLAG_REGISTER_CONTENT_OBSERVER")
|
||||
field.set_name("FLAG_REGISTER_CONTENT_OBSERVER_RENAMED")
|
||||
self.assertEqual(field.get_name(), "FLAG_REGISTER_CONTENT_OBSERVER_RENAMED")
|
||||
self.assertEqual(
|
||||
field.get_name(), "FLAG_REGISTER_CONTENT_OBSERVER_RENAMED"
|
||||
)
|
||||
|
||||
def testClassRename(self):
|
||||
clazz = self.d.get_class("LTestDefaultPackage;")
|
||||
|
@ -1,29 +1,31 @@
|
||||
# -*- coding: utf8- -*-
|
||||
import os
|
||||
import unittest
|
||||
|
||||
import os
|
||||
|
||||
from androguard.core import mutf8
|
||||
from androguard.core import dex
|
||||
from androguard.core import dex, mutf8
|
||||
|
||||
test_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
|
||||
class StringTest(unittest.TestCase):
|
||||
def testDex(self):
|
||||
with open(os.path.join(test_dir, 'data/APK/StringTests.dex'), "rb") as fd:
|
||||
with open(
|
||||
os.path.join(test_dir, 'data/APK/StringTests.dex'), "rb"
|
||||
) as fd:
|
||||
d = dex.DEX(fd.read())
|
||||
|
||||
stests = ["this is a quite normal string",
|
||||
"\u0000 \u0001 \u1234",
|
||||
"使用在線工具將字符串翻譯為中文",
|
||||
"перевод строки на русский с помощью онлайн-инструментов",
|
||||
"온라인 도구를 사용하여 문자열을 한국어로 번역",
|
||||
"オンラインツールを使用して文字列を日本語に翻訳",
|
||||
"This is \U0001f64f, an emoji.", # complete surrogate
|
||||
"\u2713 check this string",
|
||||
"\uffff \u0000 \uff00",
|
||||
"\u0420\u043e\u0441\u0441\u0438\u044f"]
|
||||
stests = [
|
||||
"this is a quite normal string",
|
||||
"\u0000 \u0001 \u1234",
|
||||
"使用在線工具將字符串翻譯為中文",
|
||||
"перевод строки на русский с помощью онлайн-инструментов",
|
||||
"온라인 도구를 사용하여 문자열을 한국어로 번역",
|
||||
"オンラインツールを使用して文字列を日本語に翻訳",
|
||||
"This is \U0001f64f, an emoji.", # complete surrogate
|
||||
"\u2713 check this string",
|
||||
"\uffff \u0000 \uff00",
|
||||
"\u0420\u043e\u0441\u0441\u0438\u044f",
|
||||
]
|
||||
|
||||
for s in stests:
|
||||
self.assertIn(s, d.get_strings())
|
||||
@ -35,41 +37,64 @@ class StringTest(unittest.TestCase):
|
||||
self.assertEqual("\uacf0", mutf8.decode(b"\xea\xb3\xb0"))
|
||||
# # Surrogates
|
||||
self.assertEqual("🙏", mutf8.decode(b"\xed\xa0\xbd\xed\xb9\x8f"))
|
||||
self.assertEqual("\U00014f5c", mutf8.decode(b"\xed\xa1\x93\xed\xbd\x9c"))
|
||||
self.assertEqual(
|
||||
"\U00014f5c", mutf8.decode(b"\xed\xa1\x93\xed\xbd\x9c")
|
||||
)
|
||||
# # Lonely surrogates
|
||||
# self.assertEqual("\ud853", mutf8.decode(b"\xed\xa1\x93"))
|
||||
# self.assertEqual("\udf5c", mutf8.decode(b"\xed\xbd\x9c"))
|
||||
# # Normal ASCII String
|
||||
self.assertEqual("hello world", mutf8.decode(b"\x68\x65\x6c\x6c\x6f\x20\x77\x6f\x72\x6c\x64"))
|
||||
self.assertEqual(
|
||||
"hello world",
|
||||
mutf8.decode(b"\x68\x65\x6c\x6c\x6f\x20\x77\x6f\x72\x6c\x64"),
|
||||
)
|
||||
|
||||
# Testing decode
|
||||
|
||||
b = b"\xed\xa1\x93\xed\xbd\x9c" + \
|
||||
b"\xed\xa0\xbd\xed\xb9\x8f" + \
|
||||
b"\xed\xa0\xbd" + \
|
||||
b"\xea\xb3\xb0" + \
|
||||
b"\x68\x65\x6c\x6c\x6f\x20\x77\x6f\x72\x6c\x64" + \
|
||||
b"\xc0\x80"
|
||||
b = (
|
||||
b"\xed\xa1\x93\xed\xbd\x9c"
|
||||
+ b"\xed\xa0\xbd\xed\xb9\x8f"
|
||||
+ b"\xed\xa0\xbd"
|
||||
+ b"\xea\xb3\xb0"
|
||||
+ b"\x68\x65\x6c\x6c\x6f\x20\x77\x6f\x72\x6c\x64"
|
||||
+ b"\xc0\x80"
|
||||
)
|
||||
|
||||
self.assertEqual("hello world", mutf8.decode(b"\x68\x65\x6c\x6c\x6f\x20\x77\x6f\x72\x6c\x64").encode('utf8',
|
||||
errors='backslashreplace').decode(
|
||||
'utf8'))
|
||||
self.assertEqual("\U00014f5c",
|
||||
mutf8.decode(b"\xed\xa1\x93\xed\xbd\x9c").encode('utf8', errors='backslashreplace').decode(
|
||||
'utf8'))
|
||||
self.assertEqual("\U0001f64f",
|
||||
mutf8.decode(b"\xed\xa0\xbd\xed\xb9\x8f").encode('utf8', errors='backslashreplace').decode(
|
||||
'utf8'))
|
||||
self.assertEqual(
|
||||
"hello world",
|
||||
mutf8.decode(b"\x68\x65\x6c\x6c\x6f\x20\x77\x6f\x72\x6c\x64")
|
||||
.encode('utf8', errors='backslashreplace')
|
||||
.decode('utf8'),
|
||||
)
|
||||
self.assertEqual(
|
||||
"\U00014f5c",
|
||||
mutf8.decode(b"\xed\xa1\x93\xed\xbd\x9c")
|
||||
.encode('utf8', errors='backslashreplace')
|
||||
.decode('utf8'),
|
||||
)
|
||||
self.assertEqual(
|
||||
"\U0001f64f",
|
||||
mutf8.decode(b"\xed\xa0\xbd\xed\xb9\x8f")
|
||||
.encode('utf8', errors='backslashreplace')
|
||||
.decode('utf8'),
|
||||
)
|
||||
# self.assertEqual("\\ud853",
|
||||
# mutf8.decode(b"\xed\xa1\x93").encode('utf8', errors='backslashreplace').decode('utf8'))
|
||||
# mutf8.decode(b"\xed\xa1\x93").encode('utf8', errors='backslashreplace').decode('utf8'))
|
||||
# self.assertEqual("\U00024f5c\U0001f64f\\ud83d\uacf0hello world\x00",
|
||||
# mutf8.decode(b).encode('utf8', errors='backslashreplace').decode('utf8'))
|
||||
|
||||
# Testing encode
|
||||
|
||||
self.assertEqual(b"\x68\x65\x6c\x6c\x6f\x20\x77\x6f\x72\x6c\x64", mutf8.encode("hello world"))
|
||||
self.assertEqual(b"\xed\xa2\x93\xed\xbd\x9c", mutf8.encode("\U00024f5c"))
|
||||
self.assertEqual(b"\xed\xa1\xbd\xed\xb9\x8f", mutf8.encode("\U0001f64f"))
|
||||
self.assertEqual(
|
||||
b"\x68\x65\x6c\x6c\x6f\x20\x77\x6f\x72\x6c\x64",
|
||||
mutf8.encode("hello world"),
|
||||
)
|
||||
self.assertEqual(
|
||||
b"\xed\xa2\x93\xed\xbd\x9c", mutf8.encode("\U00024f5c")
|
||||
)
|
||||
self.assertEqual(
|
||||
b"\xed\xa1\xbd\xed\xb9\x8f", mutf8.encode("\U0001f64f")
|
||||
)
|
||||
# self.assertEqual(b"\xed\xa1\x93", mutf8.encode("\ud853"))
|
||||
# self.assertEqual(b, mutf8.encode("\U00024f5c\U0001f64f\ud83d\uacf0hello world\x00"))
|
||||
|
||||
|
@ -2,11 +2,10 @@
|
||||
|
||||
import os
|
||||
import unittest
|
||||
from struct import pack, unpack, calcsize
|
||||
from struct import calcsize, pack, unpack
|
||||
|
||||
from androguard.session import Session
|
||||
|
||||
|
||||
test_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
TEST_CASE = os.path.join(test_dir, 'data/APK/classes.dex')
|
||||
|
||||
@ -23,7 +22,6 @@ VALUES = {
|
||||
('<d', 3),
|
||||
('<d', 4),
|
||||
('<d', 5),
|
||||
|
||||
('<l', -5),
|
||||
('<l', -4),
|
||||
('<l', -3),
|
||||
@ -35,7 +33,6 @@ VALUES = {
|
||||
('<l', 3),
|
||||
('<l', 4),
|
||||
('<l', 5),
|
||||
|
||||
('<f', -5),
|
||||
('<f', -4),
|
||||
('<f', -3),
|
||||
@ -47,61 +44,49 @@ VALUES = {
|
||||
('<f', 3),
|
||||
('<f', 4),
|
||||
('<f', 5),
|
||||
|
||||
('<d', 65534),
|
||||
('<d', 65535),
|
||||
('<d', 65536),
|
||||
('<d', 65537),
|
||||
|
||||
('<d', 32769),
|
||||
('<d', 32768),
|
||||
('<d', 32767),
|
||||
('<d', 32766),
|
||||
|
||||
('<l', 65534),
|
||||
('<l', 65535),
|
||||
('<l', 65536),
|
||||
('<l', 65537),
|
||||
|
||||
('<l', 32769),
|
||||
('<l', 32768),
|
||||
('<l', 32767),
|
||||
('<l', 32766),
|
||||
|
||||
('<f', 65534),
|
||||
('<f', 65535),
|
||||
('<f', 65536),
|
||||
('<f', 65537),
|
||||
|
||||
('<f', 32769),
|
||||
('<f', 32768),
|
||||
('<f', 32767),
|
||||
('<f', 32766),
|
||||
|
||||
('<d', 5346952),
|
||||
('<l', 5346952),
|
||||
('<f', 5346952),
|
||||
|
||||
('<d', 65534.50),
|
||||
('<d', 65535.50),
|
||||
('<d', 65536.50),
|
||||
('<d', 65537.50),
|
||||
|
||||
('<d', 32769.50),
|
||||
('<d', 32768.50),
|
||||
('<d', 32767.50),
|
||||
('<d', 32766.50),
|
||||
|
||||
('<f', 65534.50),
|
||||
('<f', 65535.50),
|
||||
('<f', 65536.50),
|
||||
('<f', 65537.50),
|
||||
|
||||
('<f', 32769.50),
|
||||
('<f', 32768.50),
|
||||
('<f', 32767.50),
|
||||
('<f', 32766.50),
|
||||
|
||||
('<f', -5),
|
||||
('<f', -65535),
|
||||
('<f', -65536),
|
||||
@ -140,7 +125,7 @@ def format_value(literal, ins, to):
|
||||
trailing = bytearray()
|
||||
if to == '<l' and packed[-1] & 0x80 == 0x80:
|
||||
# Sign extension
|
||||
trailing = bytearray([0xff] * (calcsize(to) - calcsize(formats[char])))
|
||||
trailing = bytearray([0xFF] * (calcsize(to) - calcsize(formats[char])))
|
||||
elif to == '<l' and packed[-1] & 0x80 == 0x00:
|
||||
# no sign extension, but requires zero trailing
|
||||
trailing = bytearray([0] * (calcsize(to) - calcsize(formats[char])))
|
||||
@ -158,10 +143,14 @@ class TypesTest(unittest.TestCase):
|
||||
with open(TEST_CASE, "rb") as fd:
|
||||
digest, d, dx = s.addDEX(TEST_CASE, fd.read())
|
||||
|
||||
for method in filter(lambda x: x.full_name in VALUES, d.get_encoded_methods()):
|
||||
for method in filter(
|
||||
lambda x: x.full_name in VALUES, d.get_encoded_methods()
|
||||
):
|
||||
# print("METHOD", method.full_name)
|
||||
|
||||
for i in filter(lambda x: 'const' in x.get_name(), method.get_instructions()):
|
||||
for i in filter(
|
||||
lambda x: 'const' in x.get_name(), method.get_instructions()
|
||||
):
|
||||
i.show(0)
|
||||
# ins should only have one literal
|
||||
self.assertEqual(len(i.get_literals()), 1)
|
||||
|
Loading…
Reference in New Issue
Block a user