Skip to content

Commit

Permalink
ENH: Add 'androguard' executable script
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
MartinThoma committed Oct 5, 2018
1 parent ef25eb9 commit 644f4fb
Show file tree
Hide file tree
Showing 12 changed files with 933 additions and 410 deletions.
26 changes: 1 addition & 25 deletions androarsc.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__":
Expand Down
19 changes: 1 addition & 18 deletions androauto.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

from optparse import OptionParser
from androguard.core.analysis import auto
from androguard.cli import androauto_main as main, AndroLog

option_0 = {
'name': ('-d', '--directory'),
Expand All @@ -34,29 +35,11 @@
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:
Expand Down
23 changes: 1 addition & 22 deletions androaxml.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__":
Expand Down
97 changes: 15 additions & 82 deletions androcg.py
Original file line number Diff line number Diff line change
@@ -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.")

Expand All @@ -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

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)
def main():
parser = create_parser()
args = parser.parse_args()
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__":
Expand Down
126 changes: 7 additions & 119 deletions androdd.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -148,7 +32,11 @@ def export_apps_to_format(filename,
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:
Expand All @@ -172,4 +60,4 @@ def export_apps_to_format(filename,
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 changes: 10 additions & 0 deletions androguard/cli/__init__.py
Original file line number Diff line number Diff line change
@@ -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,
)
Loading

0 comments on commit 644f4fb

Please sign in to comment.