mirror of
https://github.com/androguard/androguard.git
synced 2024-10-07 18:23:25 +00:00
ENH: Add 'androguard' executable script
* New users will intuitively try the command 'androguard'. By adding this and the use of --help they can discover all of the functionality of androguard. * code moved to the androguard directory: makes it easier to test and possible to import in other code. * By not removing the scripts I think this is backwards-compatible See issue #561
This commit is contained in:
parent
ef25eb9ff7
commit
644f4fb0ae
26
androarsc.py
26
androarsc.py
@ -21,35 +21,11 @@
|
||||
from __future__ import print_function
|
||||
import sys
|
||||
from argparse import ArgumentParser
|
||||
import lxml.etree as etree
|
||||
|
||||
from androguard.core import androconf
|
||||
from androguard.core.bytecodes import apk
|
||||
from androguard.util import read
|
||||
|
||||
|
||||
def main(arscobj, outp=None, package=None, typ=None, locale=None):
|
||||
package = package or arscobj.get_packages_names()[0]
|
||||
ttype = typ or "public"
|
||||
locale = locale or '\x00\x00'
|
||||
|
||||
# TODO: be able to dump all locales of a specific type
|
||||
# TODO: be able to recreate the structure of files when developing, eg a 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)
|
||||
sys.exit(1)
|
||||
|
||||
x = getattr(arscobj, "get_" + ttype + "_resources")(package, locale)
|
||||
|
||||
buff = etree.tostring(etree.fromstring(x), pretty_print=True, encoding="UTF-8")
|
||||
|
||||
if outp:
|
||||
with open(outp, "wb") as fd:
|
||||
fd.write(buff)
|
||||
else:
|
||||
print(buff.decode("UTF-8"))
|
||||
from androguard.cli import androarsc_main as main
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
19
androauto.py
19
androauto.py
@ -24,6 +24,7 @@ import sys
|
||||
|
||||
from optparse import OptionParser
|
||||
from androguard.core.analysis import auto
|
||||
from androguard.cli import androauto_main as main, AndroLog
|
||||
|
||||
option_0 = {
|
||||
'name': ('-d', '--directory'),
|
||||
@ -34,29 +35,11 @@ option_1 = {'name': ('-v', '--verbose'), 'help': 'add debug', 'action': 'count'}
|
||||
options = [option_0, option_1]
|
||||
|
||||
|
||||
class AndroLog(object):
|
||||
def __init__(self, id_file, filename):
|
||||
self.id_file = id_file
|
||||
self.filename = filename
|
||||
|
||||
|
||||
class AndroTest(auto.DirectoryAndroAnalysis):
|
||||
def analysis_app(self, log, apkobj, dexobj, adexobj):
|
||||
print(log.id_file, log.filename, apkobj, dexobj, adexobj)
|
||||
|
||||
|
||||
def main(options, arguments):
|
||||
if options.directory:
|
||||
settings = {
|
||||
"my": AndroTest(options.directory),
|
||||
"log": AndroLog,
|
||||
"max_fetcher": 3,
|
||||
}
|
||||
|
||||
aa = auto.AndroAuto(settings)
|
||||
aa.go()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = OptionParser()
|
||||
for option in options:
|
||||
|
23
androaxml.py
23
androaxml.py
@ -20,31 +20,10 @@
|
||||
|
||||
from __future__ import print_function
|
||||
import sys
|
||||
from lxml import etree
|
||||
from argparse import ArgumentParser
|
||||
|
||||
from androguard.core import androconf
|
||||
from androguard.core.bytecodes import apk
|
||||
from androguard.util import read
|
||||
|
||||
|
||||
def main(inp, outp=None):
|
||||
ret_type = androconf.is_android(inp)
|
||||
if ret_type == "APK":
|
||||
a = apk.APK(inp)
|
||||
axml = a.get_android_manifest_xml()
|
||||
elif ".xml" in inp:
|
||||
axml = apk.AXMLPrinter(read(inp)).get_xml_obj()
|
||||
else:
|
||||
print("Unknown file type")
|
||||
return
|
||||
|
||||
buff = etree.tostring(axml, pretty_print=True, encoding="utf-8")
|
||||
if outp:
|
||||
with open(outp, "wb") as fd:
|
||||
fd.write(buff)
|
||||
else:
|
||||
print(buff.decode("UTF-8"))
|
||||
from androguard.cli import androaxml_main as main
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
97
androcg.py
97
androcg.py
@ -1,52 +1,10 @@
|
||||
#!/usr/bin/env python3
|
||||
from androguard.misc import AnalyzeAPK
|
||||
from androguard.core.androconf import show_logging
|
||||
from androguard.core.analysis.analysis import ExternalMethod
|
||||
from androguard.core.bytecode import FormatClassToJava
|
||||
import matplotlib.pyplot as plt
|
||||
import networkx as nx
|
||||
from argparse import ArgumentParser
|
||||
import sys
|
||||
import logging
|
||||
|
||||
log = logging.getLogger("androcfg")
|
||||
from androguard.cli import androcg_main
|
||||
|
||||
|
||||
def plot(CG):
|
||||
"""
|
||||
Plot the call graph using matplotlib
|
||||
For larger graphs, this should not be used, as it is very slow
|
||||
and probably you can not see anything on it.
|
||||
|
||||
:param CG: A networkx graph to plot
|
||||
"""
|
||||
pos = nx.spring_layout(CG)
|
||||
|
||||
internal = []
|
||||
external = []
|
||||
|
||||
for n in CG.node:
|
||||
if isinstance(n, ExternalMethod):
|
||||
external.append(n)
|
||||
else:
|
||||
internal.append(n)
|
||||
|
||||
nx.draw_networkx_nodes(CG, pos=pos, node_color='r', nodelist=internal)
|
||||
nx.draw_networkx_nodes(CG, pos=pos, node_color='b', nodelist=external)
|
||||
nx.draw_networkx_edges(CG, pos, arrow=True)
|
||||
nx.draw_networkx_labels(CG, pos=pos, labels={x: "{} {}".format(x.get_class_name(), x.get_name()) for x in CG.edge})
|
||||
plt.draw()
|
||||
plt.show()
|
||||
|
||||
|
||||
def _write_gml(G, path):
|
||||
"""
|
||||
Wrapper around nx.write_gml
|
||||
"""
|
||||
return nx.write_gml(G, path, stringizer=str)
|
||||
|
||||
|
||||
def main():
|
||||
def create_parser():
|
||||
parser = ArgumentParser(description="Create a call graph based on the data"
|
||||
"of Analysis and export it into a graph format.")
|
||||
|
||||
@ -63,46 +21,21 @@ def main():
|
||||
parser.add_argument("--accessflag", default=".*", help="Regex to filter by accessflags")
|
||||
parser.add_argument("--no-isolated", default=False, action="store_true",
|
||||
help="Do not store methods which has no xrefs")
|
||||
return parser
|
||||
|
||||
|
||||
def main():
|
||||
parser = create_parser()
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.verbose:
|
||||
show_logging(logging.INFO)
|
||||
|
||||
a, d, dx = AnalyzeAPK(args.APK[0])
|
||||
|
||||
entry_points = map(FormatClassToJava, a.get_activities() + a.get_providers() + a.get_services() + a.get_receivers())
|
||||
entry_points = list(entry_points)
|
||||
|
||||
log.info("Found The following entry points by search AndroidManifest.xml: {}".format(entry_points))
|
||||
|
||||
CG = dx.get_call_graph(args.classname,
|
||||
args.methodname,
|
||||
args.descriptor,
|
||||
args.accessflag,
|
||||
args.no_isolated,
|
||||
entry_points,
|
||||
)
|
||||
|
||||
write_methods = dict(gml=_write_gml,
|
||||
gexf=nx.write_gexf,
|
||||
gpickle=nx.write_gpickle,
|
||||
graphml=nx.write_graphml,
|
||||
yaml=nx.write_yaml,
|
||||
net=nx.write_pajek,
|
||||
)
|
||||
|
||||
if args.show:
|
||||
plot(CG)
|
||||
else:
|
||||
writer = args.output.rsplit(".", 1)[1]
|
||||
if writer in ["bz2", "gz"]:
|
||||
writer = args.output.rsplit(".", 2)[1]
|
||||
if writer not in write_methods:
|
||||
print("Could not find a method to export files to {}!".format(writer))
|
||||
sys.exit(1)
|
||||
|
||||
write_methods[writer](CG, args.output)
|
||||
androcg_main(args.verbose,
|
||||
args.APK[0],
|
||||
args.classname,
|
||||
args.methodname,
|
||||
args.descriptor,
|
||||
args.accessflag,
|
||||
args.no_isolated,
|
||||
args.show,
|
||||
args.output)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
126
androdd.py
126
androdd.py
@ -3,131 +3,15 @@
|
||||
from __future__ import print_function
|
||||
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
from argparse import ArgumentParser
|
||||
|
||||
from androguard import session
|
||||
from androguard.misc import clean_file_name
|
||||
from androguard.core import androconf
|
||||
from androguard.core.bytecode import method2dot, method2format
|
||||
from androguard.core.bytecodes import dvm
|
||||
from androguard.decompiler import decompiler
|
||||
from androguard.cli import export_apps_to_format
|
||||
|
||||
|
||||
def valid_class_name(class_name):
|
||||
if class_name[-1] == ";":
|
||||
class_name = class_name[1:-1]
|
||||
return os.path.join(*class_name.split("/"))
|
||||
|
||||
|
||||
def create_directory(pathdir):
|
||||
if not os.path.exists(pathdir):
|
||||
os.makedirs(pathdir)
|
||||
|
||||
|
||||
def export_apps_to_format(filename,
|
||||
s,
|
||||
output,
|
||||
methods_filter=None,
|
||||
jar=None,
|
||||
decompiler_type=None,
|
||||
form=None):
|
||||
print("Dump information %s in %s" % (filename, output))
|
||||
|
||||
if not os.path.exists(output):
|
||||
print("Create directory %s" % output)
|
||||
os.makedirs(output)
|
||||
else:
|
||||
print("Clean directory %s" % output)
|
||||
androconf.rrmdir(output)
|
||||
os.makedirs(output)
|
||||
|
||||
methods_filter_expr = None
|
||||
if methods_filter:
|
||||
methods_filter_expr = re.compile(methods_filter)
|
||||
|
||||
dump_classes = []
|
||||
for _, vm, vmx in s.get_objects_dex():
|
||||
print("Decompilation ...", end=' ')
|
||||
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"]))
|
||||
elif decompiler_type == "dex2winejad":
|
||||
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"]))
|
||||
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"]))
|
||||
|
||||
print("End")
|
||||
|
||||
if jar:
|
||||
print("jar ...", end=' ')
|
||||
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_methods():
|
||||
if methods_filter_expr:
|
||||
msig = "%s%s%s" % (method.get_class_name(), method.get_name(),
|
||||
method.get_descriptor())
|
||||
if not methods_filter_expr.search(msig):
|
||||
continue
|
||||
|
||||
# Current Folder to write to
|
||||
filename_class = valid_class_name(method.get_class_name())
|
||||
filename_class = os.path.join(output, filename_class)
|
||||
create_directory(filename_class)
|
||||
|
||||
print("Dump %s %s %s ..." % (method.get_class_name(),
|
||||
method.get_name(),
|
||||
method.get_descriptor()), end=' ')
|
||||
|
||||
filename = clean_file_name(os.path.join(filename_class, method.get_short_string()))
|
||||
|
||||
buff = method2dot(vmx.get_method(method))
|
||||
# Write Graph of method
|
||||
if form:
|
||||
print("%s ..." % form, end=' ')
|
||||
method2format(filename + "." + form, form, None, buff)
|
||||
|
||||
# Write the Java file for the whole class
|
||||
if 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(current_class.get_name())
|
||||
|
||||
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())
|
||||
|
||||
# Write SMALI like code
|
||||
print("bytecodes ...", end=' ')
|
||||
bytecode_buff = dvm.get_bytecodes_method(vm, vmx, method)
|
||||
with open(filename + ".ag", "w") as fd:
|
||||
fd.write(bytecode_buff)
|
||||
print()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
def get_parser():
|
||||
parser = ArgumentParser(description="Decompile an APK and create Control Flow Graphs")
|
||||
|
||||
parser.add_argument("--version", "-v", action="store_true", default=False,
|
||||
@ -148,7 +32,11 @@ if __name__ == "__main__":
|
||||
help="Limit to certain methods only by regex (default: '.*')")
|
||||
parser.add_argument("--decompiler", "-d",
|
||||
help="Use a different decompiler (default: DAD)")
|
||||
return parser
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = get_parser()
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.file and args.input:
|
||||
@ -172,4 +60,4 @@ if __name__ == "__main__":
|
||||
with open(fname, "rb") as fd:
|
||||
s.add(fname, fd.read())
|
||||
export_apps_to_format(fname, s, args.output, args.limit,
|
||||
args.jar, args.decompiler, args.format)
|
||||
args.jar, args.decompiler, args.format)
|
||||
|
10
androguard/cli/__init__.py
Normal file
10
androguard/cli/__init__.py
Normal file
@ -0,0 +1,10 @@
|
||||
from androguard.cli.main import (androarsc_main,
|
||||
androauto_main,
|
||||
androaxml_main,
|
||||
androcg_main,
|
||||
androgui_main,
|
||||
AndroLog,
|
||||
androlyze_main,
|
||||
androsign_main,
|
||||
export_apps_to_format,
|
||||
)
|
438
androguard/cli/entry_points.py
Executable file
438
androguard/cli/entry_points.py
Executable file
@ -0,0 +1,438 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""Androguard is a full Python tool to play with Android files."""
|
||||
|
||||
# core modules
|
||||
import sys
|
||||
|
||||
# 3rd party modules
|
||||
import click
|
||||
|
||||
# local modules
|
||||
import androguard
|
||||
from androguard.cli import (androarsc_main,
|
||||
androauto_main,
|
||||
androaxml_main,
|
||||
androcg_main,
|
||||
export_apps_to_format,
|
||||
androsign_main,
|
||||
androlyze_main,
|
||||
androgui_main)
|
||||
|
||||
|
||||
@click.group(help=__doc__)
|
||||
@click.version_option(version=androguard.__version__)
|
||||
def entry_point():
|
||||
pass
|
||||
|
||||
|
||||
@entry_point.command()
|
||||
@click.option(
|
||||
'--input', '-i', 'input_',
|
||||
type=click.Path(exists=True),
|
||||
help='AndroidManifest.xml or APK to parse (legacy option)',
|
||||
)
|
||||
@click.option(
|
||||
'--output', '-o',
|
||||
required=True,
|
||||
help='filename to save the decoded AndroidManifest.xml to',
|
||||
)
|
||||
@click.argument(
|
||||
'file_',
|
||||
type=click.Path(exists=True),
|
||||
# help='AndroidManifest.xml or APK to parse',
|
||||
required=False,
|
||||
)
|
||||
def axml(input_, output, file_):
|
||||
"""
|
||||
Parse the AndroidManifest.xml.
|
||||
|
||||
Parsing is either direct or from a given APK and prints in XML format or
|
||||
saves to file.
|
||||
|
||||
This tool can also be used to process any AXML encoded file, for example
|
||||
from the layout directory.
|
||||
|
||||
Example:
|
||||
|
||||
\b
|
||||
$ 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!")
|
||||
sys.exit(1)
|
||||
|
||||
if file_ is None and input_ is None:
|
||||
print("Give one file to decode!")
|
||||
sys.exit(1)
|
||||
|
||||
if file_ is not None:
|
||||
androaxml_main(file_, output)
|
||||
elif input_ is not None:
|
||||
androaxml_main(input_, output)
|
||||
|
||||
|
||||
@entry_point.command()
|
||||
@click.option(
|
||||
'--input', '-i', 'input_',
|
||||
type=click.Path(exists=True),
|
||||
help='resources.arsc or APK to parse (legacy option)',
|
||||
)
|
||||
@click.argument(
|
||||
'file_',
|
||||
# help='resources.arsc or APK to parse',
|
||||
required=False,
|
||||
)
|
||||
@click.option(
|
||||
'--output', '-o',
|
||||
# required=True, # not required due to --list-types
|
||||
help='filename to save the decoded resources to',
|
||||
)
|
||||
@click.option(
|
||||
'--package', '-p',
|
||||
help='Show only resources for the given package name '
|
||||
'(default: the first package name found)',
|
||||
)
|
||||
@click.option(
|
||||
'--locale', '-l',
|
||||
help='Show only resources for the given locale (default: \'\\x00\\x00\')',
|
||||
)
|
||||
@click.option(
|
||||
'--type', '-t', 'type_',
|
||||
help='Show only resources of the given type (default: public)',
|
||||
)
|
||||
@click.option(
|
||||
'--list-packages', '-t',
|
||||
default=False,
|
||||
help='List all package names and exit',
|
||||
)
|
||||
@click.option(
|
||||
'--list-locales', '-t',
|
||||
default=False,
|
||||
help='List all package names and exit',
|
||||
)
|
||||
@click.option(
|
||||
'--list-types', '-t',
|
||||
default=False,
|
||||
help='List all types and exit',
|
||||
)
|
||||
def arsc(input_,
|
||||
file_,
|
||||
output,
|
||||
package,
|
||||
locale,
|
||||
type_,
|
||||
list_packages,
|
||||
list_locales,
|
||||
list_types):
|
||||
"""
|
||||
Decode resources.arsc either directly from a given file or from an APK.
|
||||
|
||||
Example:
|
||||
|
||||
\b
|
||||
$ androguard arsc app.apk
|
||||
"""
|
||||
from androguard.core import androconf
|
||||
from androguard.core.bytecodes import apk
|
||||
|
||||
if file_ and input_:
|
||||
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_:
|
||||
print("Give one file to decode!", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
if input_:
|
||||
fname = input_
|
||||
else:
|
||||
fname = file_
|
||||
|
||||
ret_type = androconf.is_android(fname)
|
||||
if ret_type == "APK":
|
||||
a = apk.APK(fname)
|
||||
arscobj = a.get_android_resources()
|
||||
elif ret_type == "ARSC":
|
||||
arscobj = apk.ARSCParser(read(fname))
|
||||
else:
|
||||
print("Unknown file type!", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
if list_packages:
|
||||
print("\n".join(arscobj.get_packages_names()))
|
||||
sys.exit(0)
|
||||
|
||||
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)))))
|
||||
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)))))
|
||||
sys.exit(0)
|
||||
|
||||
androarsc_main(arscobj,
|
||||
outp=output,
|
||||
package=package,
|
||||
typ=type_,
|
||||
locale=locale)
|
||||
|
||||
|
||||
class AutoOptions(object):
|
||||
"""Represent options of auto script (legacy support reasons)."""
|
||||
|
||||
def __init__(self, directory):
|
||||
self.directory = directory
|
||||
|
||||
|
||||
@entry_point.command()
|
||||
@click.option(
|
||||
'--directory', '-d',
|
||||
help='directory input',
|
||||
)
|
||||
def auto(directory):
|
||||
"""TODO: What is this good for?."""
|
||||
options = AutoOptions(directory)
|
||||
androauto_main(options, arguments={})
|
||||
|
||||
|
||||
@entry_point.command()
|
||||
@click.option(
|
||||
'--output', '-o',
|
||||
default="callgraph.gml", show_default=True,
|
||||
help='Filename of the output file, the extension is used to decide which '
|
||||
'format to use (default callgraph.gml)',
|
||||
)
|
||||
@click.option(
|
||||
'--show', '-s',
|
||||
default=False,
|
||||
help='instead of saving the graph, print it with mathplotlib '
|
||||
'(you might not see anything!)',
|
||||
)
|
||||
@click.option(
|
||||
'--verbose', '-v', is_flag=True,
|
||||
default=False,
|
||||
help='Print more output',
|
||||
)
|
||||
@click.option(
|
||||
'--classname',
|
||||
default='.*', show_default=True,
|
||||
help='Regex to filter by classname',
|
||||
)
|
||||
@click.option(
|
||||
'--methodname',
|
||||
default='.*', show_default=True,
|
||||
help='Regex to filter by methodname',
|
||||
)
|
||||
@click.option(
|
||||
'--descriptor',
|
||||
default='.*', show_default=True,
|
||||
help='Regex to filter by descriptor',
|
||||
)
|
||||
@click.option(
|
||||
'--accessflag',
|
||||
default='.*', show_default=True,
|
||||
help='Regex to filter by accessflags',
|
||||
)
|
||||
@click.option(
|
||||
'--no-isolated/--isolated',
|
||||
default=False,
|
||||
help='Do not store methods which has no xrefs',
|
||||
)
|
||||
@click.argument(
|
||||
'APK',
|
||||
# help='The APK to analyze',
|
||||
nargs=1,
|
||||
required=False,
|
||||
type=click.Path(exists=True),
|
||||
)
|
||||
def cg(output,
|
||||
show,
|
||||
verbose,
|
||||
classname,
|
||||
methodname,
|
||||
descriptor,
|
||||
accessflag,
|
||||
no_isolated,
|
||||
apk):
|
||||
"""
|
||||
Create a call graph and export it into a graph format.
|
||||
|
||||
Example:
|
||||
|
||||
\b
|
||||
$ androguard cg APK
|
||||
"""
|
||||
androcg_main(verbose=verbose,
|
||||
APK=apk,
|
||||
classname=classname,
|
||||
methodname=methodname,
|
||||
descriptor=descriptor,
|
||||
accessflag=accessflag,
|
||||
no_isolated=no_isolated,
|
||||
show=show,
|
||||
output=output)
|
||||
|
||||
|
||||
@entry_point.command()
|
||||
@click.option(
|
||||
'--input', '-i', 'input_',
|
||||
type=click.Path(exists=True),
|
||||
help='resources.arsc or APK to parse (legacy option)',
|
||||
)
|
||||
@click.argument(
|
||||
'file_',
|
||||
type=click.Path(exists=True),
|
||||
# help='resources.arsc or APK to parse',
|
||||
required=False,
|
||||
)
|
||||
@click.option(
|
||||
'--output', '-o',
|
||||
required=True,
|
||||
help='output directory. If the output folder already exsist, '
|
||||
'it will be overwritten!',
|
||||
)
|
||||
@click.option(
|
||||
'--format', '-f', 'format_',
|
||||
help='Additionally write control flow graphs for each method, specify '
|
||||
'the format for example png, jpg, raw (write dot file), ...',
|
||||
)
|
||||
@click.option(
|
||||
'--jar', '-j',
|
||||
is_flag=True,
|
||||
default=False,
|
||||
help='Use DEX2JAR to create a JAR file',
|
||||
)
|
||||
@click.option(
|
||||
'--limit', '-l',
|
||||
help='Limit to certain methods only by regex (default: \'.*\')',
|
||||
)
|
||||
@click.option(
|
||||
'--decompiler', '-d',
|
||||
help='Use a different decompiler (default: DAD)',
|
||||
)
|
||||
def decompile(input_, file_, output, format_, jar, limit, decompiler):
|
||||
"""
|
||||
Decompile an APK and create Control Flow Graphs.
|
||||
|
||||
Example:
|
||||
|
||||
\b
|
||||
$ 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)
|
||||
sys.exit(1)
|
||||
|
||||
if not input_ and not file_:
|
||||
print("Give one file to decode!", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
if input_:
|
||||
fname = input_
|
||||
else:
|
||||
fname = file_
|
||||
|
||||
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_)
|
||||
|
||||
|
||||
@entry_point.command()
|
||||
@click.option(
|
||||
'--hash', 'hash_',
|
||||
type=click.Choice(['md5', 'sha1', 'sha256', 'sha512']),
|
||||
default='sha1', show_default=True,
|
||||
help='Fingerprint Hash algorithm',
|
||||
)
|
||||
@click.option(
|
||||
'--all', '-a', 'print_all_hashes',
|
||||
is_flag=True,
|
||||
default=False, show_default=True,
|
||||
help='Print all supported hashes',
|
||||
)
|
||||
@click.option(
|
||||
'--show', '-s',
|
||||
is_flag=True,
|
||||
default=False, show_default=True,
|
||||
help='Additionally of printing the fingerprints, show more '
|
||||
'certificate information',
|
||||
)
|
||||
@click.argument(
|
||||
'apk',
|
||||
# help='APK(s) to extract the Fingerprint of Certificates from',
|
||||
nargs=-1,
|
||||
required=False,
|
||||
type=click.Path(exists=True),
|
||||
)
|
||||
def sign(hash_, print_all_hashes, show, apk):
|
||||
"""Return the fingerprint(s) of all certificates inside an APK."""
|
||||
androsign_main(apk, hash_, print_all_hashes, show)
|
||||
|
||||
|
||||
@entry_point.command()
|
||||
@click.option(
|
||||
'--input_file', '-i',
|
||||
type=click.Path(exists=True),
|
||||
)
|
||||
@click.option(
|
||||
'--input_plugin', '-p',
|
||||
type=click.Path(exists=True),
|
||||
)
|
||||
def gui(input_file, input_plugin):
|
||||
"""Androguard GUI"""
|
||||
androgui_main(input_file, input_plugin)
|
||||
|
||||
|
||||
@entry_point.command()
|
||||
@click.option(
|
||||
'--debug', '--verbose', '-d',
|
||||
is_flag=True,
|
||||
default=False, show_default=True,
|
||||
help='Print log messages',
|
||||
)
|
||||
@click.option(
|
||||
'--ddebug', '--very-verbose', '-dd',
|
||||
is_flag=True,
|
||||
default=False, show_default=True,
|
||||
help='Print log messages (higher verbosity)',
|
||||
)
|
||||
@click.option(
|
||||
'--no-session',
|
||||
is_flag=True,
|
||||
default=False, show_default=True,
|
||||
help='Do not start an Androguard session',
|
||||
)
|
||||
@click.argument(
|
||||
'apk',
|
||||
# help='Start the shell with the given APK. a, d, dx are available then. Loading might be slower in this case!',
|
||||
default=None,
|
||||
required=False,
|
||||
type=click.Path(exists=True),
|
||||
)
|
||||
def analyze(debug, ddebug, no_session, apk):
|
||||
"""Open a IPython Shell and start reverse engineering."""
|
||||
androlyze_main(debug, ddebug, no_session, apk)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
entry_point()
|
436
androguard/cli/main.py
Normal file
436
androguard/cli/main.py
Normal file
@ -0,0 +1,436 @@
|
||||
from __future__ import print_function
|
||||
|
||||
# core modules
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
# 3rd party modules
|
||||
from lxml import etree
|
||||
|
||||
# internal modules
|
||||
from androguard.core import androconf
|
||||
from androguard.core.bytecodes import apk
|
||||
from androguard.util import read
|
||||
|
||||
|
||||
def androaxml_main(inp, outp=None):
|
||||
ret_type = androconf.is_android(inp)
|
||||
if ret_type == "APK":
|
||||
a = apk.APK(inp)
|
||||
axml = a.get_android_manifest_xml()
|
||||
elif ".xml" in inp:
|
||||
axml = apk.AXMLPrinter(read(inp)).get_xml_obj()
|
||||
else:
|
||||
print("Unknown file type")
|
||||
return
|
||||
|
||||
buff = etree.tostring(axml, pretty_print=True, encoding="utf-8")
|
||||
if outp:
|
||||
with open(outp, "wb") as fd:
|
||||
fd.write(buff)
|
||||
else:
|
||||
print(buff.decode("UTF-8"))
|
||||
|
||||
|
||||
def androarsc_main(arscobj, outp=None, package=None, typ=None, locale=None):
|
||||
package = package or arscobj.get_packages_names()[0]
|
||||
ttype = typ or "public"
|
||||
locale = locale or '\x00\x00'
|
||||
|
||||
# TODO: be able to dump all locales of a specific type
|
||||
# TODO: be able to recreate the structure of files when developing, eg a
|
||||
# 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)
|
||||
sys.exit(1)
|
||||
|
||||
x = getattr(arscobj, "get_" + ttype + "_resources")(package, locale)
|
||||
|
||||
buff = etree.tostring(etree.fromstring(x),
|
||||
pretty_print=True,
|
||||
encoding="UTF-8")
|
||||
|
||||
if outp:
|
||||
with open(outp, "wb") as fd:
|
||||
fd.write(buff)
|
||||
else:
|
||||
print(buff.decode("UTF-8"))
|
||||
|
||||
|
||||
class AndroLog(object):
|
||||
def __init__(self, id_file, filename):
|
||||
self.id_file = id_file
|
||||
self.filename = filename
|
||||
|
||||
|
||||
def androauto_main(options, arguments):
|
||||
if options.directory:
|
||||
settings = {
|
||||
"my": AndroTest(options.directory),
|
||||
"log": AndroLog,
|
||||
"max_fetcher": 3,
|
||||
}
|
||||
|
||||
aa = auto.AndroAuto(settings)
|
||||
aa.go()
|
||||
|
||||
|
||||
def androcg_main(verbose,
|
||||
APK,
|
||||
classname,
|
||||
methodname,
|
||||
descriptor,
|
||||
accessflag,
|
||||
no_isolated,
|
||||
show,
|
||||
output):
|
||||
from androguard.core.androconf import show_logging
|
||||
from androguard.core.bytecode import FormatClassToJava
|
||||
from androguard.misc import AnalyzeAPK
|
||||
import networkx as nx
|
||||
import logging
|
||||
log = logging.getLogger("androcfg")
|
||||
if verbose:
|
||||
show_logging(logging.INFO)
|
||||
|
||||
a, d, dx = AnalyzeAPK(APK)
|
||||
|
||||
entry_points = map(FormatClassToJava,
|
||||
a.get_activities() + a.get_providers() +
|
||||
a.get_services() + a.get_receivers())
|
||||
entry_points = list(entry_points)
|
||||
|
||||
log.info("Found The following entry points by search AndroidManifest.xml: "
|
||||
"{}".format(entry_points))
|
||||
|
||||
CG = dx.get_call_graph(classname,
|
||||
methodname,
|
||||
descriptor,
|
||||
accessflag,
|
||||
no_isolated,
|
||||
entry_points,
|
||||
)
|
||||
|
||||
write_methods = dict(gml=_write_gml,
|
||||
gexf=nx.write_gexf,
|
||||
gpickle=nx.write_gpickle,
|
||||
graphml=nx.write_graphml,
|
||||
yaml=nx.write_yaml,
|
||||
net=nx.write_pajek,
|
||||
)
|
||||
|
||||
if show:
|
||||
plot(CG)
|
||||
else:
|
||||
writer = output.rsplit(".", 1)[1]
|
||||
if writer in ["bz2", "gz"]:
|
||||
writer = output.rsplit(".", 2)[1]
|
||||
if writer not in write_methods:
|
||||
print("Could not find a method to export files to {}!"
|
||||
.format(writer))
|
||||
sys.exit(1)
|
||||
|
||||
write_methods[writer](CG, output)
|
||||
|
||||
|
||||
def plot(cg):
|
||||
"""
|
||||
Plot the call graph using matplotlib
|
||||
For larger graphs, this should not be used, as it is very slow
|
||||
and probably you can not see anything on it.
|
||||
|
||||
:param cg: A networkx call graph to plot
|
||||
"""
|
||||
from androguard.core.analysis.analysis import ExternalMethod
|
||||
import matplotlib.pyplot as plt
|
||||
import networkx as nx
|
||||
pos = nx.spring_layout(cg)
|
||||
|
||||
internal = []
|
||||
external = []
|
||||
|
||||
for n in cg.node:
|
||||
if isinstance(n, ExternalMethod):
|
||||
external.append(n)
|
||||
else:
|
||||
internal.append(n)
|
||||
|
||||
nx.draw_networkx_nodes(cg, pos=pos, node_color='r', nodelist=internal)
|
||||
nx.draw_networkx_nodes(cg, pos=pos, node_color='b', nodelist=external)
|
||||
nx.draw_networkx_edges(cg, pos, arrow=True)
|
||||
nx.draw_networkx_labels(cg, pos=pos,
|
||||
labels={x: "{} {}".format(x.get_class_name(),
|
||||
x.get_name())
|
||||
for x in cg.edge})
|
||||
plt.draw()
|
||||
plt.show()
|
||||
|
||||
|
||||
def _write_gml(G, path):
|
||||
"""
|
||||
Wrapper around nx.write_gml
|
||||
"""
|
||||
import networkx as nx
|
||||
return nx.write_gml(G, path, stringizer=str)
|
||||
|
||||
|
||||
def export_apps_to_format(filename,
|
||||
s,
|
||||
output,
|
||||
methods_filter=None,
|
||||
jar=None,
|
||||
decompiler_type=None,
|
||||
form=None):
|
||||
from androguard.misc import clean_file_name
|
||||
from androguard.core.bytecodes import dvm
|
||||
from androguard.core.bytecode import method2dot, method2format
|
||||
from androguard.decompiler import decompiler
|
||||
print("Dump information %s in %s" % (filename, output))
|
||||
|
||||
if not os.path.exists(output):
|
||||
print("Create directory %s" % output)
|
||||
os.makedirs(output)
|
||||
else:
|
||||
print("Clean directory %s" % output)
|
||||
androconf.rrmdir(output)
|
||||
os.makedirs(output)
|
||||
|
||||
methods_filter_expr = None
|
||||
if methods_filter:
|
||||
methods_filter_expr = re.compile(methods_filter)
|
||||
|
||||
dump_classes = []
|
||||
for _, vm, vmx in s.get_objects_dex():
|
||||
print("Decompilation ...", end=' ')
|
||||
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"]))
|
||||
elif decompiler_type == "dex2winejad":
|
||||
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"]))
|
||||
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"]))
|
||||
|
||||
print("End")
|
||||
|
||||
if jar:
|
||||
print("jar ...", end=' ')
|
||||
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_methods():
|
||||
if methods_filter_expr:
|
||||
msig = "%s%s%s" % (method.get_class_name(), method.get_name(),
|
||||
method.get_descriptor())
|
||||
if not methods_filter_expr.search(msig):
|
||||
continue
|
||||
|
||||
# Current Folder to write to
|
||||
filename_class = valid_class_name(method.get_class_name())
|
||||
filename_class = os.path.join(output, filename_class)
|
||||
create_directory(filename_class)
|
||||
|
||||
print("Dump %s %s %s ..." % (method.get_class_name(),
|
||||
method.get_name(),
|
||||
method.get_descriptor()), end=' ')
|
||||
|
||||
filename = clean_file_name(os.path.join(filename_class, method.get_short_string()))
|
||||
|
||||
buff = method2dot(vmx.get_method(method))
|
||||
# Write Graph of method
|
||||
if form:
|
||||
print("%s ..." % form, end=' ')
|
||||
method2format(filename + "." + form, form, None, buff)
|
||||
|
||||
# Write the Java file for the whole class
|
||||
if 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(current_class.get_name())
|
||||
|
||||
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())
|
||||
|
||||
# Write SMALI like code
|
||||
print("bytecodes ...", end=' ')
|
||||
bytecode_buff = dvm.get_bytecodes_method(vm, vmx, method)
|
||||
with open(filename + ".ag", "w") as fd:
|
||||
fd.write(bytecode_buff)
|
||||
print()
|
||||
|
||||
|
||||
def valid_class_name(class_name):
|
||||
if class_name[-1] == ";":
|
||||
class_name = class_name[1:-1]
|
||||
return os.path.join(*class_name.split("/"))
|
||||
|
||||
|
||||
def create_directory(pathdir):
|
||||
if not os.path.exists(pathdir):
|
||||
os.makedirs(pathdir)
|
||||
|
||||
|
||||
def androgui_main(input_file, input_plugin):
|
||||
# Load pyqt5 after argument processing, so we can collect the arguments
|
||||
# on a system without PyQT5.
|
||||
try:
|
||||
from PyQt5 import QtWidgets, QtGui
|
||||
except ImportError:
|
||||
print("No PyQT5 found! Exiting...", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
try:
|
||||
import pyperclip
|
||||
except ImportError:
|
||||
print("No pyperclip found! Exiting...", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
from androguard.gui.mainwindow import MainWindow
|
||||
|
||||
# We need that to save huge sessions when leaving and avoid
|
||||
# RuntimeError: maximum recursion depth exceeded while pickling an object
|
||||
# or
|
||||
# RuntimeError: maximum recursion depth exceeded in cmp
|
||||
# http://stackoverflow.com/questions/2134706/hitting-maximum-recursion-depth-using-pythons-pickle-cpickle
|
||||
sys.setrecursionlimit(50000)
|
||||
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
|
||||
window = MainWindow(input_file=input_file,
|
||||
input_plugin=input_plugin)
|
||||
window.resize(1024, 768)
|
||||
window.show()
|
||||
|
||||
sys.exit(app.exec_())
|
||||
|
||||
|
||||
def androlyze_main(debug, ddebug, no_session, args_apk):
|
||||
# Import commonly used classes
|
||||
import logging
|
||||
from androguard.core.bytecodes.apk import APK
|
||||
from androguard.core.bytecodes.dvm import DalvikVMFormat
|
||||
from androguard.core.analysis.analysis import Analysis
|
||||
if debug:
|
||||
androconf.show_logging(logging.INFO)
|
||||
if ddebug:
|
||||
androconf.show_logging(logging.DEBUG)
|
||||
|
||||
# Go interactive!
|
||||
interact(session=not no_session, apk=args_apk)
|
||||
|
||||
|
||||
def interact(session=False, apk=None):
|
||||
"""
|
||||
Start an interactive shell
|
||||
:param session:
|
||||
:param apk:
|
||||
:return:
|
||||
"""
|
||||
from androguard.core.androconf import ANDROGUARD_VERSION, CONF
|
||||
from IPython.terminal.embed import InteractiveShellEmbed
|
||||
from traitlets.config import Config
|
||||
|
||||
from androguard.misc import init_print_colors, AnalyzeAPK
|
||||
from androguard.session import Session
|
||||
|
||||
if session:
|
||||
CONF["SESSION"] = Session(export_ipython=True)
|
||||
|
||||
if apk:
|
||||
print("Loading apk {}...".format(os.path.basename(apk)))
|
||||
print("Please be patient, this might take a while.")
|
||||
# TODO we can export fancy aliases for those as well...
|
||||
a, d, dx = AnalyzeAPK(apk)
|
||||
|
||||
cfg = Config()
|
||||
_version_string = "Androguard version {}".format(ANDROGUARD_VERSION)
|
||||
ipshell = InteractiveShellEmbed(config=cfg, banner1="{} started"
|
||||
.format(_version_string))
|
||||
init_print_colors()
|
||||
ipshell()
|
||||
|
||||
# TODO: on exit, save the session if requested
|
||||
|
||||
|
||||
def androsign_main(args_apk, args_hash, args_all, show):
|
||||
from androguard.core.bytecodes.apk import APK
|
||||
from androguard.util import get_certificate_name_string
|
||||
|
||||
import hashlib
|
||||
import traceback
|
||||
from colorama import Fore, Style
|
||||
from asn1crypto import x509
|
||||
|
||||
# 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,
|
||||
)
|
||||
|
||||
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)
|
||||
sys.exit(1)
|
||||
|
||||
for path in args_apk:
|
||||
try:
|
||||
a = APK(path)
|
||||
|
||||
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()))
|
||||
|
||||
certs = set(a.get_certificates_der_v2() + [a.get_certificate_der(x) for x in a.get_signature_names()])
|
||||
|
||||
if len(certs) > 0:
|
||||
print("Found {} unique certificates".format(len(certs)))
|
||||
|
||||
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("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)
|
||||
|
||||
if not args_all:
|
||||
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()
|
||||
except:
|
||||
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()
|
41
androgui.py
41
androgui.py
@ -3,47 +3,20 @@
|
||||
from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
|
||||
from androguard.core import androconf
|
||||
from androguard.cli import androgui_main
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
def get_parser():
|
||||
parser = argparse.ArgumentParser(description="Androguard GUI")
|
||||
parser.add_argument("-d", "--debug", action="store_true", default=False)
|
||||
parser.add_argument("-i", "--input_file", default=None)
|
||||
parser.add_argument("-p", "--input_plugin", default=None)
|
||||
return parser
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = get_parser()
|
||||
args = parser.parse_args()
|
||||
|
||||
# Load pyqt5 after argument processing, so we can collect the arguments
|
||||
# on a system without PyQT5.
|
||||
try:
|
||||
from PyQt5 import QtWidgets, QtGui
|
||||
except ImportError:
|
||||
print("No PyQT5 found! Exiting...", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
try:
|
||||
import pyperclip
|
||||
except ImportError:
|
||||
print("No pyperclip found! Exiting...", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
from androguard.gui.mainwindow import MainWindow
|
||||
|
||||
# We need that to save huge sessions when leaving and avoid
|
||||
# RuntimeError: maximum recursion depth exceeded while pickling an object
|
||||
# or
|
||||
# RuntimeError: maximum recursion depth exceeded in cmp
|
||||
# http://stackoverflow.com/questions/2134706/hitting-maximum-recursion-depth-using-pythons-pickle-cpickle
|
||||
sys.setrecursionlimit(50000)
|
||||
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
|
||||
window = MainWindow(input_file=args.input_file,
|
||||
input_plugin=args.input_plugin)
|
||||
window.resize(1024, 768)
|
||||
window.show()
|
||||
|
||||
sys.exit(app.exec_())
|
||||
androgui_main(args.input_file, args.input_plugin)
|
||||
|
56
androlyze.py
56
androlyze.py
@ -22,47 +22,14 @@ from __future__ import print_function
|
||||
|
||||
from argparse import ArgumentParser
|
||||
|
||||
from IPython.terminal.embed import InteractiveShellEmbed
|
||||
from traitlets.config import Config
|
||||
from androguard.cli import androlyze_main
|
||||
|
||||
# Import commonly used classes
|
||||
from androguard.core.androconf import *
|
||||
from androguard.misc import *
|
||||
from androguard.session import Session
|
||||
import os
|
||||
import logging
|
||||
# Import commonly used classes
|
||||
from androguard.core.bytecodes.apk import APK
|
||||
from androguard.core.bytecodes.dvm import DalvikVMFormat
|
||||
from androguard.core.analysis.analysis import Analysis
|
||||
|
||||
_version_string = "Androguard version {}".format(ANDROGUARD_VERSION)
|
||||
|
||||
|
||||
def interact(session=False, apk=None):
|
||||
"""
|
||||
Start an interactive shell
|
||||
:param session:
|
||||
:param apk:
|
||||
:return:
|
||||
"""
|
||||
if session:
|
||||
CONF["SESSION"] = Session(export_ipython=True)
|
||||
|
||||
if apk:
|
||||
print("Loading apk {}...".format(os.path.basename(apk)))
|
||||
print("Please be patient, this might take a while.")
|
||||
# TODO we can export fancy aliases for those as well...
|
||||
a, d, dx = AnalyzeAPK(apk)
|
||||
|
||||
cfg = Config()
|
||||
ipshell = InteractiveShellEmbed(config=cfg, banner1="{} started".format(_version_string))
|
||||
init_print_colors()
|
||||
ipshell()
|
||||
|
||||
# TODO: on exit, save the session if requested
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
def get_parser():
|
||||
parser = ArgumentParser(description="Open a IPython Shell and start reverse engineering")
|
||||
|
||||
parser.add_argument("--shell", "-s", default=False, action="store_true", help="Will do nothing, this argument is just here for your convenience")
|
||||
@ -71,17 +38,10 @@ if __name__ == "__main__":
|
||||
parser.add_argument("--no-session", default=False, action="store_true", help="Do not start an Androguard session")
|
||||
parser.add_argument("--version", "-v", default=False, action="store_true", help="Print the Androguard Version and exit")
|
||||
parser.add_argument("apk", default=None, nargs="?", help="Start the shell with the given APK. a, d, dx are available then. Loading might be slower in this case!")
|
||||
return parser
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = get_parser()
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.version:
|
||||
print(_version_string)
|
||||
sys.exit()
|
||||
|
||||
if args.debug:
|
||||
androconf.show_logging(logging.INFO)
|
||||
if args.ddebug:
|
||||
androconf.show_logging(logging.DEBUG)
|
||||
|
||||
# Go interactive!
|
||||
interact(session=not args.no_session, apk=args.apk)
|
||||
androlyze_main(args.debug, args.ddebug, args.no_session, args.apk)
|
||||
|
67
androsign.py
67
androsign.py
@ -1,14 +1,8 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import print_function
|
||||
from androguard.core.bytecodes.apk import APK
|
||||
from androguard.util import get_certificate_name_string
|
||||
from argparse import ArgumentParser
|
||||
import sys
|
||||
import os
|
||||
import hashlib
|
||||
import traceback
|
||||
from colorama import Fore, Back, Style
|
||||
from asn1crypto import x509
|
||||
|
||||
from androguard.cli import androsign_main
|
||||
|
||||
|
||||
def get_parser():
|
||||
@ -22,59 +16,8 @@ def get_parser():
|
||||
|
||||
return parser
|
||||
|
||||
def main():
|
||||
parser = get_parser()
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
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)
|
||||
sys.exit(1)
|
||||
|
||||
for path in args.apk:
|
||||
try:
|
||||
a = APK(path)
|
||||
|
||||
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()))
|
||||
|
||||
certs = set(a.get_certificates_der_v2() + [a.get_certificate_der(x) for x in a.get_signature_names()])
|
||||
|
||||
if len(certs) > 0:
|
||||
print("Found {} unique certificates".format(len(certs)))
|
||||
|
||||
for cert in certs:
|
||||
if args.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("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)
|
||||
|
||||
if not args.all:
|
||||
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()
|
||||
except:
|
||||
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()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
parser = get_parser()
|
||||
args = parser.parse_args()
|
||||
androsign_main(args.apk, args.hash, args.all, args.show)
|
||||
|
4
setup.py
4
setup.py
@ -24,6 +24,7 @@ install_requires = ['future',
|
||||
'colorama',
|
||||
'matplotlib',
|
||||
'asn1crypto>=0.24.0',
|
||||
'click',
|
||||
]
|
||||
|
||||
# python version specific library versions:
|
||||
@ -77,6 +78,9 @@ setup(
|
||||
'androgui.py',
|
||||
'androcg.py',
|
||||
],
|
||||
entry_points={
|
||||
'console_scripts': ['androguard=androguard.cli.entry_points:entry_point']
|
||||
},
|
||||
install_requires=install_requires,
|
||||
extras_require={
|
||||
'GUI': ["pyperclip", "PyQt5"],
|
||||
|
Loading…
Reference in New Issue
Block a user