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:
Martin Thoma 2018-10-02 18:13:29 +02:00
parent ef25eb9ff7
commit 644f4fb0ae
No known key found for this signature in database
GPG Key ID: 94DD4FD95F98B113
12 changed files with 933 additions and 410 deletions

View File

@ -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__":

View File

@ -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:

View File

@ -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__":

View File

@ -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__":

View File

@ -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)

View 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
View 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
View 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()

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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"],