Clean up and add some ui for dynamic analysis

This commit is contained in:
totoag 2023-06-10 07:00:04 -07:00
parent 1e89ba82e1
commit ca1c12b94f
92 changed files with 2502 additions and 2772 deletions

9
.gitignore vendored
View File

@ -8,15 +8,6 @@ docs/build
*.o
*.so
# Binary Files
elsim/elsim/elsign/formula/test
elsim/elsim/similarity/libsimilarity/bz2/test_bz2
elsim/elsim/similarity/libsimilarity/lzma/test_lzma
elsim/elsim/similarity/libsimilarity/smaz/test_smaz
elsim/elsim/similarity/libsimilarity/snappy/test_snappy
elsim/elsim/similarity/libsimilarity/vcblocksort/test_vcblocksort
elsim/elsim/similarity/libsimilarity/xz/test_xz
elsim/elsim/similarity/libsimilarity/z/test_z
androguard.egg-info/
build/

View File

@ -5,7 +5,6 @@
[![Build Status](https://travis-ci.org/androguard/androguard.svg?branch=master)](https://travis-ci.org/androguard/androguard)
[![Documentation Status](https://github.com/androguard/androguard/wiki)](https://github.com/androguard/androguard/wiki)
[![PyPI version](https://badge.fury.io/py/androguard.svg)](https://badge.fury.io/py/androguard)
[![Codecoverage](https://codecov.io/gh/androguard/androguard/branch/master/graph/badge.svg)](https://codecov.io/gh/androguard/androguard/)
## Installation
@ -63,7 +62,7 @@ You are using Androguard and are not listed here? Just create a [ticket](https:/
### Androguard
Copyright (C) 2012 - 2022, Anthony Desnos (desnos at t0t0.fr)
Copyright (C) 2012 - 2023, Anthony Desnos (desnos at t0t0.fr)
All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");

View File

@ -12,7 +12,7 @@ from loguru import logger
BasicOPCODES = set()
for i in dex.BRANCH_DVM_OPCODES:
for i in dex.BRANCH_DEX_OPCODES:
p = re.compile(i)
for op, items in dex.DALVIK_OPCODES_FORMAT.items():
if p.match(items[1][0]):
@ -39,9 +39,9 @@ class REF_TYPE(IntEnum):
INVOKE_INTERFACE_RANGE = 0x78
class DVMBasicBlock:
class DEXBasicBlock:
"""
A simple basic block of a dalvik method.
A simple basic block of a DEX method.
A basic block consists of a series of :class:`~androguard.core.bytecodes.dvm.Instruction`
which are not interrupted by branch or jump instructions such as `goto`, `if`, `throw`, `return`, `switch` etc.
@ -143,7 +143,7 @@ class DVMBasicBlock:
Get next basic blocks
:returns: a list of the next basic blocks
:rtype: DVMBasicBlock
:rtype: DEXBasicBlock
"""
return self.childs
@ -152,7 +152,7 @@ class DVMBasicBlock:
Get previous basic blocks
:returns: a list of the previous basic blocks
:rtype: DVMBasicBlock
:rtype: DEXBasicBlock
"""
return self.fathers
@ -223,7 +223,7 @@ class BasicBlocks:
"""
This class represents all basic blocks of a method.
It is a collection of many :class:`DVMBasicBlock`.
It is a collection of many :class:`DEXBasicBlock`.
"""
def __init__(self):
self.bb = []
@ -232,7 +232,7 @@ class BasicBlocks:
"""
Adds another basic block to the collection
:param DVBMBasicBlock bb: the DVMBasicBlock to add
:param DVBMBasicBlock bb: the DEXBasicBlock to add
"""
self.bb.append(bb)
@ -250,8 +250,8 @@ class BasicBlocks:
def __iter__(self):
"""
:returns: yields each basic block (:class:`DVMBasicBlock` object)
:rtype: Iterator[DVMBasicBlock]
:returns: yields each basic block (:class:`DEXBasicBlock` object)
:rtype: Iterator[DEXBasicBlock]
"""
yield from self.bb
@ -261,13 +261,13 @@ class BasicBlocks:
:param item: index
:return: The basic block
:rtype: DVMBasicBlock
:rtype: DEXBasicBlock
"""
return self.bb[item]
def gets(self):
"""
:returns: a list of basic blocks (:class:`DVMBasicBlock` objects)
:returns: a list of basic blocks (:class:`DEXBasicBlock` objects)
"""
return self.bb
@ -336,9 +336,9 @@ class MethodAnalysis:
"""
This class analyses in details a method of a class/dex file
It is a wrapper around a :class:`EncodedMethod` and enhances it
by using multiple :class:`DVMBasicBlock` encapsulated in a :class:`BasicBlocks` object.
by using multiple :class:`DEXBasicBlock` encapsulated in a :class:`BasicBlocks` object.
:type vm: a :class:`DalvikVMFormat` object
:type vm: a :class:`DEX` object
:type method: a :class:`EncodedMethod` object
"""
def __init__(self, vm, method):
@ -417,7 +417,7 @@ class MethodAnalysis:
Internal Method to create the basic block structure
Parses all instructions and exceptions.
"""
current_basic = DVMBasicBlock(0, self.__vm, self.method, self.basic_blocks)
current_basic = DEXBasicBlock(0, self.__vm, self.method, self.basic_blocks)
self.basic_blocks.push(current_basic)
l = []
@ -442,14 +442,14 @@ class MethodAnalysis:
# index is a destination
if idx in l:
if current_basic.get_nb_instructions() != 0:
current_basic = DVMBasicBlock(current_basic.get_end(), self.__vm, self.method, self.basic_blocks)
current_basic = DEXBasicBlock(current_basic.get_end(), self.__vm, self.method, self.basic_blocks)
self.basic_blocks.push(current_basic)
current_basic.push(ins)
# index is a branch instruction
if idx in h:
current_basic = DVMBasicBlock(current_basic.get_end(), self.__vm, self.method, self.basic_blocks)
current_basic = DEXBasicBlock(current_basic.get_end(), self.__vm, self.method, self.basic_blocks)
self.basic_blocks.push(current_basic)
if current_basic.get_nb_instructions() == 0:
@ -646,7 +646,7 @@ class MethodAnalysis:
def get_vm(self):
"""
:rtype: androguard.core.bytecodes.dvm.DalvikVMFormat
:rtype: androguard.core.bytecodes.dvm.DEX
:return:
"""
return self.__vm
@ -972,13 +972,13 @@ class ExternalMethod:
class ClassAnalysis:
"""
ClassAnalysis contains the XREFs from a given Class.
It is also used to wrap :class:`~androguard.core.bytecode.dvm.ClassDefItem`, which
It is also used to wrap :class:`~androguard.core.dex.ClassDefItem`, which
contain the actual class content like bytecode.
Also external classes will generate xrefs, obviously only XREF_FROM are
shown for external classes.
:param classobj: class:`~androguard.core.bytecode.dvm.ClassDefItem` or :class:`ExternalClass`
:param classobj: class:`~androguard.core.dex.ClassDefItem` or :class:`ExternalClass`
"""
def __init__(self, classobj):
@ -1370,29 +1370,16 @@ class ClassAnalysis:
return data
class MethodClassAnalysis(MethodAnalysis):
"""
.. deprecated:: 3.4.0
Always use MethodAnalysis!
This method is just here for compatability
"""
def __init__(self, meth):
super().__init__(meth.cm.vm, meth)
class Analysis:
"""
Analysis Object
The Analysis contains a lot of information about (multiple) DalvikVMFormat objects
The Analysis contains a lot of information about (multiple) DEX objects
Features are for example XREFs between Classes, Methods, Fields and Strings.
Yet another part is the creation of BasicBlocks, which is important in the usage of
the Androguard Decompiler.
Multiple DalvikVMFormat Objects can be added using the function :meth:`add`.
Multiple DEX Objects can be added using the function :meth:`add`.
XREFs are created for:
* classes (`ClassAnalysis`)
@ -1402,12 +1389,12 @@ class Analysis:
The Analysis should be the only object you are using next to the :class:`~androguard.core.bytecodes.apk.APK`.
It encapsulates all the Dalvik related functions into a single place, while you have still the ability to use
the functions from :class:`~androguard.core.bytecodes.dvm.DalvikVMFormat` and the related classes.
the functions from :class:`~androguard.core.bytecodes.dvm.DEX` and the related classes.
:param Optional[androguard.core.bytecodes.dvm.DalvikVMFormat] vm: inital DalvikVMFormat object (default None)
:param Optional[androguard.core.bytecodes.dvm.DEX] vm: inital DEX object (default None)
"""
def __init__(self, vm=None):
# Contains DalvikVMFormat objects
# Contains DEX objects
self.vms = []
# A dict of {classname: ClassAnalysis}, populated on add(vm)
self.classes = dict()
@ -1426,9 +1413,9 @@ class Analysis:
def add(self, vm):
"""
Add a DalvikVMFormat to this Analysis.
Add a DEX to this Analysis.
:param androguard.core.bytecodes.dvm.DalvikVMFormat vm: :class:`dvm.DalvikVMFormat` to add to this Analysis
:param androguard.core.bytecodes.dvm.DEX vm: :class:`dvm.DEX` to add to this Analysis
"""
self.vms.append(vm)
@ -1525,7 +1512,7 @@ class Analysis:
idx_type = instruction.get_ref_kind()
# type_info is the string like 'Ljava/lang/Object;'
type_info = instruction.cm.vm.get_cm_type(idx_type).lstrip('[')
if type_info[0] != b'L':
if type_info[0] != 'L':
# Need to make sure, that we get class types and not other types
continue
@ -1566,7 +1553,7 @@ class Analysis:
continue
class_info = method_info[0].lstrip('[')
if class_info[0] != b'L':
if class_info[0] != 'L':
# Need to make sure, that we get class types and not other types
# If another type, like int is used, we simply skip it.
continue
@ -1736,7 +1723,7 @@ class Analysis:
def get_internal_classes(self):
"""
Returns all external classes, that means all classes that are
defined in the given set of :class:`~DalvikVMFormat`.
defined in the given set of :class:`~DEX`.
:rtype: Iterator[ClassAnalysis]
"""
@ -2018,7 +2005,7 @@ def is_ascii_obfuscation(vm):
Tests if any class inside a DalvikVMObject
uses ASCII Obfuscation (e.g. UTF-8 Chars in Classnames)
:param androguard.core.bytecodes.dvm.DalvikVMFormat vm: `DalvikVMObject`
:param androguard.core.bytecodes.dvm.DEX vm: `DalvikVMObject`
:return: True if ascii obfuscation otherwise False
:rtype: bool
"""

View File

@ -1,450 +0,0 @@
import os
import queue
import threading
import time
import zlib
import multiprocessing
import logging
from androguard.core import androconf
from androguard.core.bytecodes import apk, dvm, axml
from androguard.core.analysis import analysis
from androguard.util import read
l = logging.getLogger("androguard.auto")
class AndroAuto:
"""
The main class which analyse automatically android apps by calling methods
from a specific object
Automatic analysis requires two objects to be created:
1) a Logger, found at key `log` in the settings
2) an Analysis runner, found at key `my` in the settings
Both are passed to :class:`AndroAuto` via a dictionary.
The setting dict understands the following keys:
* `my`: The Analysis runner (required)
* `log`: The Logger
* `max_fetcher`: Maximum number of concurrent threads
:class:`DefaultAndroLog` can be used as a baseclass for the Logger, while
:class:`DefaultAndroAnalysis` can be used a baseclass for the Analysis.
There is also :class:`DirectoryAndroAnalysis` which implements a `fetcher`
which recursively reads a directory for files and can be used a baseclass as
well.
example::
from androguard.core.analysis import auto
class AndroTest(auto.DirectoryAndroAnalysis):
# This is the Test Runner
def analysis_app(self, log, apkobj, dexobj, analysisobj):
# Just print all objects to stdout
print(log.id_file, log.filename, apkobj, dexobj, analysisobj)
settings = {
# The directory `some/directory` should contain some APK files
"my": AndroTest('some/directory'),
# Use the default Logger
"log": auto.DefaultAndroLog,
# Use maximum of 2 threads
"max_fetcher": 2,
}
aa = auto.AndroAuto(settings)
aa.go()
:param settings: the settings of the analysis
:type settings: dict
"""
def __init__(self, settings):
if not "my" in settings:
raise ValueError("'my' object not found in settings!")
if not "log" in settings:
raise ValueError("'log' object not found in settings!")
if not "max_fetcher" in settings:
settings["max_fetcher"] = multiprocessing.cpu_count()
l.warning("No maximum number of threads found, setting MAX_CPU: {}".format(settings["max_fetcher"]))
self.settings = settings
def dump(self):
"""
Dump the analysis
Calls `dump()` on the Analysis object
"""
self.settings["my"].dump()
def dump_file(self, filename):
"""
Dump the analysis into a file
Calls `dump_file(filename)` on the Analysis object
"""
self.settings["my"].dump_file(filename)
def go(self):
"""
Launch the analysis.
this will start a total of `max_fetcher` threads.
"""
myandro = self.settings["my"]
def worker(idx, q):
"""
Worker Thread
"""
l.debug("Running worker-%d" % idx)
while True:
a, d, dx, axmlobj, arscobj = None, None, None, None, None
try:
filename, fileraw = q.get()
id_file = zlib.adler32(fileraw)
l.debug("(worker-%d) get %s %d" % (idx, filename, id_file))
# FIXME: If the try/catch crashes before this line, there
# will be no logf to put into finish.
logf = self.settings["log"](id_file, filename)
is_analysis_dex, is_analysis_adex = True, True
l.debug("(worker-%d) filtering file %d" % (idx, id_file))
# TODO: This information should probably also go into the logf?
filter_file_ret, filter_file_type = myandro.filter_file(logf, fileraw)
if filter_file_ret:
l.debug("(worker-%d) analysis %s" % (id_file, filter_file_type))
if filter_file_type == "APK":
a = myandro.create_apk(logf, fileraw)
is_analysis_dex = myandro.analysis_apk(logf, a)
# TODO: Support multidex here
fileraw = a.get_dex()
filter_file_type = androconf.is_android_raw(fileraw)
elif filter_file_type == "AXML":
axmlobj = myandro.create_axml(logf, fileraw)
# TODO: the return value of analysis_axml is not checked
myandro.analysis_axml(logf, axmlobj)
elif filter_file_type == "ARSC":
arscobj = myandro.create_arsc(logf, fileraw)
# TODO: the return value of analysis_arsc is not checked
myandro.analysis_arsc(logf, arscobj)
if is_analysis_dex and filter_file_type == "DEX":
d = myandro.create_dex(logf, fileraw)
is_analysis_adex = myandro.analysis_dex(logf, d)
elif is_analysis_dex and filter_file_type == "DEY":
d = myandro.create_dey(logf, fileraw)
is_analysis_adex = myandro.analysis_dey(logf, d)
if is_analysis_adex and d:
# TODO: Support multidex here
dx = myandro.create_adex(logf, d)
# TODO: The return value of analysis_adex is not checked
myandro.analysis_adex(logf, dx)
# TODO: this is called also if AXML or ARSC is set...
myandro.analysis_app(logf, a, d, dx)
myandro.finish(logf)
except Exception as why:
myandro.crash(logf, why)
# FIXME: finish is called here in any case of an exception
# but is only called if filter_file_ret is true above.
myandro.finish(logf)
del a, d, dx, axmlobj, arscobj
q.task_done()
q = queue.Queue(self.settings["max_fetcher"])
for i in range(self.settings["max_fetcher"]):
t = threading.Thread(target=worker, args=[i, q])
t.daemon = True
t.start()
# FIXME: Busy waiting with sleep...
terminated = True
while terminated:
terminated = myandro.fetcher(q)
try:
if terminated:
time.sleep(10)
except KeyboardInterrupt:
terminated = False
q.join()
class DefaultAndroAnalysis:
"""
This class can be used as a template in order to analyse apps
The order of methods called in this class is the following:
* :meth:`fetcher` is called to get files
* :meth:`filter_file` is called to get the filetype
* :meth:`create_apk` or :meth:`create_axml` or :meth:`create_arsc` and
:meth:`create_dex` or :meth:`create_dey` depending on the filetype
* :meth:`analysis_apk` or :meth:`analysis_axml` or :meth:`analysis_arsc` and
:meth:`analysis_dex` or :meth:`analysis_dey` depending on the filetype
* :meth:`create_adex` if at least one dex was found
* :meth:`analysis_app` with all the gathered objects so far
* :meth:`finish` is called in any case after the analysis
:meth:`crash` can be called during analysis if any Exception happens.
"""
def fetcher(self, q):
"""
This method is called to fetch a new app in order to analyse it. The queue
must be fill with the following format: (filename, raw)
must return False if the queue is filled, thus all files are read.
:param q: the Queue to put new app
"""
return False
def filter_file(self, log, fileraw):
"""
This method is called in order to filer a specific app
:param log: an object which corresponds to a unique app
:param bytes fileraw: the raw file as bytes
:rtype: a tuple with 2 elements, the return value (boolean) if it is necessary to
continue the analysis and the file type
"""
file_type = androconf.is_android_raw(fileraw)
if file_type in ["APK", "DEX", "DEY", "AXML", "ARSC"]:
return True, file_type
return False, None
def create_axml(self, log, fileraw):
"""
This method is called in order to create a new AXML object
:param log: an object which corresponds to a unique app
:param fileraw: the raw axml (a string)
:rtype: an :class:`~androguard.core.bytecodes.axml.AXMLPrinter` object
"""
return axml.AXMLPrinter(fileraw)
def create_arsc(self, log, fileraw):
"""
This method is called in order to create a new ARSC object
:param log: an object which corresponds to a unique app
:param fileraw: the raw arsc (a string)
:rtype: an :class:`~androguard.core.bytecodes.axml.ARSCParser` object
"""
return axml.ARSCParser(fileraw)
def create_apk(self, log, fileraw):
"""
This method is called in order to create a new APK object
:param log: an object which corresponds to a unique app
:param fileraw: the raw apk (a string)
:rtype: an :class:`~androguard.core.bytecodes.apk.APK` object
"""
return apk.APK(fileraw, raw=True)
def create_dex(self, log, dexraw):
"""
This method is called in order to create a DalvikVMFormat object
:param log: an object which corresponds to a unique app
:param dexraw: the raw classes.dex (a string)
:rtype: a :class:`~androguard.core.bytecodes.dvm.DalvikVMFormat` object
"""
return dvm.DalvikVMFormat(dexraw)
def create_dey(self, log, dexraw):
"""
This method is called in order to create a DalvikOdexVMFormat object
:param log: an object which corresponds to a unique app
:param dexraw: the raw odex file (a string)
:rtype: a :class:`~androguard.core.bytecodes.dvm.DalvikOdexVMFormat` object
"""
return dvm.DalvikOdexVMFormat(dexraw)
def create_adex(self, log, dexobj):
"""
This method is called in order to create an Analysis object
:param log: an object which corresponds to a unique app
:param androguard.core.bytecodes.dvm.DalvikVMFormat dexobj: a :class:`DalvikVMFormat` object
:rytpe: a :class:`~androguard.core.analysis.analysis.Analysis` object
"""
vm_analysis = analysis.Analysis(dexobj)
vm_analysis.create_xref()
return vm_analysis
def analysis_axml(self, log, axmlobj):
"""
This method is called in order to know if the analysis must continue
:param log: an object which corresponds to a unique app
:param androguard.core.bytecodes.axml.AXMLPrinter axmlobj: a :class:`AXMLPrinter` object
:returns: True if the analysis should continue afterwards
:rtype: bool
"""
return True
def analysis_arsc(self, log, arscobj):
"""
This method is called in order to know if the analysis must continue
:param log: an object which corresponds to a unique app
:param androguard.core.bytecodes.axml.ARSCParser arscobj: a :class:`ARSCParser` object
:returns: True if the analysis should continue afterwards
:rtype: bool
"""
return True
def analysis_apk(self, log, apkobj):
"""
This method is called in order to know if the analysis must continue
:param log: an object which corresponds to a unique app
:param androguard.core.bytecodes.apk.APK apkobj: a :class:`APK` object
:returns: True if a DEX file should be analyzed as well
:rtype: bool
"""
return True
def analysis_dex(self, log, dexobj):
"""
This method is called in order to know if the analysis must continue
:param log: an object which corresponds to a unique app
:param androguard.core.bytecodes.dvm.DalvikVMFormat dexobj: a :class:`DalvikVMFormat` object
:returns: True if the analysis should continue with an analysis.Analysis
:rtype: bool
"""
return True
def analysis_dey(self, log, deyobj):
"""
This method is called in order to know if the analysis must continue
:param log: an object which corresponds to a unique app
:param androguard.core.bytecodes.dvm.DalvikOdexVMFormat deyobj: a :class:`DalvikOdexVMFormat` object
:returns: True if the analysis should continue with an analysis.Analysis
:rtype: bool
"""
return True
def analysis_adex(self, log, adexobj):
"""
This method is called in order to know if the analysis must continue
:param log: an object which corresponds to a unique app
:param androguard.core.analysis.analysis.Analysis adexobj: a :class:`Analysis` object
:rtype: a boolean
"""
return True
def analysis_app(self, log, apkobj, dexobj, adexobj):
"""
This method is called if you wish to analyse the final app
:param log: an object which corresponds to a unique app
:param androguard.core.bytecodes.apk.APK apkobj: a :class:`APK` object
:param androguard.core.bytecodes.dvm.DalvikVMFormat dexobj: a :class:`DalvikVMFormat` object
:param androguard.core.analysis.analysis.Analysis adexobj: a :class:`Analysis` object
"""
pass
def finish(self, log):
"""
This method is called before the end of the analysis
:param log: an object which corresponds to an unique app
"""
pass
def crash(self, log, why):
"""
This method is called if a crash happens
:param log: an object which corresponds to an unique app
:param why: the exception
"""
pass
def dump(self):
"""
This method is called to dump the result
"""
pass
def dump_file(self, filename):
"""
This method is called to dump the result in a file
:param filename: the filename to dump the result
"""
pass
class DirectoryAndroAnalysis(DefaultAndroAnalysis):
"""
A simple class example to analyse a whole directory with many APKs in it
"""
def __init__(self, directory):
self.directory = directory
def fetcher(self, q):
for root, _, files in os.walk(self.directory, followlinks=True):
for f in files:
real_filename = os.path.join(root, f)
q.put((real_filename, read(real_filename)))
return False
class DefaultAndroLog:
"""
A base class for the Androguard Auto Logger.
The Logger contains two attributes of the analyzed File: :py:attr:`filename`
and :py:attr:`id_file`, which is the Adler32 Checksum of the file.
The Logger can be extended to contain more attributes.
"""
def __init__(self, id_file, filename):
self.id_file = id_file
self.filename = filename
def __str__(self):
return "<AutoLog {:08x} '{}'>".format(self.id_file, self.filename)

View File

@ -1,109 +0,0 @@
import re
class Enum:
def __init__(self, names):
self.names = names
for value, name in enumerate(self.names):
setattr(self, name.upper(), value)
def tuples(self):
return tuple(enumerate(self.names))
TAG_ANDROID = Enum(
['ANDROID', 'TELEPHONY', 'SMS', 'SMSMESSAGE', 'ACCESSIBILITYSERVICE',
'ACCOUNTS', 'ANIMATION', 'APP', 'BLUETOOTH', 'CONTENT', 'DATABASE',
'DEBUG', 'DRM', 'GESTURE', 'GRAPHICS', 'HARDWARE', 'INPUTMETHODSERVICE',
'LOCATION', 'MEDIA', 'MTP', 'NET', 'NFC', 'OPENGL', 'OS', 'PREFERENCE',
'PROVIDER', 'RENDERSCRIPT', 'SAX', 'SECURITY', 'SERVICE', 'SPEECH',
'SUPPORT', 'TEST', 'TEXT', 'UTIL', 'VIEW', 'WEBKIT', 'WIDGET',
'DALVIK_BYTECODE', 'DALVIK_SYSTEM', 'JAVA_REFLECTION'])
TAG_REVERSE_ANDROID = {i[0]: i[1] for i in TAG_ANDROID.tuples()}
TAGS_ANDROID = {
TAG_ANDROID.ANDROID: [0, "Landroid"],
TAG_ANDROID.TELEPHONY: [0, "Landroid/telephony"],
TAG_ANDROID.SMS: [0, "Landroid/telephony/SmsManager"],
TAG_ANDROID.SMSMESSAGE: [0, "Landroid/telephony/SmsMessage"],
TAG_ANDROID.DEBUG: [0, "Landroid/os/Debug"],
TAG_ANDROID.ACCESSIBILITYSERVICE: [0, "Landroid/accessibilityservice"],
TAG_ANDROID.ACCOUNTS: [0, "Landroid/accounts"],
TAG_ANDROID.ANIMATION: [0, "Landroid/animation"],
TAG_ANDROID.APP: [0, "Landroid/app"],
TAG_ANDROID.BLUETOOTH: [0, "Landroid/bluetooth"],
TAG_ANDROID.CONTENT: [0, "Landroid/content"],
TAG_ANDROID.DATABASE: [0, "Landroid/database"],
TAG_ANDROID.DRM: [0, "Landroid/drm"],
TAG_ANDROID.GESTURE: [0, "Landroid/gesture"],
TAG_ANDROID.GRAPHICS: [0, "Landroid/graphics"],
TAG_ANDROID.HARDWARE: [0, "Landroid/hardware"],
TAG_ANDROID.INPUTMETHODSERVICE: [0, "Landroid/inputmethodservice"],
TAG_ANDROID.LOCATION: [0, "Landroid/location"],
TAG_ANDROID.MEDIA: [0, "Landroid/media"],
TAG_ANDROID.MTP: [0, "Landroid/mtp"],
TAG_ANDROID.NET: [0, "Landroid/net"],
TAG_ANDROID.NFC: [0, "Landroid/nfc"],
TAG_ANDROID.OPENGL: [0, "Landroid/opengl"],
TAG_ANDROID.OS: [0, "Landroid/os"],
TAG_ANDROID.PREFERENCE: [0, "Landroid/preference"],
TAG_ANDROID.PROVIDER: [0, "Landroid/provider"],
TAG_ANDROID.RENDERSCRIPT: [0, "Landroid/renderscript"],
TAG_ANDROID.SAX: [0, "Landroid/sax"],
TAG_ANDROID.SECURITY: [0, "Landroid/security"],
TAG_ANDROID.SERVICE: [0, "Landroid/service"],
TAG_ANDROID.SPEECH: [0, "Landroid/speech"],
TAG_ANDROID.SUPPORT: [0, "Landroid/support"],
TAG_ANDROID.TEST: [0, "Landroid/test"],
TAG_ANDROID.TEXT: [0, "Landroid/text"],
TAG_ANDROID.UTIL: [0, "Landroid/util"],
TAG_ANDROID.VIEW: [0, "Landroid/view"],
TAG_ANDROID.WEBKIT: [0, "Landroid/webkit"],
TAG_ANDROID.WIDGET: [0, "Landroid/widget"],
TAG_ANDROID.DALVIK_BYTECODE: [0, "Ldalvik/bytecode"],
TAG_ANDROID.DALVIK_SYSTEM: [0, "Ldalvik/system"],
TAG_ANDROID.JAVA_REFLECTION: [0, "Ljava/lang/reflect"],
}
class Tags:
"""
Handle specific tags
:param patterns:
:params reverse:
"""
def __init__(self, patterns=TAGS_ANDROID, reverse=TAG_REVERSE_ANDROID):
self.tags = set()
self.patterns = patterns
self.reverse = TAG_REVERSE_ANDROID
for i in self.patterns:
self.patterns[i][1] = re.compile(self.patterns[i][1])
def emit(self, method):
for i in self.patterns:
if self.patterns[i][0] == 0:
if self.patterns[i][1].search(method.get_class()) is not None:
self.tags.add(i)
def emit_by_classname(self, classname):
for i in self.patterns:
if self.patterns[i][0] == 0:
if self.patterns[i][1].search(classname) is not None:
self.tags.add(i)
def get_list(self):
return [self.reverse[i] for i in self.tags]
def __contains__(self, key):
return key in self.tags
def __str__(self):
return str([self.reverse[i] for i in self.tags])
def empty(self):
return self.tags == set()

View File

@ -3,13 +3,12 @@ import os
import logging
import tempfile
from colorama import init, Fore
from loguru import logger
from androguard import __version__
from androguard.core.api_specific_resources import load_permission_mappings, load_permissions
ANDROGUARD_VERSION = __version__
log = logging.getLogger("androguard.default")
# initialize colorama, only has an effect on windows
init()
@ -169,30 +168,6 @@ def is_android_raw(raw):
return val
def show_logging(level=logging.INFO):
"""
enable log messages on stdout
We will catch all messages here! From all loggers...
"""
logger = logging.getLogger()
h = logging.StreamHandler(stream=sys.stderr)
h.setFormatter(logging.Formatter(fmt="[%(levelname)-8s] %(name)s: %(message)s"))
logger.addHandler(h)
logger.setLevel(level)
def set_options(key, value):
"""
.. deprecated:: 3.3.5
Use :code:`CONF[key] = value` instead
"""
CONF[key] = value
def rrmdir(directory):
"""
Recursivly delete a directory
@ -300,7 +275,7 @@ def load_api_specific_resource_module(resource_name, api=None):
if ret == {}:
# No API mapping found, return default
log.warning("API mapping for API level {} was not found! "
logger.warning("API mapping for API level {} was not found! "
"Returning default, which is API level {}".format(api, CONF['DEFAULT_API']))
ret = loader[resource_name](CONF['DEFAULT_API'])

View File

@ -1578,7 +1578,7 @@
"protectionLevel": "dangerous"
},
"android.permission.WRITE_CALL_LOG": {
"description": "Allows the app to modify your phone's call log, including data about incoming and outgoing calls.\n Malicious apps may use this to erase or modify your call log.",
"description": "Allows the app to modify your phone's call log, including data about incoming and outgoing calls.\n Malicious apps may use this to erase or modify your call logger.",
"description_ptr": "permdesc_writeCallLog",
"label": "write call log",
"label_ptr": "permlab_writeCallLog",

View File

@ -1902,7 +1902,7 @@
"protectionLevel": "dangerous"
},
"android.permission.WRITE_CALL_LOG": {
"description": "Allows the app to modify your phone's call log, including data about incoming and outgoing calls.\n Malicious apps may use this to erase or modify your call log.",
"description": "Allows the app to modify your phone's call log, including data about incoming and outgoing calls.\n Malicious apps may use this to erase or modify your call logger.",
"description_ptr": "permdesc_writeCallLog",
"label": "write call log",
"label_ptr": "permlab_writeCallLog",

View File

@ -1983,7 +1983,7 @@
"protectionLevel": "dangerous"
},
"android.permission.WRITE_CALL_LOG": {
"description": "Allows the app to modify your phone's call log, including data about incoming and outgoing calls.\n Malicious apps may use this to erase or modify your call log.",
"description": "Allows the app to modify your phone's call log, including data about incoming and outgoing calls.\n Malicious apps may use this to erase or modify your call logger.",
"description_ptr": "permdesc_writeCallLog",
"label": "write call log",
"label_ptr": "permlab_writeCallLog",

View File

@ -2172,7 +2172,7 @@
"protectionLevel": "dangerous"
},
"android.permission.WRITE_CALL_LOG": {
"description": "Allows the app to modify your phone's call log, including data about incoming and outgoing calls.\n Malicious apps may use this to erase or modify your call log.",
"description": "Allows the app to modify your phone's call log, including data about incoming and outgoing calls.\n Malicious apps may use this to erase or modify your call logger.",
"description_ptr": "permdesc_writeCallLog",
"label": "write call log",
"label_ptr": "permlab_writeCallLog",

View File

@ -2496,7 +2496,7 @@
"protectionLevel": "dangerous"
},
"android.permission.WRITE_CALL_LOG": {
"description": "Allows the app to modify your phone's call log, including data about incoming and outgoing calls.\n Malicious apps may use this to erase or modify your call log.",
"description": "Allows the app to modify your phone's call log, including data about incoming and outgoing calls.\n Malicious apps may use this to erase or modify your call logger.",
"description_ptr": "permdesc_writeCallLog",
"label": "write call log",
"label_ptr": "permlab_writeCallLog",

View File

@ -2577,7 +2577,7 @@
"protectionLevel": "dangerous"
},
"android.permission.WRITE_CALL_LOG": {
"description": "Allows the app to modify your phone's call log, including data about incoming and outgoing calls.\n Malicious apps may use this to erase or modify your call log.",
"description": "Allows the app to modify your phone's call log, including data about incoming and outgoing calls.\n Malicious apps may use this to erase or modify your call logger.",
"description_ptr": "permdesc_writeCallLog",
"label": "write call log",
"label_ptr": "permlab_writeCallLog",

View File

@ -2730,7 +2730,7 @@
"protectionLevel": "dangerous"
},
"android.permission.WRITE_CALL_LOG": {
"description": "Allows the app to modify your phone's call log, including data about incoming and outgoing calls.\n Malicious apps may use this to erase or modify your call log.",
"description": "Allows the app to modify your phone's call log, including data about incoming and outgoing calls.\n Malicious apps may use this to erase or modify your call logger.",
"description_ptr": "permdesc_writeCallLog",
"label": "write call log",
"label_ptr": "permlab_writeCallLog",

View File

@ -3045,7 +3045,7 @@
"protectionLevel": "dangerous"
},
"android.permission.WRITE_CALL_LOG": {
"description": "Allows the app to modify your phone's call log, including data about incoming and outgoing calls.\n Malicious apps may use this to erase or modify your call log.",
"description": "Allows the app to modify your phone's call log, including data about incoming and outgoing calls.\n Malicious apps may use this to erase or modify your call logger.",
"description_ptr": "permdesc_writeCallLog",
"label": "write call log",
"label_ptr": "permlab_writeCallLog",

View File

@ -3054,7 +3054,7 @@
"protectionLevel": "dangerous"
},
"android.permission.WRITE_CALL_LOG": {
"description": "Allows the app to modify your phone's call log, including data about incoming and outgoing calls.\n Malicious apps may use this to erase or modify your call log.",
"description": "Allows the app to modify your phone's call log, including data about incoming and outgoing calls.\n Malicious apps may use this to erase or modify your call logger.",
"description_ptr": "permdesc_writeCallLog",
"label": "write call log",
"label_ptr": "permlab_writeCallLog",

View File

@ -3369,7 +3369,7 @@
"protectionLevel": "dangerous"
},
"android.permission.WRITE_CALL_LOG": {
"description": "Allows the app to modify your phone's call log, including data about incoming and outgoing calls.\n Malicious apps may use this to erase or modify your call log.",
"description": "Allows the app to modify your phone's call log, including data about incoming and outgoing calls.\n Malicious apps may use this to erase or modify your call logger.",
"description_ptr": "permdesc_writeCallLog",
"label": "write call log",
"label_ptr": "permlab_writeCallLog",

View File

@ -3531,7 +3531,7 @@
"protectionLevel": "dangerous"
},
"android.permission.WRITE_CALL_LOG": {
"description": "Allows the app to modify your phone's call log, including data about incoming and outgoing calls.\n Malicious apps may use this to erase or modify your call log.",
"description": "Allows the app to modify your phone's call log, including data about incoming and outgoing calls.\n Malicious apps may use this to erase or modify your call logger.",
"description_ptr": "permdesc_writeCallLog",
"label": "write call log",
"label_ptr": "permlab_writeCallLog",

View File

@ -3999,7 +3999,7 @@
"protectionLevel": "dangerous"
},
"android.permission.WRITE_CALL_LOG": {
"description": "Allows the app to modify your phone's call log, including data about incoming and outgoing calls.\n Malicious apps may use this to erase or modify your call log.",
"description": "Allows the app to modify your phone's call log, including data about incoming and outgoing calls.\n Malicious apps may use this to erase or modify your call logger.",
"description_ptr": "permdesc_writeCallLog",
"label": "write call log",
"label_ptr": "permlab_writeCallLog",

View File

@ -4704,7 +4704,7 @@
"protectionLevel": "dangerous"
},
"android.permission.WRITE_CALL_LOG": {
"description": "Allows the app to modify your phone's call log, including data about incoming and outgoing calls.\n Malicious apps may use this to erase or modify your call log.",
"description": "Allows the app to modify your phone's call log, including data about incoming and outgoing calls.\n Malicious apps may use this to erase or modify your call logger.",
"description_ptr": "permdesc_writeCallLog",
"label": "write call log",
"label_ptr": "permlab_writeCallLog",

View File

@ -15,19 +15,17 @@ import logging
from struct import unpack
import hashlib
import warnings
import sys
# External dependecies
from loguru import logger
import lxml.sax
from xml.dom.pulldom import SAX2DOM
# Used for reading Certificates
from asn1crypto import cms, x509, keys
from loguru import logger
NS_ANDROID_URI = 'http://schemas.android.com/apk/res/android'
NS_ANDROID = '{{{}}}'.format(NS_ANDROID_URI) # Namespace as used by etree
log = logging.getLogger("androguard.apk")
def parse_lxml_dom(tree):
@ -505,7 +503,7 @@ class APK:
res_id,
ARSCResTableConfig.default_config())[0][1]
except Exception as e:
log.warning("Exception selecting app name: %s" % e)
logger.warning("Exception selecting app name: %s" % e)
return app_name
def get_app_icon(self, max_dpi=65536):
@ -651,14 +649,14 @@ class APK:
import magic
except ImportError:
self.__no_magic = True
log.warning("No Magic library was found on your system.")
logger.warning("No Magic library was found on your system.")
return default
except TypeError as e:
self.__no_magic = True
log.warning("It looks like you have the magic python package installed but not the magic library itself!")
log.warning("Error from magic library: %s", e)
log.warning("Please follow the installation instructions at https://github.com/ahupp/python-magic/#installation")
log.warning("You can also install the 'python-magic-bin' package on Windows and MacOS")
logger.warning("It looks like you have the magic python package installed but not the magic library itself!")
logger.warning("Error from magic library: %s", e)
logger.warning("Please follow the installation instructions at https://github.com/ahupp/python-magic/#installation")
logger.warning("You can also install the 'python-magic-bin' package on Windows and MacOS")
return default
try:
@ -669,7 +667,7 @@ class APK:
getattr(magic, "MagicException")
except AttributeError:
self.__no_magic = True
log.warning("Not the correct Magic library was found on your "
logger.warning("Not the correct Magic library was found on your "
"system. Please install python-magic or python-magic-bin!")
return default
@ -677,7 +675,7 @@ class APK:
# 1024 byte are usually enough to test the magic
ftype = magic.from_buffer(buffer[:1024])
except magic.MagicException as e:
log.exception("Error getting the magic type: %s", e)
logger.exception("Error getting the magic type: %s", e)
return default
if not ftype:
@ -736,7 +734,7 @@ class APK:
if filename not in self.files_crc32:
self.files_crc32[filename] = crc32(buffer)
if self.files_crc32[filename] != self.zip.getinfo(filename).CRC:
log.error("File '{}' has different CRC32 after unpacking! "
logger.error("File '{}' has different CRC32 after unpacking! "
"Declared: {:08x}, Calculated: {:08x}".format(filename,
self.zip.getinfo(filename).CRC,
self.files_crc32[filename]))
@ -833,29 +831,6 @@ class APK:
dexre = re.compile(r"^classes(\d+)?.dex$")
return len([instance for instance in self.get_files() if dexre.search(instance)]) > 1
def get_elements(self, tag_name, attribute, with_namespace=True):
"""
.. deprecated:: 3.3.5
use :meth:`get_all_attribute_value` instead
Return elements in xml files which match with the tag name and the specific attribute
:param str tag_name: a string which specify the tag name
:param str attribute: a string which specify the attribute
"""
warnings.warn("This method is deprecated since 3.3.5.", DeprecationWarning)
for i in self.xml:
if self.xml[i] is None:
continue
for item in self.xml[i].findall('.//' + tag_name):
if with_namespace:
value = item.get(self._ns(attribute))
else:
value = item.get(attribute)
# There might be an attribute without the namespace
if value:
yield self._format_value(value)
def _format_value(self, value):
"""
Format a value with packagename, if not already set.
@ -877,41 +852,6 @@ class APK:
value = self.package + "." + value
return value
def get_element(self, tag_name, attribute, **attribute_filter):
"""
.. deprecated:: 3.3.5
use :meth:`get_attribute_value` instead
Return element in xml files which match with the tag name and the specific attribute
:param str tag_name: specify the tag name
:param str attribute: specify the attribute
:rtype: str
"""
warnings.warn("This method is deprecated since 3.3.5.", DeprecationWarning)
for i in self.xml:
if self.xml[i] is None:
continue
tag = self.xml[i].findall('.//' + tag_name)
if len(tag) == 0:
return None
for item in tag:
skip_this_item = False
for attr, val in list(attribute_filter.items()):
attr_val = item.get(self._ns(attr))
if attr_val != val:
skip_this_item = True
break
if skip_this_item:
continue
value = item.get(self._ns(attribute))
if value is not None:
return value
return None
def get_all_attribute_value(
self, tag_name, attribute, format_value=True, **attribute_filter
):
@ -987,7 +927,7 @@ class APK:
if value:
# If value is still None, the attribute could not be found, thus is not present
log.warning("Failed to get the attribute '{}' on tag '{}' with namespace. "
logger.warning("Failed to get the attribute '{}' on tag '{}' with namespace. "
"But found the same attribute without namespace!".format(attribute, tag.tag))
return value
@ -1088,7 +1028,7 @@ class APK:
if activity is not None:
x.add(item.get(self._ns("name")))
else:
log.warning('Main activity without name')
logger.warning('Main activity without name')
for sitem in item.findall(".//category"):
val = sitem.get(self._ns("name"))
@ -1097,7 +1037,7 @@ class APK:
if activity is not None:
y.add(item.get(self._ns("name")))
else:
log.warning('Launcher activity without name')
logger.warning('Launcher activity without name')
return x.intersection(y)
@ -1190,7 +1130,7 @@ class APK:
res_id,
ARSCResTableConfig.default_config())[0][1]
except Exception as e:
log.warning("Exception get resolved resource id: %s" % e)
logger.warning("Exception get resolved resource id: %s" % e)
return name
return value
@ -1354,20 +1294,6 @@ class APK:
"Unknown permission from android reference"]
return self._fill_deprecated_permissions(l)
def get_requested_permissions(self):
"""
.. deprecated:: 3.1.0
use :meth:`get_permissions` instead.
Returns all requested permissions.
It has the same result as :meth:`get_permissions` and might be removed in the future
:rtype: list of str
"""
warnings.warn("This method is deprecated since 3.1.0.", DeprecationWarning)
return self.get_permissions()
def get_requested_aosp_permissions(self):
"""
Returns requested permissions declared within AOSP project.
@ -2093,7 +2019,7 @@ class APK:
if "{}.SF".format(i.rsplit(".", 1)[0]) in self.get_files():
signatures.append(i)
else:
log.warning("v1 signature file {} missing .SF file - Partial signature!".format(i))
logger.warning("v1 signature file {} missing .SF file - Partial signature!".format(i))
return signatures

View File

@ -8,11 +8,9 @@ from collections import defaultdict
from lxml import etree
from loguru import logger
import logging
import re
import binascii
import io
import sys
# Constants for ARSC Files
# see http://androidxref.com/9.0.0_r3/xref/frameworks/base/libs/androidfw/include/androidfw/ResourceTypes.h#215

View File

@ -5,7 +5,7 @@ import textwrap
import json
from androguard.core.androconf import CONF, color_range
from androguard.core.dex.dvm_types import Kind, Operand
from androguard.core.dex.dex_types import Kind, Operand
def _PrintBanner():
@ -462,7 +462,7 @@ def method2format(output, _format="png", mx=None, raw=None):
d = pydot.graph_from_dot_data(buff)
if len(d) > 1:
# Not sure what to do in this case?!
log.warnig("The graph generated for '{}' has too many subgraphs! "
logger.warnig("The graph generated for '{}' has too many subgraphs! "
"Only plotting the first one.".format(output))
for g in d:
getattr(g, "write_" + _format.lower())(output)
@ -509,7 +509,7 @@ def vm2json(vm):
"""
Get a JSON representation of a DEX file
:param vm: :class:`~androguard.core.bytecodes.dvm.DalvikVMFormat`
:param vm: :class:`~androguard.core.bytecodes.dvm.DEX`
:return:
"""
d = {"name": "root", "children": []}

View File

@ -19,7 +19,7 @@ from loguru import logger
from androguard.core import mutf8
from .dvm_types import (
from .dex_types import (
TypeMapItem,
ACCESS_FLAGS,
TYPE_DESCRIPTOR,
@ -172,7 +172,7 @@ FIELD_WRITE_DVM_OPCODES = [".put"]
BREAK_DVM_OPCODES = ["invoke.", "move.", ".put", "if."]
BRANCH_DVM_OPCODES = ["throw", "throw.", "if.", "goto", "goto.", "return",
BRANCH_DEX_OPCODES = ["throw", "throw.", "if.", "goto", "goto.", "return",
"return.", "packed-switch$", "sparse-switch$"]
@ -399,7 +399,7 @@ def determineException(vm, m):
"""
Returns try-catch handler inside the method.
:param vm: a :class:`~DalvikVMFormat`
:param vm: a :class:`~DEX`
:param m: a :class:`~EncodedMethod`
:return:
"""
@ -4373,20 +4373,6 @@ class Instruction:
"""
raise Exception("not implemented")
def get_formatted_operands(self):
"""
Returns the formatted operands, if any.
This is a list with the parsed and interpreted operands
of the opcode.
Returns None if no operands, otherwise a List
.. deprecated:: 3.4.0
Will be removed! This method always returns None
"""
warnings.warn("deprecated, this class will be removed!", DeprecationWarning)
return None
def get_hex(self):
"""
Returns a HEX String, separated by spaces every byte
@ -6491,9 +6477,7 @@ class LinearSweepAlgorithm:
else:
obj = get_instruction(cm, op_value & 0xff, insn[idx:])
except InvalidInstruction as e:
# TODO somehow it would be nice to know that the parsing failed at the level of EncodedMethod or for the decompiler
logger.error("Invalid instruction encountered! Stop parsing bytecode at idx %s. Message: %s", idx, e)
return
raise InvalidInstruction("Invalid instruction encountered! Stop parsing bytecode at idx %s. Message: %s", idx, e)
# emit instruction
yield obj
idx += obj.get_length()
@ -7186,16 +7170,6 @@ class MapItem:
self.item = item
class OffObj:
def __init__(self, o):
"""
.. deprecated:: 3.3.5
Will be removed!
"""
warnings.warn("deprecated, this class will be removed!", DeprecationWarning)
self.off = o
class ClassManager:
"""
This class is used to access to all elements (strings, type, proto ...) of the dex format
@ -7204,7 +7178,7 @@ class ClassManager:
def __init__(self, vm):
"""
:param DalvikVMFormat vm: the VM to create a ClassManager for
:param DEX vm: the VM to create a ClassManager for
"""
self.vm = vm
self.buff = vm
@ -7270,14 +7244,6 @@ class ClassManager:
def get_string_by_offset(self, offset):
return self.__strings_off[offset]
def get_lazy_analysis(self):
"""
.. deprecated:: 3.3.5
do not use this function anymore!
"""
warnings.warn("deprecated, this method always returns False!", DeprecationWarning)
return False
def set_decompiler(self, decompiler):
self.decompiler_ob = decompiler
@ -7287,22 +7253,6 @@ class ClassManager:
def get_analysis(self):
return self.analysis_dex
def get_engine(self):
"""
.. deprecated:: 3.3.5
do not use this function anymore!
"""
warnings.warn("deprecated, this method always returns None!", DeprecationWarning)
return None
def get_all_engine(self):
"""
.. deprecated:: 3.3.5
do not use this function anymore!
"""
warnings.warn("deprecated, this method always returns None!", DeprecationWarning)
return None
def add_type_item(self, type_item, c_item, item):
self.__manage_item[type_item] = item
@ -8332,30 +8282,6 @@ class DEX:
j.get_descriptor()))
setattr(_class.F, name, j)
def get_BRANCH_DVM_OPCODES(self):
"""
.. deprecated:: 3.4.0
Will be removed!
"""
warnings.warn("deprecated, this method will be removed!", DeprecationWarning)
return BRANCH_DVM_OPCODES
def get_determineNext(self):
"""
.. deprecated:: 3.4.0
Will be removed!
"""
warnings.warn("deprecated, this method will be removed!", DeprecationWarning)
return determineNext
def get_determineException(self):
"""
.. deprecated:: 3.4.0
Will be removed!
"""
warnings.warn("deprecated, this method will be removed!", DeprecationWarning)
return determineException
def set_decompiler(self, decompiler):
self.CM.set_decompiler(decompiler)
@ -8429,26 +8355,6 @@ class DEX:
return Root
def print_classes_hierarchy(self):
"""
.. deprecated:: 3.4.0
Will be removed!
"""
warnings.warn("deprecated, this method will be removed!", DeprecationWarning)
def print_map(node, l, lvl=0):
for n in node.children:
if lvl == 0:
l.append("%s" % n.title)
else:
l.append("{} {}".format('\t' * lvl, n.title))
if len(n.children) > 0:
print_map(n, l, lvl + 1)
l = []
print_map(self._get_class_hierarchy(), l)
return l
def list_classes_hierarchy(self):
"""
Get a tree structure of the classes.
@ -8479,46 +8385,6 @@ class DEX:
return l
def get_format(self):
"""
.. deprecated:: 3.4.0
Will be removed!
"""
warnings.warn("deprecated, this method will be removed!", DeprecationWarning)
objs = self.map_list.get_obj()
h = {}
index = {}
self._get_objs(h, index, objs)
return h, index
def _get_objs(self, h, index, objs):
"""
.. deprecated:: 3.4.0
Will be removed!
"""
warnings.warn("deprecated, this method will be removed!", DeprecationWarning)
for i in objs:
if isinstance(i, list):
self._get_objs(h, index, i)
else:
try:
if i is not None:
h[i] = {}
index[i] = i.offset
except AttributeError:
pass
try:
if not isinstance(i, MapList):
next_objs = i.get_obj()
if isinstance(next_objs, list):
self._get_objs(h[i], index, next_objs)
except AttributeError:
pass
class OdexHeaderItem:
"""
This class can parse the odex header

View File

@ -1544,13 +1544,13 @@
"Theme.Holo.NoActionBar.Fullscreen": 16973933,
"Theme.Holo.Light": 16973934,
"Theme.Holo.Dialog": 16973935,
"Theme.Holo.Dialog.MinWidth": 16973936,
"Theme.Holo.Dialog.NoActionBar": 16973937,
"Theme.Holo.Dialog.NoActionBar.MinWidth": 16973938,
"Theme.Holo.Dialogger.MinWidth": 16973936,
"Theme.Holo.Dialogger.NoActionBar": 16973937,
"Theme.Holo.Dialogger.NoActionBar.MinWidth": 16973938,
"Theme.Holo.Light.Dialog": 16973939,
"Theme.Holo.Light.Dialog.MinWidth": 16973940,
"Theme.Holo.Light.Dialog.NoActionBar": 16973941,
"Theme.Holo.Light.Dialog.NoActionBar.MinWidth": 16973942,
"Theme.Holo.Light.Dialogger.MinWidth": 16973940,
"Theme.Holo.Light.Dialogger.NoActionBar": 16973941,
"Theme.Holo.Light.Dialogger.NoActionBar.MinWidth": 16973942,
"Theme.Holo.DialogWhenLarge": 16973943,
"Theme.Holo.DialogWhenLarge.NoActionBar": 16973944,
"Theme.Holo.Light.DialogWhenLarge": 16973945,
@ -1735,13 +1735,13 @@
"Theme.DeviceDefault.Light.NoActionBar": 16974124,
"Theme.DeviceDefault.Light.NoActionBar.Fullscreen": 16974125,
"Theme.DeviceDefault.Dialog": 16974126,
"Theme.DeviceDefault.Dialog.MinWidth": 16974127,
"Theme.DeviceDefault.Dialog.NoActionBar": 16974128,
"Theme.DeviceDefault.Dialog.NoActionBar.MinWidth": 16974129,
"Theme.DeviceDefault.Dialogger.MinWidth": 16974127,
"Theme.DeviceDefault.Dialogger.NoActionBar": 16974128,
"Theme.DeviceDefault.Dialogger.NoActionBar.MinWidth": 16974129,
"Theme.DeviceDefault.Light.Dialog": 16974130,
"Theme.DeviceDefault.Light.Dialog.MinWidth": 16974131,
"Theme.DeviceDefault.Light.Dialog.NoActionBar": 16974132,
"Theme.DeviceDefault.Light.Dialog.NoActionBar.MinWidth": 16974133,
"Theme.DeviceDefault.Light.Dialogger.MinWidth": 16974131,
"Theme.DeviceDefault.Light.Dialogger.NoActionBar": 16974132,
"Theme.DeviceDefault.Light.Dialogger.NoActionBar.MinWidth": 16974133,
"Theme.DeviceDefault.DialogWhenLarge": 16974134,
"Theme.DeviceDefault.DialogWhenLarge.NoActionBar": 16974135,
"Theme.DeviceDefault.Light.DialogWhenLarge": 16974136,
@ -1982,11 +1982,11 @@
"Theme.DeviceDefault.Settings": 16974371,
"Theme.Material": 16974372,
"Theme.Material.Dialog": 16974373,
"Theme.Material.Dialog.Alert": 16974374,
"Theme.Material.Dialog.MinWidth": 16974375,
"Theme.Material.Dialog.NoActionBar": 16974376,
"Theme.Material.Dialog.NoActionBar.MinWidth": 16974377,
"Theme.Material.Dialog.Presentation": 16974378,
"Theme.Material.Dialogger.Alert": 16974374,
"Theme.Material.Dialogger.MinWidth": 16974375,
"Theme.Material.Dialogger.NoActionBar": 16974376,
"Theme.Material.Dialogger.NoActionBar.MinWidth": 16974377,
"Theme.Material.Dialogger.Presentation": 16974378,
"Theme.Material.DialogWhenLarge": 16974379,
"Theme.Material.DialogWhenLarge.NoActionBar": 16974380,
"Theme.Material.InputMethod": 16974381,
@ -2002,11 +2002,11 @@
"Theme.Material.Light": 16974391,
"Theme.Material.Light.DarkActionBar": 16974392,
"Theme.Material.Light.Dialog": 16974393,
"Theme.Material.Light.Dialog.Alert": 16974394,
"Theme.Material.Light.Dialog.MinWidth": 16974395,
"Theme.Material.Light.Dialog.NoActionBar": 16974396,
"Theme.Material.Light.Dialog.NoActionBar.MinWidth": 16974397,
"Theme.Material.Light.Dialog.Presentation": 16974398,
"Theme.Material.Light.Dialogger.Alert": 16974394,
"Theme.Material.Light.Dialogger.MinWidth": 16974395,
"Theme.Material.Light.Dialogger.NoActionBar": 16974396,
"Theme.Material.Light.Dialogger.NoActionBar.MinWidth": 16974397,
"Theme.Material.Light.Dialogger.Presentation": 16974398,
"Theme.Material.Light.DialogWhenLarge": 16974399,
"Theme.Material.Light.DialogWhenLarge.NoActionBar": 16974400,
"Theme.Material.Light.NoActionBar": 16974401,
@ -2153,13 +2153,13 @@
"Widget.Material.Light.WebTextView": 16974542,
"Widget.Material.Light.WebView": 16974543,
"Theme.Leanback.FormWizard": 16974544,
"Theme.DeviceDefault.Dialog.Alert": 16974545,
"Theme.DeviceDefault.Light.Dialog.Alert": 16974546,
"Theme.DeviceDefault.Dialogger.Alert": 16974545,
"Theme.DeviceDefault.Light.Dialogger.Alert": 16974546,
"Widget.Material.Button.Colored": 16974547,
"TextAppearance.Material.Widget.Button.Inverse": 16974548,
"Theme.Material.Light.LightStatusBar": 16974549,
"ThemeOverlay.Material.Dialog": 16974550,
"ThemeOverlay.Material.Dialog.Alert": 16974551,
"ThemeOverlay.Material.Dialogger.Alert": 16974551,
"Theme.Material.Light.DialogWhenLarge.DarkActionBar": 16974552,
"Widget.Material.SeekBar.Discrete": 16974553,
"Widget.Material.CompoundButton.Switch": 16974554,

View File

@ -1507,13 +1507,13 @@
<public type="style" name="Theme.Holo.NoActionBar.Fullscreen" id="0x0103006d" />
<public type="style" name="Theme.Holo.Light" id="0x0103006e" />
<public type="style" name="Theme.Holo.Dialog" id="0x0103006f" />
<public type="style" name="Theme.Holo.Dialog.MinWidth" id="0x01030070" />
<public type="style" name="Theme.Holo.Dialog.NoActionBar" id="0x01030071" />
<public type="style" name="Theme.Holo.Dialog.NoActionBar.MinWidth" id="0x01030072" />
<public type="style" name="Theme.Holo.Dialogger.MinWidth" id="0x01030070" />
<public type="style" name="Theme.Holo.Dialogger.NoActionBar" id="0x01030071" />
<public type="style" name="Theme.Holo.Dialogger.NoActionBar.MinWidth" id="0x01030072" />
<public type="style" name="Theme.Holo.Light.Dialog" id="0x01030073" />
<public type="style" name="Theme.Holo.Light.Dialog.MinWidth" id="0x01030074" />
<public type="style" name="Theme.Holo.Light.Dialog.NoActionBar" id="0x01030075" />
<public type="style" name="Theme.Holo.Light.Dialog.NoActionBar.MinWidth" id="0x01030076" />
<public type="style" name="Theme.Holo.Light.Dialogger.MinWidth" id="0x01030074" />
<public type="style" name="Theme.Holo.Light.Dialogger.NoActionBar" id="0x01030075" />
<public type="style" name="Theme.Holo.Light.Dialogger.NoActionBar.MinWidth" id="0x01030076" />
<public type="style" name="Theme.Holo.DialogWhenLarge" id="0x01030077" />
<public type="style" name="Theme.Holo.DialogWhenLarge.NoActionBar" id="0x01030078" />
<public type="style" name="Theme.Holo.Light.DialogWhenLarge" id="0x01030079" />
@ -1792,13 +1792,13 @@
<public type="style" name="Theme.DeviceDefault.Light.NoActionBar" id="0x0103012c" />
<public type="style" name="Theme.DeviceDefault.Light.NoActionBar.Fullscreen" id="0x0103012d" />
<public type="style" name="Theme.DeviceDefault.Dialog" id="0x0103012e" />
<public type="style" name="Theme.DeviceDefault.Dialog.MinWidth" id="0x0103012f" />
<public type="style" name="Theme.DeviceDefault.Dialog.NoActionBar" id="0x01030130" />
<public type="style" name="Theme.DeviceDefault.Dialog.NoActionBar.MinWidth" id="0x01030131" />
<public type="style" name="Theme.DeviceDefault.Dialogger.MinWidth" id="0x0103012f" />
<public type="style" name="Theme.DeviceDefault.Dialogger.NoActionBar" id="0x01030130" />
<public type="style" name="Theme.DeviceDefault.Dialogger.NoActionBar.MinWidth" id="0x01030131" />
<public type="style" name="Theme.DeviceDefault.Light.Dialog" id="0x01030132" />
<public type="style" name="Theme.DeviceDefault.Light.Dialog.MinWidth" id="0x01030133" />
<public type="style" name="Theme.DeviceDefault.Light.Dialog.NoActionBar" id="0x01030134" />
<public type="style" name="Theme.DeviceDefault.Light.Dialog.NoActionBar.MinWidth" id="0x01030135" />
<public type="style" name="Theme.DeviceDefault.Light.Dialogger.MinWidth" id="0x01030133" />
<public type="style" name="Theme.DeviceDefault.Light.Dialogger.NoActionBar" id="0x01030134" />
<public type="style" name="Theme.DeviceDefault.Light.Dialogger.NoActionBar.MinWidth" id="0x01030135" />
<public type="style" name="Theme.DeviceDefault.DialogWhenLarge" id="0x01030136" />
<public type="style" name="Theme.DeviceDefault.DialogWhenLarge.NoActionBar" id="0x01030137" />
<public type="style" name="Theme.DeviceDefault.Light.DialogWhenLarge" id="0x01030138" />
@ -2369,11 +2369,11 @@
<public type="style" name="Theme.Material" id="0x01030224" />
<public type="style" name="Theme.Material.Dialog" id="0x01030225" />
<public type="style" name="Theme.Material.Dialog.Alert" id="0x01030226" />
<public type="style" name="Theme.Material.Dialog.MinWidth" id="0x01030227" />
<public type="style" name="Theme.Material.Dialog.NoActionBar" id="0x01030228" />
<public type="style" name="Theme.Material.Dialog.NoActionBar.MinWidth" id="0x01030229" />
<public type="style" name="Theme.Material.Dialog.Presentation" id="0x0103022a" />
<public type="style" name="Theme.Material.Dialogger.Alert" id="0x01030226" />
<public type="style" name="Theme.Material.Dialogger.MinWidth" id="0x01030227" />
<public type="style" name="Theme.Material.Dialogger.NoActionBar" id="0x01030228" />
<public type="style" name="Theme.Material.Dialogger.NoActionBar.MinWidth" id="0x01030229" />
<public type="style" name="Theme.Material.Dialogger.Presentation" id="0x0103022a" />
<public type="style" name="Theme.Material.DialogWhenLarge" id="0x0103022b" />
<public type="style" name="Theme.Material.DialogWhenLarge.NoActionBar" id="0x0103022c" />
<public type="style" name="Theme.Material.InputMethod" id="0x0103022d" />
@ -2390,11 +2390,11 @@
<public type="style" name="Theme.Material.Light" id="0x01030237" />
<public type="style" name="Theme.Material.Light.DarkActionBar" id="0x01030238" />
<public type="style" name="Theme.Material.Light.Dialog" id="0x01030239" />
<public type="style" name="Theme.Material.Light.Dialog.Alert" id="0x0103023a" />
<public type="style" name="Theme.Material.Light.Dialog.MinWidth" id="0x0103023b" />
<public type="style" name="Theme.Material.Light.Dialog.NoActionBar" id="0x0103023c" />
<public type="style" name="Theme.Material.Light.Dialog.NoActionBar.MinWidth" id="0x0103023d" />
<public type="style" name="Theme.Material.Light.Dialog.Presentation" id="0x0103023e" />
<public type="style" name="Theme.Material.Light.Dialogger.Alert" id="0x0103023a" />
<public type="style" name="Theme.Material.Light.Dialogger.MinWidth" id="0x0103023b" />
<public type="style" name="Theme.Material.Light.Dialogger.NoActionBar" id="0x0103023c" />
<public type="style" name="Theme.Material.Light.Dialogger.NoActionBar.MinWidth" id="0x0103023d" />
<public type="style" name="Theme.Material.Light.Dialogger.Presentation" id="0x0103023e" />
<public type="style" name="Theme.Material.Light.DialogWhenLarge" id="0x0103023f" />
<public type="style" name="Theme.Material.Light.DialogWhenLarge.NoActionBar" id="0x01030240" />
<public type="style" name="Theme.Material.Light.NoActionBar" id="0x01030241" />
@ -2601,8 +2601,8 @@
<public type="attr" name="drawableTintMode" id="0x010104d7" />
<public type="attr" name="fraction" id="0x010104d8" />
<public type="style" name="Theme.DeviceDefault.Dialog.Alert" id="0x010302d1" />
<public type="style" name="Theme.DeviceDefault.Light.Dialog.Alert" id="0x010302d2" />
<public type="style" name="Theme.DeviceDefault.Dialogger.Alert" id="0x010302d1" />
<public type="style" name="Theme.DeviceDefault.Light.Dialogger.Alert" id="0x010302d2" />
<!-- ===============================================================
Resources added in version M of the platform
@ -2639,7 +2639,7 @@
<public type="style" name="TextAppearance.Material.Widget.Button.Inverse" id="0x010302d4" />
<public type="style" name="Theme.Material.Light.LightStatusBar" id="0x010302d5" />
<public type="style" name="ThemeOverlay.Material.Dialog" id="0x010302d6" />
<public type="style" name="ThemeOverlay.Material.Dialog.Alert" id="0x010302d7" />
<public type="style" name="ThemeOverlay.Material.Dialogger.Alert" id="0x010302d7" />
<public type="id" name="pasteAsPlainText" id="0x01020031" />
<public type="id" name="undo" id="0x01020032" />

View File

@ -1 +1,3 @@
import sys
sys.setrecursionlimit(5000)

View File

@ -15,12 +15,11 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import logging
from collections import defaultdict
from androguard.decompiler.dad.opcode_ins import INSTRUCTION_SET
from androguard.decompiler.dad.instruction import MoveExceptionExpression
from androguard.decompiler.dad.node import Node
from androguard.decompiler.dad.util import get_type
from androguard.decompiler.opcode_ins import INSTRUCTION_SET
from androguard.decompiler.instruction import MoveExceptionExpression
from androguard.decompiler.node import Node
from androguard.decompiler.util import get_type
from loguru import logger

View File

@ -15,13 +15,12 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import logging
from collections import defaultdict
from androguard.decompiler.dad.basic_blocks import (
from androguard.decompiler.basic_blocks import (
CatchBlock, Condition, LoopBlock, ShortCircuitBlock, TryBlock)
from androguard.decompiler.dad.graph import Graph
from androguard.decompiler.dad.node import Interval
from androguard.decompiler.dad.util import common_dom
from androguard.decompiler.graph import Graph
from androguard.decompiler.node import Interval
from androguard.decompiler.util import common_dom
from loguru import logger

View File

@ -1 +0,0 @@

View File

@ -16,8 +16,8 @@
"""This file is a simplified version of writer.py that outputs an AST instead of source code."""
import struct
from androguard.decompiler.dad import basic_blocks, instruction, opcode_ins
from androguard.core.dex.dvm_types import TYPE_DESCRIPTOR
from androguard.decompiler import basic_blocks, instruction, opcode_ins
from androguard.core.dex.dex_types import TYPE_DESCRIPTOR
from loguru import logger

View File

@ -15,11 +15,10 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import logging
from collections import defaultdict
from androguard.decompiler.dad.instruction import (Variable, ThisParam, Param)
from androguard.decompiler.dad.util import build_path, common_dom
from androguard.decompiler.dad.node import Node
from androguard.decompiler.instruction import (Variable, ThisParam, Param)
from androguard.decompiler.util import build_path, common_dom
from androguard.decompiler.node import Node
from loguru import logger

View File

@ -14,7 +14,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import logging
import struct
import sys
from collections import defaultdict
@ -22,27 +22,27 @@ from collections import defaultdict
from loguru import logger
import androguard.core.androconf as androconf
import androguard.decompiler.dad.util as util
import androguard.decompiler.util as util
from androguard.core.analysis import analysis
from androguard.core import apk, dex
from androguard.decompiler.dad.control_flow import identify_structures
from androguard.decompiler.dad.dast import (
from androguard.decompiler.control_flow import identify_structures
from androguard.decompiler.dast import (
JSONWriter,
parse_descriptor,
literal_string,
literal_hex_int,
dummy
)
from androguard.decompiler.dad.dataflow import (
from androguard.decompiler.dataflow import (
build_def_use,
place_declarations,
dead_code_elimination,
register_propagation,
split_variables
)
from androguard.decompiler.dad.graph import construct, simplify, split_if_nodes
from androguard.decompiler.dad.instruction import Param, ThisParam
from androguard.decompiler.dad.writer import Writer
from androguard.decompiler.graph import construct, simplify, split_if_nodes
from androguard.decompiler.instruction import Param, ThisParam
from androguard.decompiler.writer import Writer
from androguard.util import readFile
logger.add(sys.stderr, format="{time} {level} {message}", filter="dad", level="INFO")
@ -446,7 +446,7 @@ class DvMachine:
Wrapper class for a Dalvik Object, like a DEX or ODEX file.
The wrapper allows to take a Dalvik file and get a list of Classes out of it.
The :class:`~androguard.decompiler.dad.decompile.DvMachine` can take either an APK file directly,
The :class:`~androguard.decompiler.decompile.DvMachine` can take either an APK file directly,
where all DEX files from the multidex are used, or a single DEX or ODEX file as an argument.
At first, :py:attr:`classes` contains only :class:`~androguard.core.bytecodes.dvm.ClassDefItem` as values.
@ -507,7 +507,7 @@ class DvMachine:
"""
Process all classes inside the machine.
This calls :meth:`~androgaurd.decompiler.dad.decompile.DvClass.process` on each :class:`DvClass`.
This calls :meth:`~androgaurd.decompiler.decompile.DvClass.process` on each :class:`DvClass`.
"""
for name, klass in self.classes.items():
logger.debug('Processing class: %s', name)
@ -522,7 +522,7 @@ class DvMachine:
Calls `show_source` on all classes inside the machine.
This prints the source to stdout.
This calls :meth:`~androgaurd.decompiler.dad.decompile.DvClass.show_source` on each :class:`DvClass`.
This calls :meth:`~androgaurd.decompiler.decompile.DvClass.show_source` on each :class:`DvClass`.
"""
for klass in self.classes.values():
klass.show_source()
@ -554,53 +554,3 @@ class DvMachine:
cls.process(doAST=True)
ret[name] = cls.get_ast()
return ret
sys.setrecursionlimit(5000)
def main():
# logger.setLevel(logging.DEBUG) for debugging output
# comment the line to disable the logging.
logger.setLevel(logging.INFO)
console_hdlr = logging.StreamHandler(sys.stdout)
console_hdlr.setFormatter(logging.Formatter('%(levelname)s: %(message)s'))
logger.addHandler(console_hdlr)
default_file = 'examples/android/TestsAndroguard/bin/TestActivity.apk'
if len(sys.argv) > 1:
machine = DvMachine(sys.argv[1])
else:
machine = DvMachine(default_file)
logger.info('========================')
logger.info('Classes:')
for class_name in sorted(machine.get_classes()):
logger.info(' %s', class_name)
logger.info('========================')
cls_name = input('Choose a class (* for all classes): ')
if cls_name == '*':
machine.process_and_show()
else:
cls = machine.get_class(cls_name)
if cls is None:
logger.error('%s not found.', cls_name)
else:
logger.info('======================')
for i, method in enumerate(cls.get_methods()):
logger.info('%d: %s', i, method.name)
logger.info('======================')
meth = input('Method (* for all methods): ')
if meth == '*':
logger.info('CLASS = %s', cls)
cls.process()
else:
cls.process_method(int(meth))
logger.info('Source:')
logger.info('===========================')
cls.show_source()
if __name__ == '__main__':
main()

View File

@ -15,553 +15,27 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from subprocess import Popen, PIPE, STDOUT
from androguard.decompiler import decompile
import tempfile
import os
from androguard.core.androconf import rrmdir
from androguard.decompiler.dad import decompile
from androguard.util import readFile
from pygments.filter import Filter
from pygments import highlight
from pygments.lexers import get_lexer_by_name
from pygments.formatters import TerminalFormatter
from pygments.token import Token
import logging
import warnings
from loguru import logger
class JADXDecompilerError(Exception):
"""
Exception for JADX related problems
"""
pass
class MethodFilter(Filter):
def __init__(self, **options):
"""
Filter Method Code from a whole class
:param options:
"""
Filter.__init__(self, **options)
self.method_name = options["method_name"]
# self.descriptor = options["descriptor"]
self.present = False
self.get_desc = True # False
def filter(self, lexer, stream):
a = []
l = []
rep = []
for ttype, value in stream:
if self.method_name == value and (ttype is Token.Name.Function or
ttype is Token.Name):
# print ttype, value
item_decl = -1
for i in range(len(a) - 1, 0, -1):
if a[i][0] is Token.Keyword.Declaration:
if a[i][1] != "class":
item_decl = i
break
if item_decl != -1:
self.present = True
l.extend(a[item_decl:])
if self.present and ttype is Token.Keyword.Declaration:
item_end = -1
for i in range(len(l) - 1, 0, -1):
if l[i][0] is Token.Operator and l[i][1] == "}":
item_end = i
break
if item_end != -1:
rep.extend(l[:item_end + 1])
l = []
self.present = False
if self.present:
l.append((ttype, value))
a.append((ttype, value))
if self.present:
nb = 0
item_end = -1
for i in range(len(l) - 1, 0, -1):
if l[i][0] is Token.Operator and l[i][1] == "}":
nb += 1
if nb == 2:
item_end = i
break
rep.extend(l[:item_end + 1])
return rep
# TODO move it somewhere else
class Dex2Jar:
def __init__(self,
vm,
bin_dex2jar="dex2jar.sh",
tmp_dir="/tmp/"):
"""
DEX2JAR is a tool to convert a Dalvik file into Java Classes
:param vm:
:param bin_dex2jar:
:param tmp_dir:
"""
pathtmp = tmp_dir
if not os.path.exists(pathtmp):
os.makedirs(pathtmp)
fd, fdname = tempfile.mkstemp(dir=pathtmp)
with os.fdopen(fd, "w+b") as fd:
fd.write(vm.get_buff())
fd.flush()
cmd = Popen([bin_dex2jar, fdname],
stdout=PIPE,
stderr=STDOUT)
stdout, stderr = cmd.communicate()
os.unlink(fdname)
self.jarfile = fdname + "_dex2jar.jar"
def get_jar(self):
return self.jarfile
class DecompilerDex2Jad:
def __init__(self,
vm,
bin_dex2jar="dex2jar.sh",
bin_jad="jad",
tmp_dir="/tmp/"):
"""
Decompiler interface for JAD
JAD is not a native Dalvik decompiler, therefore dex2jar is required.
.. deprecated:: 3.3.5
JAD is not supported anymore in androguard!
:param vm:
:param bin_dex2jar:
:param bin_jad:
:param tmp_dir:
"""
warnings.warn("JAD is deprecated since 3.3.5.", DeprecationWarning)
self.classes = {}
self.classes_failed = []
pathtmp = tmp_dir
if not os.path.exists(pathtmp):
os.makedirs(pathtmp)
fd, fdname = tempfile.mkstemp(dir=pathtmp)
with os.fdopen(fd, "w+b") as fd:
fd.write(vm.get_buff())
fd.flush()
cmd = Popen([bin_dex2jar, fdname],
stdout=PIPE,
stderr=STDOUT)
stdout, stderr = cmd.communicate()
os.unlink(fdname)
pathclasses = fdname + "dex2jar/"
cmd = Popen(["unzip", fdname + "_dex2jar.jar", "-d", pathclasses],
stdout=PIPE,
stderr=STDOUT)
stdout, stderr = cmd.communicate()
os.unlink(fdname + "_dex2jar.jar")
for root, dirs, files in os.walk(pathclasses, followlinks=True):
if files:
for f in files:
real_filename = root
if real_filename[-1] != "/":
real_filename += "/"
real_filename += f
cmd = Popen([bin_jad, "-o", "-d", root,
real_filename],
stdout=PIPE,
stderr=STDOUT)
stdout, stderr = cmd.communicate()
for i in vm.get_classes():
fname = pathclasses + "/" + i.get_name()[1:-1] + ".jad"
if os.path.isfile(fname):
self.classes[i.get_name()] = readFile(fname, binary=False)
else:
self.classes_failed.append(i.get_name())
rrmdir(pathclasses)
def get_source_method(self, method):
class_name = method.get_class_name()
method_name = method.get_name()
if class_name not in self.classes:
return ""
lexer = get_lexer_by_name("java", stripall=True)
lexer.add_filter(MethodFilter(method_name=method_name))
formatter = TerminalFormatter()
result = highlight(self.classes[class_name], lexer, formatter)
return result
def display_source(self, method):
print(self.get_source_method(method))
def get_source_class(self, _class):
return self.classes[_class.get_name()]
def get_all(self, class_name):
if class_name not in self.classes:
return ""
lexer = get_lexer_by_name("java", stripall=True)
formatter = TerminalFormatter()
result = highlight(self.classes[class_name], lexer, formatter)
return result
def display_all(self, _class):
print(self.get_all(_class.get_name()))
class DecompilerDex2WineJad:
def __init__(self,
vm,
bin_dex2jar="dex2jar.sh",
bin_jad="jad",
tmp_dir="/tmp/"):
"""
Use JAD on wine
.. deprecated:: 3.3.5
JAD is not supported anymore by androguard!
:param vm:
:param bin_dex2jar:
:param bin_jad:
:param tmp_dir:
"""
warnings.warn("JAD is deprecated since 3.3.5.", DeprecationWarning)
self.classes = {}
self.classes_failed = []
pathtmp = tmp_dir
if not os.path.exists(pathtmp):
os.makedirs(pathtmp)
fd, fdname = tempfile.mkstemp(dir=pathtmp)
with os.fdopen(fd, "w+b") as fd:
fd.write(vm.get_buff())
fd.flush()
cmd = Popen([bin_dex2jar, fdname],
stdout=PIPE,
stderr=STDOUT)
stdout, stderr = cmd.communicate()
os.unlink(fdname)
pathclasses = fdname + "dex2jar/"
cmd = Popen(["unzip", fdname + "_dex2jar.jar", "-d", pathclasses],
stdout=PIPE,
stderr=STDOUT)
stdout, stderr = cmd.communicate()
os.unlink(fdname + "_dex2jar.jar")
for root, dirs, files in os.walk(pathclasses, followlinks=True):
if files:
for f in files:
real_filename = root
if real_filename[-1] != "/":
real_filename += "/"
real_filename += f
cmd = Popen(["wine", bin_jad, "-o", "-d",
root, real_filename],
stdout=PIPE,
stderr=STDOUT)
stdout, stderr = cmd.communicate()
for i in vm.get_classes():
fname = pathclasses + "/" + i.get_name()[1:-1] + ".jad"
if os.path.isfile(fname):
self.classes[i.get_name()] = readFile(fname, binary=False)
else:
self.classes_failed.append(i.get_name())
rrmdir(pathclasses)
def get_source_method(self, method):
class_name = method.get_class_name()
method_name = method.get_name()
if class_name not in self.classes:
return ""
lexer = get_lexer_by_name("java", stripall=True)
lexer.add_filter(MethodFilter(method_name=method_name))
formatter = TerminalFormatter()
result = highlight(self.classes[class_name], lexer, formatter)
return result
def display_source(self, method):
print(self.get_source_method(method))
def get_source_class(self, _class):
return self.classes[_class.get_name()]
def get_all(self, class_name):
if class_name not in self.classes:
return ""
lexer = get_lexer_by_name("java", stripall=True)
formatter = TerminalFormatter()
result = highlight(self.classes[class_name], lexer, formatter)
return result
def display_all(self, _class):
print(self.get_all(_class.get_name()))
class DecompilerDed:
def __init__(self,
vm,
bin_ded="ded.sh",
tmp_dir="/tmp/"):
"""
DED is an old, probably deprecated, decompiler
http://siis.cse.psu.edu/ded/
.. deprecated:: 3.3.5
DED is not supported by androguard anymore!
It is now replaced by DARE.
:param vm: `DalvikVMFormat` object
:param bin_ded:
:param tmp_dir:
"""
warnings.warn("DED is deprecated since 3.3.5.", DeprecationWarning)
self.classes = {}
self.classes_failed = []
pathtmp = tmp_dir
if not os.path.exists(pathtmp):
os.makedirs(pathtmp)
fd, fdname = tempfile.mkstemp(dir=pathtmp)
with os.fdopen(fd, "w+b") as fd:
fd.write(vm.get_buff())
fd.flush()
dirname = tempfile.mkdtemp(prefix=fdname + "-src")
cmd = Popen([bin_ded, "-c", "-o", "-d", dirname, fdname],
stdout=PIPE,
stderr=STDOUT)
stdout, stderr = cmd.communicate()
os.unlink(fdname)
findsrc = None
for root, dirs, files in os.walk(dirname + "/optimized-decompiled/"):
if dirs:
for f in dirs:
if f == "src":
findsrc = root
if findsrc[-1] != "/":
findsrc += "/"
findsrc += f
break
if findsrc is not None:
break
for i in vm.get_classes():
fname = findsrc + "/" + i.get_name()[1:-1] + ".java"
# print fname
if os.path.isfile(fname):
self.classes[i.get_name()] = readFile(fname, binary=False)
else:
self.classes_failed.append(i.get_name())
rrmdir(dirname)
def get_source_method(self, method):
class_name = method.get_class_name()
method_name = method.get_name()
if class_name not in self.classes:
return ""
lexer = get_lexer_by_name("java", stripall=True)
lexer.add_filter(MethodFilter(method_name=method_name))
formatter = TerminalFormatter()
result = highlight(self.classes[class_name], lexer, formatter)
return result
def display_source(self, method):
print(self.get_source_method(method))
def get_all(self, class_name):
if class_name not in self.classes:
return ""
lexer = get_lexer_by_name("java", stripall=True)
formatter = TerminalFormatter()
result = highlight(self.classes[class_name], lexer, formatter)
return result
def get_source_class(self, _class):
return self.classes[_class.get_name()]
def display_all(self, _class):
print(self.get_all(_class.get_name()))
class DecompilerDex2Fernflower:
def __init__(self,
vm,
bin_dex2jar="dex2jar.sh",
bin_fernflower="fernflower.jar",
options_fernflower={"dgs": '1',
"asc": '1'},
tmp_dir="/tmp/"):
"""
Decompiler interface for Fernflower
Fernflower is a java decompiler by IntelliJ:
https://github.com/JetBrains/intellij-community/tree/master/plugins/java-decompiler/engine
As it can not decompile Dalvik code directly, the DEX is first
decompiled as a JAR file.
.. deprecated:: 3.3.5
Fernflower is not supported anymore by androguard.
:param vm: `DalvikVMFormtat` object
:param bin_dex2jar:
:param bin_fernflower:
:param options_fernflower:
:param tmp_dir:
"""
warnings.warn("Fernflower is deprecated since 3.3.5.", DeprecationWarning)
self.classes = {}
self.classes_failed = []
pathtmp = tmp_dir
if not os.path.exists(pathtmp):
os.makedirs(pathtmp)
fd, fdname = tempfile.mkstemp(dir=pathtmp)
with os.fdopen(fd, "w+b") as fd:
fd.write(vm.get_buff())
fd.flush()
cmd = Popen([bin_dex2jar, fdname],
stdout=PIPE,
stderr=STDOUT)
stdout, stderr = cmd.communicate()
os.unlink(fdname)
pathclasses = fdname + "dex2jar/"
cmd = Popen(["unzip", fdname + "_dex2jar.jar", "-d", pathclasses],
stdout=PIPE,
stderr=STDOUT)
stdout, stderr = cmd.communicate()
os.unlink(fdname + "_dex2jar.jar")
for root, dirs, files in os.walk(pathclasses, followlinks=True):
if files:
for f in files:
real_filename = root
if real_filename[-1] != "/":
real_filename += "/"
real_filename += f
l = ["java", "-jar", bin_fernflower]
for option in options_fernflower:
l.append("-%s:%s" %
(option, options_fernflower[option]))
l.append(real_filename)
l.append(root)
cmd = Popen(l, stdout=PIPE, stderr=STDOUT)
stdout, stderr = cmd.communicate()
for i in vm.get_classes():
fname = pathclasses + "/" + i.get_name()[1:-1] + ".java"
if os.path.isfile(fname):
self.classes[i.get_name()] = readFile(fname, binary=False)
else:
self.classes_failed.append(i.get_name())
rrmdir(pathclasses)
def get_source_method(self, method):
class_name = method.get_class_name()
method_name = method.get_name()
if class_name not in self.classes:
return ""
lexer = get_lexer_by_name("java", stripall=True)
lexer.add_filter(MethodFilter(method_name=method_name))
formatter = TerminalFormatter()
result = highlight(self.classes[class_name], lexer, formatter)
return result
def display_source(self, method):
print(self.get_source_method(method))
def get_source_class(self, _class):
return self.classes[_class.get_name()]
def get_all(self, class_name):
if class_name not in self.classes:
return ""
lexer = get_lexer_by_name("java", stripall=True)
formatter = TerminalFormatter()
result = highlight(self.classes[class_name], lexer, formatter)
return result
def display_all(self, _class):
print(self.get_all(_class.get_name()))
class DecompilerDAD:
def __init__(self, vm, vmx):
"""
Decompiler wrapper for DAD: **D**AD is **A** **D**ecompiler
DAD is the androguard internal decompiler.
This Method does not use the :class:`~androguard.decompiler.dad.decompile.DvMachine` but
creates :class:`~androguard.decompiler.dad.decompile.DvClass` and
:class:`~androguard.decompiler.dad.decompile.DvMethod` on demand.
This Method does not use the :class:`~androguard.decompiler.decompile.DvMachine` but
creates :class:`~androguard.decompiler.decompile.DvClass` and
:class:`~androguard.decompiler.decompile.DvMethod` on demand.
:param androguard.core.bytecodes.dvm.DalvikVMFormat vm: `DalvikVMFormat` object
:param androguard.core.bytecodes.dvm.DEX vm: `DEX` object
:param androguard.core.analysis.analysis.Analysis vmx: `Analysis` object
"""
self.vm = vm
@ -612,193 +86,3 @@ class DecompilerDAD:
formatter = TerminalFormatter()
result = highlight(result, lexer, formatter)
print(result)
def get_all(self, class_name):
pass
class DecompilerJADX:
def __init__(self, vm, vmx, jadx="jadx", keepfiles=False):
"""
DecompilerJADX is a wrapper for the jadx decompiler:
https://github.com/skylot/jadx
Note, that jadx need to write files to your local disk.
:param vm: `DalvikVMFormat` object
:param vmx: `Analysis` object
:param jadx: path to the jadx executable
:param keepfiles: set to True, if you like to keep temporary files
"""
self.vm = vm
self.vmx = vmx
# Dictionary to store classnames: sourcecode
self.classes = {}
# Result directory:
# TODO need to remove the folder correctly!
tmpfolder = tempfile.mkdtemp()
# We need to decompile the whole dex file, as we do not have an API...
# dump the dex file into a temp file
# THIS WILL NOT WORK ON WINDOWS!!!
# See https://stackoverflow.com/q/15169101/446140
# Files can not be read, only if they specify temp file. But jadx does not do that...
#
# We need to trick jadx by setting the suffix, otherwise the file will not be loaded
with tempfile.NamedTemporaryFile(suffix=".dex") as tf:
tf.write(vm.get_buff())
cmd = [jadx, "-ds", tmpfolder, "--escape-unicode", "--no-res", tf.name]
logger.debug("Call JADX with the following cmdline: {}".format(" ".join(cmd)))
x = Popen(cmd, stdout=PIPE, stderr=PIPE)
stdout, _ = x.communicate()
# Looks like jadx does not use stderr
logger.info("Output of JADX during decompilation")
for line in stdout.decode("UTF-8").splitlines():
logger.info(line)
if x.returncode != 0:
rrmdir(tmpfolder)
raise JADXDecompilerError("Could not decompile file. Args: {}".format(" ".join(cmd)))
# Next we parse the folder structure for later lookup
# We read the content of each file here, so we can later delete the folder
# We check here two ways, first we iterate all files and see if the class exists
# in androguard
# then, we iterate all classes in androguard and check if the file exists.
andr_class_names = {x.get_name()[1:-1]: x for x in vm.get_classes()}
# First, try to find classes for the files we have
for root, dirs, files in os.walk(tmpfolder):
for f in files:
if not f.endswith(".java"):
logger.warning("found a file in jadx folder which is not a java file: {}".format(f))
continue
# as the path begins always with `self.res` (hopefully), we remove that length
# also, all files should end with .java
path = os.path.join(root, f)[len(tmpfolder) + 1:-5]
path = path.replace(os.sep, "/")
# Special care for files without package
# All files that have no package set, will get the
# package `defpackage` automatically
if path.startswith("defpackage"):
path = path[len("defpackage/"):]
if path in andr_class_names:
with open(os.path.join(root, f), "rb") as fp:
# Need to convert back to the "full" classname
self.classes["L{};".format(path)] = fp.read().decode("UTF-8")
else:
logger.warning("Found a class called {}, which is not found by androguard!".format(path))
# Next, try to find files for the classes we have
for cl in andr_class_names:
fname = self._find_class(str(cl), tmpfolder)
if fname:
if "L{};".format(cl) not in self.classes:
with open(fname, "rb") as fp:
# TODO need to snip inner class from file
self.classes["L{};".format(cl)] = fp.read().decode("UTF-8")
else:
# Class was already found...
pass
else:
logger.warning("Found a class called {} which is not decompiled by jadx".format(cl))
# check if we have good matching
if len(self.classes) == len(andr_class_names):
logger.debug("JADX looks good, we have the same number of classes: {}".format(len(self.classes)))
else:
logger.info("Looks like JADX is missing some classes or "
"we decompiled too much: decompiled: {} vs androguard: {}".format(len(self.classes),
len(andr_class_names)))
if not keepfiles:
rrmdir(tmpfolder)
def _find_class(self, clname, basefolder):
# check if defpackage
if "/" not in clname:
# this is a defpackage class probably...
# Search first for defpackage, then search for requested string
res = self._find_class("defpackage/{}".format(clname), basefolder)
if res:
return res
# We try to map inner classes here
if "$" in clname:
# sometimes the inner class get's an extra file, sometimes not...
# So we try all possibilities
for x in range(clname.count("$")):
tokens = clname.split("$", x + 1)
base = "$".join(tokens[:-1])
res = self._find_class(base, basefolder)
if res:
return res
# Check the whole supplied name
fname = os.path.join(basefolder, clname.replace("/", os.sep) + ".java")
if not os.path.isfile(fname):
return None
return fname
def get_source_method(self, m):
"""
Return the Java source of a single method
:param m: `EncodedMethod` Object
:return:
"""
class_name = m.get_class_name()
method_name = m.get_name()
if class_name not in self.classes:
return ""
lexer = get_lexer_by_name("java", stripall=True)
lexer.add_filter(MethodFilter(method_name=method_name))
formatter = TerminalFormatter()
result = highlight(self.classes[class_name], lexer, formatter)
return result
def get_source_class(self, _class):
"""
Return the Java source code of a whole class
:param _class: `ClassDefItem` object, to get the source from
:return:
"""
if not _class.get_name() in self.classes:
return ""
return self.classes[_class.get_name()]
def display_source(self, m):
"""
This method does the same as `get_source_method`
but prints the result directly to stdout
:param m: `EncodedMethod` to print
:return:
"""
print(self.get_source_method(m))
def display_all(self, _class):
"""
???
:param _class:
:return:
"""
pass
def get_all(self, class_name):
"""
???
:param class_name:
:return:
"""
pass

View File

@ -14,12 +14,11 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import logging
from collections import defaultdict
from androguard.decompiler.dad.basic_blocks import (build_node_from_block,
from androguard.decompiler.basic_blocks import (build_node_from_block,
StatementBlock, CondBlock)
from androguard.decompiler.dad.instruction import Variable
from androguard.decompiler.instruction import Variable
from loguru import logger
@ -62,7 +61,7 @@ class Graph:
"""
Adds the given node to the graph, without connecting it to anyhting else.
:param androguard.decompiler.dad.node.Node node: node to add
:param androguard.decompiler.node.Node node: node to add
"""
self.nodes.append(node)
@ -86,7 +85,7 @@ class Graph:
"""
Remove the node from the graph, removes also all connections.
:param androguard.decompiler.dad.node.Node node: the node to remove
:param androguard.decompiler.node.Node node: the node to remove
"""
preds = self.reverse_edges.get(node, [])
for pred in preds:
@ -146,7 +145,7 @@ class Graph:
def post_order(self):
"""
Yields the :class`~androguard.decompiler.dad.node.Node`s of the graph in post-order i.e we visit all the
Yields the :class`~androguard.decompiler.node.Node`s of the graph in post-order i.e we visit all the
children of a node before visiting the node itself.
"""
def _visit(n, cnt):
@ -480,7 +479,7 @@ def construct(start_block, vmap, exceptions):
"""
Constructs a CFG
:param androguard.core.analysis.analysis.DVMBasicBlock start_block: The startpoint
:param androguard.core.analysis.analysis.DEXBasicBlock start_block: The startpoint
:param vmap: variable mapping
:param exceptions: list of androguard.core.analysis.analysis.ExceptionAnalysis

View File

@ -15,7 +15,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import androguard.decompiler.dad.util as util
import androguard.decompiler.util as util
class IRForm:

View File

@ -15,10 +15,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import logging
from struct import pack, unpack
import androguard.decompiler.dad.util as util
from androguard.decompiler.dad.instruction import (
import androguard.decompiler.util as util
from androguard.decompiler.instruction import (
ArrayLengthExpression, ArrayLoadExpression, ArrayStoreInstruction,
AssignExpression, BaseClass, BinaryCompExpression, BinaryExpression,
BinaryExpression2Addr, BinaryExpressionLit, CastExpression,

View File

@ -15,8 +15,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import logging
from loguru import logger
TYPE_DESCRIPTOR = {
@ -201,11 +199,11 @@ def get_params_type(descriptor):
def create_png(cls_name, meth_name, graph, dir_name='graphs2'):
"""
Creates a PNG from a given :class:`~androguard.decompiler.dad.graph.Graph`.
Creates a PNG from a given :class:`~androguard.decompiler.graph.Graph`.
:param str cls_name: name of the class
:param str meth_name: name of the method
:param androguard.decompiler.dad.graph.Graph graph:
:param androguard.decompiler.graph.Graph graph:
:param str dir_name: output directory
"""
m_name = ''.join(x for x in meth_name if x.isalnum())

View File

@ -15,12 +15,11 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import logging
from struct import unpack
from androguard.core import mutf8
from androguard.decompiler.dad.util import get_type
from androguard.decompiler.dad.opcode_ins import Op
from androguard.decompiler.dad.instruction import (
from androguard.decompiler.util import get_type
from androguard.decompiler.opcode_ins import Op
from androguard.decompiler.instruction import (
Constant, ThisParam, BinaryExpression, BaseClass, InstanceExpression,
NewInstance, Variable, BinaryCompExpression)
@ -84,7 +83,7 @@ class Writer:
# (TYPE_STR, MY_STR) such as ('THIS', 'this')
# where the 2nd field is the actual generated source code
# We can have more fields, for example:
# ('METHOD', 'sendToServer', 'this -> sendToServer', <androguard.decompiler.dad.instruction.ThisParam>)
# ('METHOD', 'sendToServer', 'this -> sendToServer', <androguard.decompiler.instruction.ThisParam>)
def write_ext(self, t):
if not isinstance(t, tuple):
raise "Error in write_ext: %s not a tuple" % str(t)

View File

@ -7,7 +7,6 @@ from androguard.core.analysis.analysis import Analysis
import hashlib
import re
import os
import warnings
from loguru import logger
@ -37,7 +36,7 @@ def AnalyzeAPK(_file, session=None, raw=False):
:type _file: string (for filename) or bytes (for raw)
:param session: A session (default: None)
:param raw: boolean if raw bytes are supplied instead of a filename
:rtype: return the :class:`~androguard.core.bytecodes.apk.APK`, list of :class:`~androguard.core.bytecodes.dvm.DalvikVMFormat`, and :class:`~androguard.core.analysis.analysis.Analysis` objects
:rtype: return the :class:`~androguard.core.apk.APK`, list of :class:`~androguard.core.dvm.DEX`, and :class:`~androguard.core.analysis.analysis.Analysis` objects
"""
logger.debug("AnalyzeAPK")
@ -56,7 +55,7 @@ def AnalyzeAPK(_file, session=None, raw=False):
else:
logger.debug("Analysing without session")
a = apk.APK(_file, raw=raw)
# FIXME: probably it is not necessary to keep all DalvikVMFormats, as
# FIXME: probably it is not necessary to keep all DEXs, as
# they are already part of Analysis. But when using sessions, it works
# this way...
d = []
@ -81,7 +80,7 @@ def AnalyzeDex(filename, session=None, raw=False):
:param session: A session (Default None)
:param raw: If set, ``filename`` will be used as the odex's data (bytes). Defaults to ``False``
:rtype: return a tuple of (sha256hash, :class:`DalvikVMFormat`, :class:`Analysis`)
:rtype: return a tuple of (sha256hash, :class:`DEX`, :class:`Analysis`)
"""
logger.debug("AnalyzeDex")
@ -122,69 +121,6 @@ def AnalyzeODex(filename, session=None, raw=False):
return session.addDEY(filename, data)
def RunDecompiler(d, dx, decompiler_name):
"""
Run the decompiler on a specific analysis
:param d: the DalvikVMFormat object
:type d: :class:`DalvikVMFormat` object
:param dx: the analysis of the format
:type dx: :class:`VMAnalysis` object
:param decompiler: the type of decompiler to use ("dad", "dex2jad", "ded")
:type decompiler: string
"""
if decompiler_name is not None:
logger.debug("Decompiler ...")
decompiler_name = decompiler_name.lower()
# TODO put this into the configuration object and make it more dynamic
# e.g. detect new decompilers and so on...
if decompiler_name == "dex2jad":
d.set_decompiler(decompiler.DecompilerDex2Jad(
d,
androconf.CONF["BIN_DEX2JAR"],
androconf.CONF["BIN_JAD"],
androconf.CONF["TMP_DIRECTORY"]))
elif decompiler_name == "dex2fernflower":
d.set_decompiler(decompiler.DecompilerDex2Fernflower(
d,
androconf.CONF["BIN_DEX2JAR"],
androconf.CONF["BIN_FERNFLOWER"],
androconf.CONF["OPTIONS_FERNFLOWER"],
androconf.CONF["TMP_DIRECTORY"]))
elif decompiler_name == "ded":
d.set_decompiler(decompiler.DecompilerDed(
d,
androconf.CONF["BIN_DED"],
androconf.CONF["TMP_DIRECTORY"]))
elif decompiler_name == "jadx":
d.set_decompiler(decompiler.DecompilerJADX(d, dx, jadx=androconf.CONF["BIN_JADX"]))
else:
d.set_decompiler(decompiler.DecompilerDAD(d, dx))
def sign_apk(filename, keystore, storepass):
"""
Use jarsigner to sign an APK file.
.. deprecated:: 3.4.0
dont use this function! Use apksigner directly
:param filename: APK file on disk to sign (path)
:param keystore: path to keystore
:param storepass: your keystorage passphrase
"""
warnings.warn("deprecated, this method will be removed!", DeprecationWarning)
from subprocess import Popen, PIPE, STDOUT
# TODO use apksigner instead of jarsigner
cmd = Popen([androconf.CONF["BIN_JARSIGNER"], "-sigalg", "MD5withRSA",
"-digestalg", "SHA1", "-storepass", storepass, "-keystore",
keystore, filename, "alias_name"],
stdout=PIPE,
stderr=STDOUT)
stdout, stderr = cmd.communicate()
def clean_file_name(filename, unique=True, replace="_", force_nt=False):
"""
Return a filename version, which has no characters in it which are forbidden.

View File

@ -6,10 +6,31 @@ import json
import threading
import glob
import os
import queue
md5 = lambda bs: hashlib.md5(bs).hexdigest()
from loguru import logger
class Message:
pass
class MessageEvent(Message):
def __init__(self, index, function_callee, function_call, params, ret_value):
self.index = index
self.from_method = function_call
self.to_method = function_callee
self.params = params
self.ret_value = ret_value
class MessageSystem(Message):
def __init__(self, index, function_callee, function_call, params, information):
self.index = index
self.from_method = function_call
self.to_method = function_callee
self.params = params
self.ret_value = information
class Pentest:
def __init__(self):
self.ag_session = None
@ -22,6 +43,11 @@ class Pentest:
self.pending = []
self.list_file_scripts = []
self.ag_scripts = ['androguard/pentest/internal/utils.js']
self.idx = 0
self.message_queue = queue.Queue()
def is_detached(self):
return self.detached
def disconnect(self):
logger.info("Disconnected from frida server")
@ -115,14 +141,9 @@ class Pentest:
except Exception as e:
logger.error(e)
def run_frida(self, loop=False):
def run_frida(self):
logger.info("Running frida ! Resuming the PID {}".format(self.pid))
self.device.resume(self.pid)
if loop:
logger.warning("Type 'e' to exit the strace ")
s = ""
while (s!='e') and (not self.detached):
s = input("Type 'e' to exit:")
def androguard_message_handler(self, message, payload):
# use for system event
@ -144,8 +165,11 @@ class Pentest:
ret_value = json.dumps(msg_payload.get("ret"))
logger.info("{} - [{}:{}] [{}] -> [{}]".format(msg_payload["timestamp"], function_call, function_callee, params, ret_value))
self.message_queue.put(MessageEvent(self.idx, function_call, function_callee, params, ret_value))
self.ag_session.insert_event(call=function_call, callee=function_callee, params=params, ret=ret_value)
previous_stacktrace = msg_payload["stacktrace"]
self.idx += 1
elif msg_payload["id"] == "AG-SYSTEM":
for i in msg_payload:
if i not in ["id", "information", "timestamp", "stacktrace"]:
@ -153,15 +177,23 @@ class Pentest:
function_call = None
function_callee = None
information = msg_payload["information"]
if previous_stacktrace:
function_call = previous_stacktrace[0]
function_callee = previous_stacktrace[1]
else:
function_callee = information
information = msg_payload["information"]
self.ag_session.insert_system_event(call=function_call, callee=function_callee, params=params, information=information)
logger.warning("{} - [{}:{}] [{}] -> [{}]".format(msg_payload["timestamp"], function_call, function_callee, information, params))
self.message_queue.put(MessageSystem(self.idx, function_call, function_callee, params, information))
self.ag_session.insert_system_event(call=function_call, callee=function_callee, params=params, information=information)
if not previous_stacktrace:
self.idx += 1
elif msg_payload["id"] == "AG-BINDER":
logger.info("BINDER {} {}".format(message, payload))
self.idx += 1
def dump(self, package_name):
api = self.scripts[0].exports
@ -196,22 +228,22 @@ class Pentest:
self.detached = True
def on_spawned(self, spawn):
logger.info('on_spawned: {}'.format(spawn))
#logger.info('on_spawned: {}'.format(spawn))
self.pending.append(spawn)
self.event.set()
def spawn_added(self, spawn):
logger.info('spawn_added: {}'.format(spawn))
#logger.info('spawn_added: {}'.format(spawn))
self.event.set()
if(spawn.identifier.startswith(self.package_name)):
logger.info('added tace: {}'.format(spawn))
#logger.info('added tace: {}'.format(spawn))
session = self.device.attach(spawn.pid)
self.load_scripts(session, self.list_file_scripts)
self.device.resume(spawn.pid)
logger.info('Resumed')
#logger.info('Resumed')
def spawn_removed(self, spawn):
logger.info('spawn_removed: {}'.format(spawn))
@ -220,29 +252,7 @@ class Pentest:
def child_added(self, spawn):
logger.info('child_added: {}'.format(spawn))
def start_strace(self, filename, session, loop=False):
self.ag_session = session
logger.info("Start to trace syscalls for {}".format(filename))
apk_obj, dex_objs, dx_obj = session.get_objects_apk(filename)
if not apk_obj:
logger.error("Can't find any APK object associated")
return
if not self.device:
logger.error("Not connected to any device yet")
return
self.install_apk(filename)
self.attach_package(apk_obj.get_package(),
[
'androguard/pentest/internal/strace.js',
])
self.run_frida(loop)
def start_trace(self, filename, session, list_modules, loop=False, live=False, noapk=False, dump=False):
def start_trace(self, filename, session, list_modules, live=False, noapk=False, dump=False):
self.ag_session = session
logger.info("Start to trace {} {}".format(filename, list_modules))
@ -281,6 +291,6 @@ class Pentest:
self.attach_package(package_name, list_scripts_to_load, pid)
if not dump:
self.run_frida(loop)
self.run_frida()
else:
self.dump(package_name)

View File

@ -4,5 +4,4 @@ from loguru import logger
def adb(device_id, cmd=None):
logger.info("ADB: Running on {}:{}".format(device_id, cmd))
subprocess.run('adb -s {} {}'.format(device_id, cmd), shell=True)

File diff suppressed because one or more lines are too long

View File

@ -34,13 +34,14 @@ function flatten(obj) {
const Packet = {
id: 'AG-EVENT',
payload: '',
toString() {
return JSON.stringify(flatten(this));
},
send() {
send(this.toString(), '');
send(this.toString(), this.payload);
},
};
@ -69,6 +70,19 @@ function agSysPacket(source) {
return obj;
}
// Create a new Androguard Binder Packet
function agBinderPacket(source, payload) {
const obj = Object.create(Packet);
obj.id = "AG-BINDER";
obj.timestamp = Date.now();
obj.payload = payload;
// Assign dynamic data from hooks to the packet
Object.assign(obj, source);
return obj;
}
function dumpIntent(intent) {
var cmp = ""
if (intent.getComponent()) {

View File

@ -0,0 +1,254 @@
// Ripped from https://github.com/foundryzero/binder-trace and modified to fit Androguard packets
colorLog('[+] LOADING BINDER/INTERCEPT_BINDER.JS',{c: Color.Red});
const BINDER_WRITER_READ = 0xc0306201;
const BC_TRANSACTION = 0x40406300;
const BC_REPLY = 0x40406301;
var parameters = {};
// Note: this filtering mechanism here doesn't actually get used - I moved to filtering displayed parcels rather than
// filtering which parcels are captured.
var exclude_re = { test(i) { return false } };
var exclude_list = Array();
// Special case: if there are no exclusions and any inclusions, this is set to true
// to exclude everything by default instead of including everything.
var just_include = false;
var include_re = { test(i) { return false } };
var include_list = Array();
var verbosity = 0;
var silent = true;
var android_version = 11; // Default to android 11, but a different version can be provided through the options object.
rpc.exports = {
init: function (stage, param) {
parameters = param;
if (!('exclude' in parameters || 'exclude_re' in parameters) && ('include' in parameters || 'include_re' in parameters)) {
just_include = true;
}
if ('exclude' in parameters) {
exclude_list = parameters.exclude
}
if ('exclude_re' in parameters) {
try {
exclude_re = new RegExp(parameters.exclude_re)
} catch (e) {
console.error("Invalid exclusion regex")
}
}
if ('include' in parameters) {
just_include = true
include_list = parameters.include
}
if ('include_re' in parameters) {
try {
include_re = new RegExp(parameters.include_re)
} catch (e) {
console.error("Invalid inclusion regex")
}
}
if ('verbosity' in parameters) {
switch (parameters.verbosity) {
case 0:
verbosity = 0;
break;
case 1:
verbosity = 1;
break;
case 2:
verbosity = 2;
break;
default:
console.error("Invalid verbosity value")
}
}
if ('connected' in parameters) {
silent = true;
}
if ('version' in parameters) {
android_version = parameters.version
}
}
}
function check_printable_descriptor(descriptor) {
var included = !just_include; // Usually set to true, false if there are only inclusion conditions.
//console.log(include_list)
if (exclude_list.includes(descriptor) || exclude_re.test(descriptor)) {
included = false;
}
if (include_list.includes(descriptor) || include_re.test(descriptor)) {
included = true;
}
return included
}
function binder_write_read(ptr) {
return {
ptr: ptr,
get write_size() { return this.ptr.readU64(); },
get write_consumed() { return this.ptr.add(8).readU64(); },
get write_buffer() { return this.ptr.add(16).readPointer(); },
get read_size() { return this.ptr.add(24).readU64(); },
get read_consumed() { return this.ptr.add(32).readU64(); },
get read_buffer() { return this.ptr.add(40).readPointer(); }
}
}
function interface_token(ptr) {
return {
ptr: ptr,
get strict_mode_policy() { return this.ptr.readU32(); },
get work_source_uid() { return this.ptr.add(4).readU32(); },
get version_header() { return this.ptr.add(8).readU32(); },
get descriptor_length() {
if (android_version >= 10) {
return this.ptr.add(12).readU32();
} else {
return this.ptr.add(4).readU32();
}
},
get descriptor() {
if (android_version >= 10) {
return this.ptr.add(16).readUtf16String(this.descriptor_length);
} else {
return this.ptr.add(8).readUtf16String(this.descriptor_length);
}
},
}
}
function parcel(ptr) {
if (Process.arch == "ia32" || Process.arch == "arm") {
// Return 32-bit parcel
return {
ptr: ptr,
get error() { return this.ptr.readU32() },
get data() { return this.ptr.add(4).readPointer() },
get data_size() { return this.ptr.add(8).readU32() },
get data_capacity() { return this.ptr.add(12).readU32() },
get data_pos() { return this.ptr.add(16).readU32() },
get objects() { return this.ptr.add(20).readPointer() },
get objects_size() { return this.ptr.add(24).readU32() },
get objects_capacity() { return this.ptr.add(28).readU32() },
get next_object_hint() { return this.ptr.add(32).readU32() },
get objects_sorted() { return this.ptr.add(36).readU8() } // TODO finish parsing this
}
} else {
// Return 64-bit parcel
return {
ptr: ptr,
get error() { return this.ptr.readU64() },
get data() { return this.ptr.add(8).readPointer() },
get data_size() { return this.ptr.add(16).readU64() },
get data_capacity() { return this.ptr.add(24).readU64() },
get data_pos() { return this.ptr.add(32).readU64() },
get objects() { return this.ptr.add(40).readPointer() },
get objects_size() { return this.ptr.add(48).readU64() },
get objects_capacity() { return this.ptr.add(56).readU64() },
get next_object_hint() { return this.ptr.add(64).readU64() },
get objects_sorted() { return this.ptr.add(72).readU8() } // TODO finish parsing this
}
}
}
function print_transaction(p, code) {
var token = interface_token(p.data)
if(silent) {
agBinderPacket({information: "TRANSACT", "code": code.toInt32()}, p.data.readByteArray(p.data_size)).send();
//send({"type" : "TRANSACT", "code": code.toInt32()}, p.data.readByteArray(p.data_size))
} else {
if (verbosity == 0) {
console.log(token.descriptor + ":" + code)
} else if (verbosity == 1) {
console.log(JSON.stringify({ "name": token.descriptor, "code": code }));
console.log(hexdump(p.data, {
length: p.data_size,
header: true,
ansi: true
}));
} else if (verbosity == 2) {
console.log('\n\n\n')
console.log(JSON.stringify({ "name": token.descriptor, "code": code }));
console.log(hexdump(p.data, {
length: p.data_size,
header: true,
ansi: true
}));
}
}
}
var temp = ptr(0)
// IPCThreadState::transact()
Interceptor.attach(Module.getExportByName("libbinder.so", "_ZN7android14IPCThreadState8transactEijRKNS_6ParcelEPS1_j"), {
reply: ptr("0x0"),
descriptor: "",
code: 0,
onEnter: function (args) {
if (!silent) {
console.log("\n\n\nTRANSACTION " + args[0] + ", " + args[1] + ", " + args[2] + ", " + args[3] + ", " + args[4] + ", " + args[5]);
}
var data = parcel(args[3])
if (data.data.isNull()) {
//console.log(">> weird packet")
this.reply = ptr("0x0");
return;
}
if (check_printable_descriptor(interface_token(data.data).descriptor)) {
print_transaction(data, args[2]);
if (args[5].toInt32() & 0x1 || args[4].isNull()) {
//console.log(">> one way packet")
this.reply = ptr("0x0");
} else {
this.reply = parcel(args[4])
this.code = args[2].toInt32()
this.descriptor = interface_token(data.data).descriptor
}
}
},
onLeave: function (retval) {
if ("data" in this.reply) { // If this.reply is a Parcel
if (!silent) {
console.log("REPLY " + this.reply.ptr)
console.log(hexdump(this.reply.data, { ansi: true, length: this.reply.data_size }));
} else {
agBinderPacket({information: "REPLY", "code": this.code, "descriptor": this.descriptor}, this.reply.data.readByteArray(this.reply.data_size)).send();
}
this.reply = ptr(0);
}
}
})

View File

@ -2,7 +2,7 @@
colorLog('[+] LOADING FRAMEWORK/FLUTTER/VERIFY_CERT_CHAIN_BYPASS_V7A.JS',{c: Color.Red});
// Based on nviso's article https://blog.nviso.eu/2020/05/20/intercepting-flutter-traffic-on-android-x64/
// Based on nviso's article https://blogger.nviso.eu/2020/05/20/intercepting-flutter-traffic-on-android-x64/
var m = Process.findModuleByName("libflutter.so");

View File

@ -2,7 +2,7 @@
colorLog('[+] LOADING FRAMEWORK/FLUTTER/VERIFY_CERT_CHAIN_BYPASS_V8A.JS',{c: Color.Red});
// Based on nviso's article https://blog.nviso.eu/2020/05/20/intercepting-flutter-traffic-on-android-x64/
// Based on nviso's article https://blogger.nviso.eu/2020/05/20/intercepting-flutter-traffic-on-android-x64/
function hook(){
var m = Process.findModuleByName("libflutter.so");

View File

@ -2,7 +2,7 @@
colorLog('[+] LOADING FRAMEWORK/FLUTTER/VERIFY_CERT_CHAIN_BYPASS_X86_64.JS',{c: Color.Red});
// Based on nviso's article https://blog.nviso.eu/2020/05/20/intercepting-flutter-traffic-on-android-x64/
// Based on nviso's article https://blogger.nviso.eu/2020/05/20/intercepting-flutter-traffic-on-android-x64/
function hook(){
var m = Process.findModuleByName("libflutter.so");

View File

@ -44,6 +44,7 @@ NativeFile.$init.overload("java.net.URI").implementation = function(neturi){
Interceptor.attach(Module.findExportByName("libc.so", "fopen"), {
onEnter: function(args) {
var path = Memory.readCString(args[0]);
if (path) {
var spath = path;
path = path.split("/");
var executable = path[path.length - 1];
@ -55,6 +56,7 @@ Interceptor.attach(Module.findExportByName("libc.so", "fopen"), {
Memory.writeUtf8String(args[0], "/notexists");
agSysPacket({information: "bypass", cmd: executable}).send();
}
}
},
onLeave: function(retval) {
}

View File

@ -15,7 +15,6 @@ var overloadCount1 = hook['getInt'].overloads.length;
for (var i = 0; i < overloadCount1; i++) {
hook['getInt'].overloads[i].implementation = function() {
colorLog('*** entered ' +'android.provider.Settings$Secure.getInt',{ c: Color.Green });
var retval = this['getInt'].apply(this, arguments);
var param = arguments[1];
if(param === "development_settings_enabled" || param == "adb_enabled") {

View File

@ -1,5 +1,6 @@
// Ripped from https://github.com/Ch0pin/medusa/ and modified to fit Androguard packets
colorLog('[+] LOADING HTTP_COMMUNICATIONS/URI.JS', {c: Color.Red});
var url_g = ''
var uriParseClz = Java.use('java.net.URI');

View File

@ -1,6 +1,6 @@
// Ripped from https://github.com/Ch0pin/medusa/ and modified to fit Androguard packets
colorLog('[+] LOADING IPC/INTENTS.JS',{c: Color.Red});
colorLog('[+] LOADING INTENTS/INTENTS.JS',{c: Color.Red});
var uri = Java.use('android.net.Uri');
var intent1 = Java.use('android.content.Intent');
@ -82,6 +82,13 @@ intent1.getParcelableExtra.implementation = function(name){
return this.getParcelableExtra(name);
}
if(Java.androidVersion > 12)
intent1.getParcelableExtra.overload('java.lang.String', 'java.lang.Class').implementation = function(name,clazz){
let ret = this.getParcelableExtra(name,clazz);
agPacket({intent: dumpIntent(this), name: name, ret: ret}).send();
return ret;
}
intent1.getBooleanExtra.implementation = function(name, value){
var ret = this.getBooleanExtra(name,value);
agPacket({intent: dumpIntent(this), ret: ret, name: name, value: value}).send();

View File

@ -0,0 +1,135 @@
// Ripped from https://github.com/Ch0pin/medusa/ and modified to fit Androguard packets
colorLog('[+] LOADING INTENTS/INTENTS_CREATION.JS',{c: Color.Red});
var intent = Java.use('android.content.Intent');
if (intent.$init) {
intent.setClassName.overload('java.lang.String', 'java.lang.String').implementation = function( packageName, className){
agPacket({intent: dumpIntent(this), packageName: packageName, className: className}).send();
return this.setClassName(packageName,className);
}
intent.putExtra.overload('java.lang.String', '[I').implementation = function(name, intB){
agPacket({intent: dumpIntent(this), name: name, intB: intB}).send();
return this.putExtra(name, intB);
}
intent.putExtra.overload('java.lang.String', '[D').implementation = function(name, doubleD){
agPacket({intent: dumpIntent(this), name: name, doubleD: doubleD}).send();
return this.putExtra(name,doubleD);
}
intent.putExtra.overload('java.lang.String', '[F').implementation = function(name, floatF){
agPacket({intent: dumpIntent(this), name: name, floatF: floatF}).send();
return this.putExtra(name,floatF);
}
intent.putExtra.overload('java.lang.String', '[B').implementation = function(name, byteB){
agPacket({intent: dumpIntent(this), name: name, byteB: byteB}).send();
return this.putExtra(name,byteB);
}
intent.putExtra.overload('java.lang.String', '[C').implementation = function(name, charC){
agPacket({intent: dumpIntent(this), name: name, charC: charC}).send();
return this.putExtra(name,charC);
}
intent.putExtra.overload('java.lang.String', '[Z').implementation = function(name, z){
agPacket({intent: dumpIntent(this), name: name, z: z}).send();
return this.putExtra(name,z);
}
intent.putExtra.overload('java.lang.String', 'boolean').implementation = function(name, boolvalue){
agPacket({intent: dumpIntent(this), name: name, boolvalue: boolvalue}).send();
return this.putExtra(name, boolvalue);
}
intent.putExtra.overload('java.lang.String', '[S').implementation = function(name, stringS){
agPacket({intent: dumpIntent(this), name: name, stringS: stringS}).send();
return this.putExtra(name,stringS);
}
intent.putExtra.overload('java.lang.String', '[Landroid.os.Parcelable;').implementation = function(name, parcel){
agPacket({intent: dumpIntent(this), name: name, parcel: parcel}).send();
return this.putExtra(name,parcel);
}
intent.putExtra.overload('java.lang.String', 'byte').implementation = function(name, bt){
agPacket({intent: dumpIntent(this), name: name, bt: bt}).send();
return this.putExtra(name,bt);
}
intent.putExtra.overload('java.lang.String', '[Ljava.lang.CharSequence;').implementation = function(name, chars){
agPacket({intent: dumpIntent(this), name: name, chars: chars}).send();
return this.putExtra(name,chars);
}
intent.putExtra.overload('java.lang.String', '[Ljava.lang.String;').implementation = function(name, data){
agPacket({intent: dumpIntent(this), name: name, data: data}).send();
return this.putExtra(name,data);
}
intent.putExtra.overload('java.lang.String', 'android.os.Bundle').implementation = function(name, bundle){
agPacket({intent: dumpIntent(this), name: name, bundle: bundle}).send();
return this.putExtra(name,bundle);
}
intent.putExtra.overload('java.lang.String', 'int').implementation = function(name, intA){
agPacket({intent: dumpIntent(this), name: name, intA: intA}).send();
return this.putExtra(name,intA);
}
intent.putExtra.overload('java.lang.String', 'long').implementation = function(name, longA){
agPacket({intent: dumpIntent(this), name: name, longA: longA}).send();
return this.putExtra(name,longA);
}
intent.putExtra.overload('java.lang.String', 'float').implementation = function(name, floatA){
agPacket({intent: dumpIntent(this), name: name, floatA: floatA}).send();
return this.putExtra(name,floatA);
}
intent.putExtra.overload('java.lang.String', 'short').implementation = function(name, shortA){
agPacket({intent: dumpIntent(this), name: name, shortA: shortA}).send();
return this.putExtra(name,shortA);
}
intent.putExtra.overload('java.lang.String', 'char').implementation = function(name, charA){
agPacket({intent: dumpIntent(this), name: name, charA: charA}).send();
return this.putExtra(name,charA);
}
intent.putExtra.overload('java.lang.String', 'double').implementation = function(name, doubleA){
agPacket({intent: dumpIntent(this), name: name, doubleA: doubleA}).send();
return this.putExtra(name,doubleA);
}
intent.putExtra.overload('java.lang.String', 'java.lang.String').implementation = function(name, stringA){
agPacket({intent: dumpIntent(this), name: name, stringA: stringA}).send();
return this.putExtra(name,stringA);
}
intent.putExtra.overload('java.lang.String', 'java.lang.CharSequence').implementation = function(name, CharSequence)
{
charsJoin = CharSequence.join("");
agPacket({intent: dumpIntent(this), name: name, charsJoin: charsJoin}).send();
return this.putExtra(name, CharSequence);
}
intent.putExtra.overload('java.lang.String', 'java.io.Serializable').implementation = function(name, serializable){
agPacket({intent: dumpIntent(this), name: name, serializable: serializable}).send();
return this.putExtra(name,serializable);
}
intent.putExtra.overload('java.lang.String', 'android.os.Parcelable').implementation = function(name, parcelable) {
agPacket({intent: dumpIntent(this), name: name, parcelable: parcelable}).send();
return this.putExtra(name,parcelable);
}
intent.putExtra.overload('java.lang.String', 'android.os.IBinder').implementation = function(name, binder){
agPacket({intent: dumpIntent(this), name: name, binder: binder}).send();
return this.putExtra(name,binder);
}
}

View File

@ -0,0 +1,37 @@
// Ripped from https://github.com/Ch0pin/medusa/ and modified to fit Androguard packets
colorLog('[+] LOADING INTENTS/PENDING_INTENTS.JS',{c: Color.Red});
var pendingIntent = Java.use('android.app.PendingIntent');
pendingIntent.getActivity.overloads[0].implementation = function(context, requestCode, intent, flags){
agPacket({intent: dumpIntent(intent), requestCode: requestCode, flags: flags}).send();
return this.getActivity(context, requestCode, intent, flags);
}
pendingIntent.getActivity.overloads[1].implementation = function(context, requestCode, intent, flags, bundle){
agPacket({intent: dumpIntent(intent), requestCode: requestCode, flags: flags, bundle: bundle}).send();
return this.getActivity(context, requestCode, intent, flags, bundle);
}
pendingIntent.getBroadcast.implementation = function(context, requestCode, intent, flags){
agPacket({intent: dumpIntent(intent), requestCode: requestCode, flags: flags}).send();
return this.getBroadcast(context, requestCode, intent, flags);
}
pendingIntent.getService.implementation = function(context, requestCode, intent, flags){
agPacket({intent: dumpIntent(intent), requestCode: requestCode, flags: flags}).send();
return this.getService(context, requestCode, intent, flags);
}
pendingIntent.getActivities.overloads[0].implementation = function(context, requestCode, intent, flags) {
for (let value of intent)
agPacket({intent: dumpIntent(value), requestCode: requestCode, flags: flags}).send();
return this.getService(context, requestCode, intent, flags);
}
pendingIntent.getActivities.overloads[1].implementation = function(context, requestCode, intent, flags,bundle){
for (let value of intent)
agPacket({intent: dumpIntent(value), requestCode: requestCode, flags: flags}).send();
return this.getService(context, requestCode, intent, flags,bundle);
}

View File

@ -1,133 +0,0 @@
// Ripped from https://github.com/Ch0pin/medusa/ and modified to fit Androguard packets
colorLog('[+] LOADING IPC/INTENTS_CREATION.JS',{c: Color.Red});
var intent = Java.use('android.content.Intent');
intent.setClassName.overload('java.lang.String', 'java.lang.String').implementation = function( packageName, className){
agPacket({intent: dumpIntent(this), packageName: packageName, className: className}).send();
return this.setClassName(packageName,className);
}
intent.putExtra.overload('java.lang.String', '[I').implementation = function(name, intB){
agPacket({intent: dumpIntent(this), name: name, intB: intB}).send();
return this.putExtra(name, intB);
}
intent.putExtra.overload('java.lang.String', '[D').implementation = function(name, doubleD){
agPacket({intent: dumpIntent(this), name: name, doubleD: doubleD}).send();
return this.putExtra(name,doubleD);
}
intent.putExtra.overload('java.lang.String', '[F').implementation = function(name, floatF){
agPacket({intent: dumpIntent(this), name: name, floatF: floatF}).send();
return this.putExtra(name,floatF);
}
intent.putExtra.overload('java.lang.String', '[B').implementation = function(name, byteB){
agPacket({intent: dumpIntent(this), name: name, byteB: byteB}).send();
return this.putExtra(name,byteB);
}
intent.putExtra.overload('java.lang.String', '[C').implementation = function(name, charC){
agPacket({intent: dumpIntent(this), name: name, charC: charC}).send();
return this.putExtra(name,charC);
}
intent.putExtra.overload('java.lang.String', '[Z').implementation = function(name, z){
agPacket({intent: dumpIntent(this), name: name, z: z}).send();
return this.putExtra(name,z);
}
intent.putExtra.overload('java.lang.String', 'boolean').implementation = function(name, boolvalue){
agPacket({intent: dumpIntent(this), name: name, boolvalue: boolvalue}).send();
return this.putExtra(name, boolvalue);
}
intent.putExtra.overload('java.lang.String', '[S').implementation = function(name, stringS){
agPacket({intent: dumpIntent(this), name: name, stringS: stringS}).send();
return this.putExtra(name,stringS);
}
intent.putExtra.overload('java.lang.String', '[Landroid.os.Parcelable;').implementation = function(name, parcel){
agPacket({intent: dumpIntent(this), name: name, parcel: parcel}).send();
return this.putExtra(name,parcel);
}
intent.putExtra.overload('java.lang.String', 'byte').implementation = function(name, bt){
agPacket({intent: dumpIntent(this), name: name, bt: bt}).send();
return this.putExtra(name,bt);
}
intent.putExtra.overload('java.lang.String', '[Ljava.lang.CharSequence;').implementation = function(name, chars){
agPacket({intent: dumpIntent(this), name: name, chars: chars}).send();
return this.putExtra(name,chars);
}
intent.putExtra.overload('java.lang.String', '[Ljava.lang.String;').implementation = function(name, data){
agPacket({intent: dumpIntent(this), name: name, data: data}).send();
return this.putExtra(name,data);
}
intent.putExtra.overload('java.lang.String', 'android.os.Bundle').implementation = function(name, bundle){
agPacket({intent: dumpIntent(this), name: name, bundle: bundle}).send();
return this.putExtra(name,bundle);
}
intent.putExtra.overload('java.lang.String', 'int').implementation = function(name, intA){
agPacket({intent: dumpIntent(this), name: name, intA: intA}).send();
return this.putExtra(name,intA);
}
intent.putExtra.overload('java.lang.String', 'long').implementation = function(name, longA){
agPacket({intent: dumpIntent(this), name: name, longA: longA}).send();
return this.putExtra(name,longA);
}
intent.putExtra.overload('java.lang.String', 'float').implementation = function(name, floatA){
agPacket({intent: dumpIntent(this), name: name, floatA: floatA}).send();
return this.putExtra(name,floatA);
}
intent.putExtra.overload('java.lang.String', 'short').implementation = function(name, shortA){
agPacket({intent: dumpIntent(this), name: name, shortA: shortA}).send();
return this.putExtra(name,shortA);
}
intent.putExtra.overload('java.lang.String', 'char').implementation = function(name, charA){
agPacket({intent: dumpIntent(this), name: name, charA: charA}).send();
return this.putExtra(name,charA);
}
intent.putExtra.overload('java.lang.String', 'double').implementation = function(name, doubleA){
agPacket({intent: dumpIntent(this), name: name, doubleA: doubleA}).send();
return this.putExtra(name,doubleA);
}
intent.putExtra.overload('java.lang.String', 'java.lang.String').implementation = function(name, stringA){
agPacket({intent: dumpIntent(this), name: name, stringA: stringA}).send();
return this.putExtra(name,stringA);
}
intent.putExtra.overload('java.lang.String', 'java.lang.CharSequence').implementation = function(name, CharSequence)
{
charsJoin = CharSequence.join("");
agPacket({intent: dumpIntent(this), name: name, charsJoin: charsJoin}).send();
return this.putExtra(name, CharSequence);
}
intent.putExtra.overload('java.lang.String', 'java.io.Serializable').implementation = function(name, serializable){
agPacket({intent: dumpIntent(this), name: name, serializable: serializable}).send();
return this.putExtra(name,serializable);
}
intent.putExtra.overload('java.lang.String', 'android.os.Parcelable').implementation = function(name, parcelable) {
agPacket({intent: dumpIntent(this), name: name, parcelable: parcelable}).send();
return this.putExtra(name,parcelable);
}
intent.putExtra.overload('java.lang.String', 'android.os.IBinder').implementation = function(name, binder){
agPacket({intent: dumpIntent(this), name: name, binder: binder}).send();
return this.putExtra(name,binder);
}

View File

@ -3,14 +3,10 @@ from androguard.core import dex, apk
from androguard.decompiler.decompiler import DecompilerDAD
from androguard.core import androconf
import hashlib
import os
import sys
import collections
import dataset
import datetime
from loguru import logger
class Session:
@ -85,7 +81,7 @@ class Session:
# classes
self.analyzed_vms = collections.OrderedDict()
# Dict of digest and DalvikVMFormat/DalvikOdexFormat
# Dict of digest and DEX/DalvikOdexFormat
# Actually not needed, as we have Analysis objects which store the DEX
# files as well, but we do not remove it here for legacy reasons
self.analyzed_dex = dict()
@ -168,7 +164,7 @@ class Session:
:param data: binary data of the dex file
:param dx: an existing Analysis Object (optional)
:param postpone_xref: True if no xref shall be created, and will be called manually
:return: A tuple of SHA256 Hash, DalvikVMFormat Object and Analysis object
:return: A tuple of SHA256 Hash, DEX Object and Analysis object
"""
digest = hashlib.sha256(data).hexdigest()
logger.info("add DEX:{}".format(digest))
@ -304,7 +300,7 @@ class Session:
def get_format(self, current_class):
"""
Returns the :class:`~androguard.core.bytecodes.dvm.DalvikVMFormat` of a
Returns the :class:`~androguard.core.bytecodes.dvm.DEX` of a
given :class:`~androguard.core.bytecodes.dvm.ClassDefItem`.
:param current_class: A ClassDefItem
@ -374,7 +370,7 @@ class Session:
def get_objects_apk(self, filename=None, digest=None):
"""
Returns APK, DalvikVMFormat and Analysis of a specified APK.
Returns APK, DEX and Analysis of a specified APK.
You must specify either `filename` or `digest`.
It is possible to use both, but in this case only `digest` is used.
@ -394,7 +390,7 @@ class Session:
:param filename: the filename of the APK file, only used of digest is None
:param digest: the sha256 hash, as returned by :meth:`add` for the APK
:returns: a tuple of (APK, [DalvikVMFormat], Analysis)
:returns: a tuple of (APK, [DEX], Analysis)
"""
if not filename and not digest:
raise ValueError("Must give at least filename or digest!")
@ -414,7 +410,7 @@ class Session:
"""
Yields all dex objects inclduing their Analysis objects
:returns: tuple of (sha256, DalvikVMFormat, Analysis)
:returns: tuple of (sha256, DEX, Analysis)
"""
# TODO: there is no variant like get_objects_apk
for digest, d in self.analyzed_dex.items():

239
androguard/ui/__init__.py Normal file
View File

@ -0,0 +1,239 @@
import os
import queue
from prompt_toolkit import Application
from prompt_toolkit.application import get_app
from prompt_toolkit.filters import Condition
from prompt_toolkit.formatted_text import StyleAndTextTuples
from prompt_toolkit.layout import HSplit, Layout, VSplit, Window, FloatContainer, Float, ConditionalContainer, UIControl, UIContent
from prompt_toolkit.key_binding import KeyBindings, merge_key_bindings
from prompt_toolkit.styles import Style
from loguru import logger
from androguard.ui.selection import SelectionViewList
from androguard.ui.widget.transactions import TransactionFrame
from androguard.ui.widget.toolbar import StatusToolbar
from androguard.ui.widget.filters import FiltersPanel
from androguard.ui.widget.help import HelpPanel
from androguard.ui.widget.details import DetailsFrame
from androguard.ui.data_types import DisplayTransaction
from androguard.ui.filter import Filter
from androguard.pentest import Message
class DummyControl(UIControl):
"""
A dummy control object that doesn't paint any content.
Useful for filling a :class:`~prompt_toolkit.layout.Window`. (The
`fragment` and `char` attributes of the `Window` class can be used to
define the filling.)
"""
def create_content(self, width: int, height: int) -> UIContent:
def get_line(i: int) -> StyleAndTextTuples:
return []
return UIContent(
get_line=get_line, line_count=100**100
) # Something very big.
def is_focusable(self) -> bool:
return True
class DynamicUI:
def __init__(self, input_queue):
logger.info("Starting the Terminal UI")
self.filter: Filter | None = None
self.input_queue = input_queue
self.all_transactions = []
self.transactions = SelectionViewList([], max_view_size=1)
self.transaction_table = TransactionFrame(self.transactions)
self.details_pane = DetailsFrame(self.transactions, 1)
self.filter_panel = FiltersPanel()
self.help_panel = HelpPanel()
self.resize_components(os.get_terminal_size())
def run(self):
self.focusable = [self.transaction_table, self.details_pane]
self.focus_index = 0
self.focusable[self.focus_index].activated = True
kb1 = KeyBindings()
@kb1.add('tab')
def _(event):
self.focus_index = (self.focus_index + 1) % len(self.focusable)
for i, f in enumerate(self.focusable):
f.activated = i == self.focus_index
@kb1.add('s-tab')
def _(event):
self.focus_index = len(self.focusable) - 1 if self.focus_index == 0 else self.focus_index -1
for i, f in enumerate(self.focusable):
f.activated = i == self.focus_index
dummy_control = DummyControl()
main_layout = HSplit(
key_bindings=kb1,
children=[
self.transaction_table,
VSplit([
self.details_pane,
# self.structure_pane,
]),
StatusToolbar(self.transactions, self.filter_panel),
Window(content=dummy_control)
],
)
@Condition
def modal_panel_visible():
return show_help() or show_filters()
@Condition
def show_filters():
return self.filter_panel.visible
@Condition
def show_help():
return self.help_panel.visible
layout = Layout(
container=FloatContainer(
content=main_layout,
floats=[
Float(top=10, content=ConditionalContainer(content=self.filter_panel, filter=show_filters)),
Float(top=10, content=ConditionalContainer(content=self.help_panel, filter=show_help)),
]
)
)
style = Style([
('field.selected', 'ansiblack bg:ansiwhite'),
('field.default', 'fg:ansiwhite'),
('frame.label', 'fg:ansiwhite'),
('frame.border', 'fg:ansiwhite'),
('frame.border.selected', 'fg:ansibrightgreen'),
('transaction.heading', 'ansiblack bg:ansigray'),
('transaction.selected', 'ansiblack bg:ansiwhite'),
('transaction.default', 'fg:ansiwhite'),
('transaction.unsupported', 'fg:ansibrightblack'),
('transaction.error', 'fg:ansired'),
('transaction.no_aidl', 'fg:ansiwhite'),
('transaction.oneway', 'fg:ansimagenta'),
('transaction.request', 'fg:ansicyan'),
('transaction.response', 'fg:ansiyellow'),
('hexdump.default', 'fg:ansiwhite'),
('hexdump.selected', 'fg:ansiblack bg:ansiwhite'),
('toolbar', 'bg:ansigreen'),
('toolbar.text', 'fg:ansiblack'),
('dialog', 'fg:ansiblack bg:ansiwhite'),
('dialog frame.border', 'fg:ansiblack bg:ansiwhite'),
('dialog frame.label', 'fg:ansiblack bg:ansiwhite'),
('dialogger.textarea', 'fg:ansiwhite bg:ansiblack'),
])
kb = KeyBindings()
@kb.add('q')
def _(event):
logger.info("Q pressed. App exiting.")
event.app.exit(exception=KeyboardInterrupt, style='class:aborting')
@kb.add('h', filter=~modal_panel_visible | show_help)
@kb.add("enter", filter=show_help)
def _(event):
self.help_panel.visible = not self.help_panel.visible
@kb.add('f', filter=~modal_panel_visible)
@kb.add("enter", filter=show_filters)
def _(event):
self.filter_panel.visible = not self.filter_panel.visible
if self.filter_panel.visible:
get_app().layout.focus(self.filter_panel.interface_textarea)
else:
self.filter = self.filter_panel.filter()
self.transactions.assign([t for t in self.all_transactions if self.filter.passes(t)])
get_app().layout.focus(dummy_control)
@kb.add("c-c")
def _(event):
active_frame = self.focusable[self.focus_index]
active_frame.copy_to_clipboard()
app = Application(
layout,
key_bindings=merge_key_bindings([
kb,
self.transaction_table.key_bindings(),
#self.structure_pane.key_bindings(),
#self.hexdump_pane.key_bindings()
]),
full_screen=True,
style=style
)
app.before_render += self.check_resize
app.run()
def check_resize(self, _):
new_dimensions = os.get_terminal_size()
if self.dimensions != new_dimensions:
self.resize_components(new_dimensions)
def resize_components(self, dimensions):
self.dimensions = dimensions
_, height = dimensions
# Allow for the borders:
# - top and bottom of transaction frame
# - top and bottom of lower frames
# - status bar
border_allowance = 5
available_height = height - border_allowance
# Split into two halfs horizontally. If there are an odd number of lines give the extra to transactions.
transactions_height = available_height - (available_height // 2)
lower_panels_height = available_height // 2
logger.debug(f"New terminal dimension: {dimensions}")
logger.debug(f"{border_allowance=}, {transactions_height=}, {lower_panels_height=}, total={border_allowance+transactions_height+lower_panels_height}")
self.transaction_table.resize(transactions_height)
#self.structure_pane.max_height = lower_panels_height
#self.hexdump_pane.max_lines = lower_panels_height
def get_available_blocks(self):
blocks: list[Message] = []
# Retrieve every unhandled block currently avilable in the queue
try:
for _ in range(10):
blocks.append(self.input_queue.get_nowait())
except queue.Empty:
pass
return blocks
def process_data(self):
blocks = self.get_available_blocks()
# For every block...
for block in blocks:
block = DisplayTransaction(block)
if not self.filter or self.filter.passes(block):
self.transactions.append(block)
self.all_transactions.append(block)
return bool(blocks)

View File

@ -0,0 +1,92 @@
import datetime
from androguard.pentest import Message, MessageEvent, MessageSystem
class DisplayTransaction:
def __init__(self, block: Message) -> None:
self.block: Message = block
self.timestamp = datetime.datetime.now().strftime('%H:%M:%S'),
@property
def index(self) -> int:
return self.block.index
@property
def unsupported_call(self) -> bool:
return '' #self.block.unsupported_call
@property
def to_method(self) -> str:
return self.block.to_method
@property
def from_method(self) -> str:
return self.block.from_method
@property
def params(self) -> str:
return self.block.params
@property
def ret_value(self) -> str:
return self.block.ret_value
@property
def fields(self): #-> Field | None:
return None #self.block.root_field
@property
def direction_indicator(self) -> str:
return '\u21D0'
if self.block.direction == Direction.IN:
return '\u21D0' if self.block.oneway else '\u21D2'
elif self.block.direction == Direction.OUT:
return '\u21CF'
else:
return ''
def style(self) -> str:
if type(self.block) is MessageEvent:
style = "class:transaction.oneway"
elif type(self.block) is MessageSystem:
style = "class:transaction.response"
else:
style = "class:transaction.default"
return style
if self.unsupported_call:
style = "class:transaction.unsupported"
elif self.block.errors:
style = "class:transaction.error"
elif self.block.unsupported_call:
style = "class:transaction.no_aidl"
elif self.block.direction == Direction.IN:
style = "class:transaction.oneway" if self.block.oneway else "class:transaction.request"
elif self.block.direction == Direction.OUT:
style = "class:transaction.response"
else:
style = "class:transaction.default"
return style
def type(self) -> str:
"""Gets the type of the Block as a simple short string for use in pattern matching"""
return "oneway"
if self.unsupported_call:
type_name = "unsupported type"
elif self.block.errors:
type_name = "error"
elif self.block.direction == Direction.IN:
# TODO: Should this be "oneway" or "async call"?
type_name = "oneway" if self.block.oneway else "call"
elif self.block.direction == Direction.OUT:
type_name = "return"
else:
type_name = "unknown" # Should be impossible
return type_name

62
androguard/ui/filter.py Normal file
View File

@ -0,0 +1,62 @@
from collections import UserList
from typing import Optional, TypeVar
class Filter:
"""
CLASS Filter
Brief - Class that represents a single filter
Description -
It holds and interface, method and list of types to check against
It also holds the function which checks if a block passes the filter
"""
def __init__(
self,
interface: Optional[str] = None,
method: Optional[str] = None,
types: Optional[list[str]] = None,
include: bool = True
):
self.interface = interface
self.method = method
self.types = types or [] # List of associated types of the filter (call, return, etc)
self.inclusive = include
def passes(self):
"""
FUNCTION passes
Brief - Returns whether a block should be displayed
Description -
Returns TRUE if the block should be shown
Returns FALSE if the block should no be shown
The code checks if the filter passes the checks, and then tailors the output to the filter_mode
The type is either Inclusive ("Incl") or Exclusive ("Excl")
"""
#matches = (
# (not self.types or block.type() in self.types) and
# (not self.interface or self.interface in block.interface) and
# (not self.method or self.method in block.method)
#)
#return not matches ^ self.inclusive
return False
def toggle_inclusivity(self):
self.inclusive = not self.inclusive
def __str__(self):
interface = self.interface or "*"
method = self.method or "*"
types = "|".join(self.types) if self.types else "*"
return f"interface={interface}, method={method}, types={types}"
_T = TypeVar('_T', bound=Filter)
class FilterSet(UserList[_T]):
def passes(self, interface=None, method=None, call_type=None):
"""Return True if all filters in the set pass, False otherwise."""
return all([f.passes(interface, method, call_type) for f in self.data])

159
androguard/ui/selection.py Normal file
View File

@ -0,0 +1,159 @@
from collections import UserList
from dataclasses import dataclass
from typing import Iterable, TypeVar
from prompt_toolkit.utils import Event
from androguard.ui.util import clamp
@dataclass
class View:
start: int
end: int
def size(self):
return self.end - self.start
_T = TypeVar('_T')
class SelectionViewList(UserList[_T]):
def __init__(self, iterable=None, max_view_size=80, view_padding=5):
super().__init__(iterable)
self.max_view_size = max_view_size
self.view_padding = view_padding
self.on_update_event = Event(self)
self.on_selection_change = Event(self)
self._reset_view()
def _reset_view(self):
# -1 means the list is empty so there is no selection.
self.selection = 0 if len(self) else -1
self.view = View(0, min(self.max_view_size, len(self)))
self.on_update_event()
self.on_selection_change()
def selection_valid(self):
return self.selection != -1
def move_selection(self, step: int):
if self.selection_valid():
if step != 0:
self.selection = clamp(0, len(self) - 1, self.selection + step)
self._update_view(step)
self.on_selection_change()
def selected(self):
if not self.selection_valid():
raise IndexError("Selection index not set.")
return self.data[self.selection]
def view_slice(self):
return self.data[self.view.start:self.view.end]
def resize_view(self, view_size):
if self.selection_valid():
before_selection = view_size // 2
self.view.start = max(0, self.selection - before_selection)
self.view.end = self.view.start
self.max_view_size = view_size
self._expand_view()
else:
self.max_view_size = view_size
self._reset_view()
def _update_view(self, step: int):
if step > 0 and self.view.end - self.selection < self.view_padding:
# We're moving down the list and are near the bottom (i.e. within padding of end of current view).
self.view.end = min(self.view.end + step, len(self))
# If we're can't fit all the data in the view set the start of the view
if self.view.end > self.max_view_size:
self.view.start = self.view.end - self.max_view_size
elif step < 0 and self.selection - self.view.start < self.view_padding:
# We're moving up the list and are near the top (i.e. within padding of start of current view)
# We're adding a negative here so although it looks a bit odd we are moving the view backwards
self.view.start = max(self.view.start + step, 0)
self.view.end = min(len(self), self.view.start + self.max_view_size)
self.on_update_event()
def __delitem__(self, i: int):
super().__delitem__(i)
self._delete_from_view(i)
def _expand_view(self):
self.view.end = self.view.start + min(self.max_view_size, len(self) - self.view.start)
if not self.selection_valid():
self.selection = 0
self.on_selection_change()
self.on_update_event()
def _delete_from_view(self, i: int):
if len(self) == 0:
self._reset_view()
else:
# if i < self.selection:
# self.selection -= 1
self.move_selection(-1)
self.on_update_event()
# TODO: once the selection is within padding of the start of the window it shoud move up
# if i <= self.view.end:
# self.view.end = min(self.view.end, len(self))
# if i < self.selection:
# self.selection -= 1
# elif i == self.selection:
# self.selection = min(self.selection, len(self))
# if i <= self.view.start:
# self.view.start = max(0, self.view.start - 1)
# self.view.end -= 1
def append(self, item: _T):
super().append(item)
self._expand_view()
def insert(self, i, item: _T):
super().insert(i, item)
if len(self) == 1:
# The list must have been empty so reset the view
self._reset_view()
else:
if i >= self.view.start:
self._expand_view()
if i <= self.selection:
self.selection += 1
self.on_selection_change()
def pop(self, i=-1):
item = super().pop(i)
self._delete_from_view(i)
return item
def remove(self, item: _T):
# We're reimplementing remove in terms of index and delete because we need the indext to update the view
i = self.index(item)
super().__delitem__(i)
self._delete_from_view(i)
def clear(self):
super().clear()
self._reset_view()
def extend(self, other: Iterable[_T]):
super().extend(other)
self._expand_view()
def assign(self, items: Iterable[_T]):
self.clear()
self.data += items
self._reset_view()

621
androguard/ui/table.py Normal file
View File

@ -0,0 +1,621 @@
#!/usr/bin/env python3
from typing import Type, Union
from prompt_toolkit import Application
from prompt_toolkit.layout.layout import Layout
from prompt_toolkit.layout.dimension import Dimension as D, sum_layout_dimensions, max_layout_dimensions, to_dimension
from prompt_toolkit.widgets import Box, TextArea, Label, Button
from prompt_toolkit.cache import SimpleCache
# from prompt_toolkit.widgets.base import Border
from prompt_toolkit.layout.containers import Window, VSplit, HSplit, HorizontalAlign, VerticalAlign
from prompt_toolkit.key_binding.key_bindings import KeyBindings
from prompt_toolkit.utils import take_using_weights
from androguard.ui.selection import SelectionViewList
class EmptyBorder:
HORIZONTAL = ''
VERTICAL = ''
TOP_LEFT = ''
TOP_RIGHT = ''
BOTTOM_LEFT = ''
BOTTOM_RIGHT = ''
LEFT_T = ''
RIGHT_T = ''
TOP_T = ''
BOTTOM_T = ''
INTERSECT = ''
class SpaceBorder:
" Box drawing characters. (Spaces) "
HORIZONTAL = ' '
VERTICAL = ' '
TOP_LEFT = ' '
TOP_RIGHT = ' '
BOTTOM_LEFT = ' '
BOTTOM_RIGHT = ' '
LEFT_T = ' '
RIGHT_T = ' '
TOP_T = ' '
BOTTOM_T = ' '
INTERSECT = ' '
class AsciiBorder:
" Box drawing characters. (ASCII) "
HORIZONTAL = '-'
VERTICAL = '|'
TOP_LEFT = '+'
TOP_RIGHT = '+'
BOTTOM_LEFT = '+'
BOTTOM_RIGHT = '+'
LEFT_T = '+'
RIGHT_T = '+'
TOP_T = '+'
BOTTOM_T = '+'
INTERSECT = '+'
class ThinBorder:
" Box drawing characters. (Thin) "
HORIZONTAL = '\u2500'
VERTICAL = '\u2502'
TOP_LEFT = '\u250c'
TOP_RIGHT = '\u2510'
BOTTOM_LEFT = '\u2514'
BOTTOM_RIGHT = '\u2518'
LEFT_T = '\u251c'
RIGHT_T = '\u2524'
TOP_T = '\u252c'
BOTTOM_T = '\u2534'
INTERSECT = '\u253c'
class RoundedBorder(ThinBorder):
" Box drawing characters. (Rounded) "
TOP_LEFT = '\u256d'
TOP_RIGHT = '\u256e'
BOTTOM_LEFT = '\u2570'
BOTTOM_RIGHT = '\u256f'
class ThickBorder:
" Box drawing characters. (Thick) "
HORIZONTAL = '\u2501'
VERTICAL = '\u2503'
TOP_LEFT = '\u250f'
TOP_RIGHT = '\u2513'
BOTTOM_LEFT = '\u2517'
BOTTOM_RIGHT = '\u251b'
LEFT_T = '\u2523'
RIGHT_T = '\u252b'
TOP_T = '\u2533'
BOTTOM_T = '\u253b'
INTERSECT = '\u254b'
class DoubleBorder:
" Box drawing characters. (Thin) "
HORIZONTAL = '\u2550'
VERTICAL = '\u2551'
TOP_LEFT = '\u2554'
TOP_RIGHT = '\u2557'
BOTTOM_LEFT = '\u255a'
BOTTOM_RIGHT = '\u255d'
LEFT_T = '\u2560'
RIGHT_T = '\u2563'
TOP_T = '\u2566'
BOTTOM_T = '\u2569'
INTERSECT = '\u256c'
AnyBorderStyle = Union[
Type[EmptyBorder],
Type[SpaceBorder],
Type[AsciiBorder],
Type[ThinBorder],
Type[RoundedBorder],
Type[ThickBorder],
Type[DoubleBorder],
]
class Merge:
def __init__(self, cell, merge=1):
self.cell = cell
self.merge = merge
def __iter__(self):
yield self.cell
yield self.merge
class Table(HSplit):
def __init__(self, table,
borders: AnyBorderStyle=ThinBorder, column_width=None, column_widths=[],
window_too_small=None, align=VerticalAlign.JUSTIFY,
padding=0, padding_char=None, padding_style='',
width=None, height=None, z_index=None,
modal=False, key_bindings=None, style='', selected_style=''):
self.borders = borders
self.column_width = column_width
self.column_widths = column_widths
self.selected_style = selected_style
# ensure the table is iterable (has rows)
if not isinstance(table, list):
table = [table]
children = [_Row(row=row, table=self, borders=borders, height=1) for row in table]
super().__init__(
children=children,
window_too_small=window_too_small,
align=align,
padding=padding,
padding_char=padding_char,
padding_style=padding_style,
width=width,
height=height,
z_index=z_index,
modal=modal,
key_bindings=key_bindings,
style=style
)
self.row_cache = SimpleCache(maxsize=30)
# def do_update(self, rows):
# self.children.clear()
# self.children.extend(_Row(row=row, table=self, borders=self.borders, height=1, style="#000000 bg:#ffffff") for row in rows)
def add_row(self, row, style, cache_id):
r = self.row_cache.get(cache_id, lambda: _Row(row=row, table=self, borders=self.borders, height=1, style=style))
self.children.append(r)
@property
def columns(self):
return max(row.raw_columns for row in self.children)
@property
def _all_children(self):
"""
List of child objects, including padding & borders.
"""
def get():
result = []
# Padding top.
if self.align in (VerticalAlign.CENTER, VerticalAlign.BOTTOM):
result.append(Window(width=D(preferred=0)))
# Border top is first inserted in children loop.
# The children with padding.
prev = None
for child in self.children:
# result.append(_Border(
# prev=prev,
# next=child,
# table=self,
# borders=self.borders))
result.append(child)
prev = child
# Border bottom.
# result.append(_Border(prev=prev, next=None, table=self, borders=self.borders))
# Padding bottom.
if self.align in (VerticalAlign.CENTER, VerticalAlign.TOP):
result.append(Window(width=D(preferred=0)))
return result
return self._children_cache.get(tuple(self.children), get)
def preferred_dimensions(self, width):
dimensions = [[]] * self.columns
for row in self.children:
assert isinstance(row, _Row)
j = 0
for cell in row.children:
assert isinstance(cell, _Cell)
if cell.merge != 1:
dimensions[j].append(cell.preferred_width(width))
j += cell.merge
for i, c in enumerate(dimensions):
yield D.exact(1)
try:
w = self.column_widths[i]
except IndexError:
w = self.column_width
if w is None: # fitted
yield max_layout_dimensions(c)
else: # fixed or weighted
yield to_dimension(w)
yield D.exact(1)
class _VerticalBorder(Window):
def __init__(self, borders):
super().__init__(width=1, char=borders.VERTICAL)
class _HorizontalBorder(Window):
def __init__(self, borders):
super().__init__(height=1, char=borders.HORIZONTAL)
class _UnitBorder(Window):
def __init__(self, char):
super().__init__(width=1, height=1, char=char)
class _BaseRow(VSplit):
@property
def columns(self):
return self.table.columns
def _divide_widths(self, width):
"""
Return the widths for all columns.
Or None when there is not enough space.
"""
children = self._all_children
if not children:
return []
# Calculate widths.
dimensions = list(self.table.preferred_dimensions(width))
preferred_dimensions = [d.preferred for d in dimensions]
# Sum dimensions
sum_dimensions = sum_layout_dimensions(dimensions)
# If there is not enough space for both.
# Don't do anything.
if sum_dimensions.min > width:
return
# Find optimal sizes. (Start with minimal size, increase until we cover
# the whole width.)
sizes = [d.min for d in dimensions]
child_generator = take_using_weights(
items=list(range(len(dimensions))),
weights=[d.weight for d in dimensions])
i = next(child_generator)
# Increase until we meet at least the 'preferred' size.
preferred_stop = min(width, sum_dimensions.preferred)
while sum(sizes) < preferred_stop:
if sizes[i] < preferred_dimensions[i]:
sizes[i] += 1
i = next(child_generator)
# Increase until we use all the available space.
max_dimensions = [d.max for d in dimensions]
max_stop = min(width, sum_dimensions.max)
while sum(sizes) < max_stop:
if sizes[i] < max_dimensions[i]:
sizes[i] += 1
i = next(child_generator)
# perform merges if necessary
if len(children) != len(sizes):
tmp = []
i = 0
for c in children:
if isinstance(c, _Cell):
inc = (c.merge * 2) - 1
tmp.append(sum(sizes[i:i + inc]))
else:
inc = 1
tmp.append(sizes[i])
i += inc
sizes = tmp
return sizes
class _Row(_BaseRow):
def __init__(self, row, table, borders,
window_too_small=None, align=HorizontalAlign.JUSTIFY,
padding=D.exact(0), padding_char=None, padding_style='',
width=None, height=None, z_index=None,
modal=False, key_bindings=None, style=''):
self.table = table
self.borders = borders
# ensure the row is iterable (has cells)
if not isinstance(row, list):
row = [row]
children = []
for c in row:
m = 1
if isinstance(c, Merge):
c, m = c
elif isinstance(c, dict):
c, m = Merge(**c)
children.append(_Cell(cell=c, table=table, row=self, merge=m))
super().__init__(
children=children,
window_too_small=window_too_small,
align=align,
padding=padding,
padding_char=padding_char,
padding_style=padding_style,
width=width,
height=height,
z_index=z_index,
modal=modal,
key_bindings=key_bindings,
style=style)
@property
def raw_columns(self):
return sum(cell.merge for cell in self.children)
@property
def _all_children(self):
"""
List of child objects, including padding & borders.
"""
def get():
result = []
# Padding left.
if self.align in (HorizontalAlign.CENTER, HorizontalAlign.RIGHT):
result.append(Window(width=D(preferred=0)))
# Border left is first inserted in children loop.
# The children with padding.
c = 0
for child in self.children:
result.append(_VerticalBorder(borders=self.borders))
result.append(child)
c += child.merge
# Fill in any missing columns
for _ in range(self.columns - c):
result.append(_VerticalBorder(borders=self.borders))
result.append(_Cell(cell=None, table=self.table, row=self))
# Border right.
result.append(_VerticalBorder(borders=self.borders))
# Padding right.
if self.align in (HorizontalAlign.CENTER, HorizontalAlign.LEFT):
result.append(Window(width=D(preferred=0)))
return result
return self._children_cache.get(tuple(self.children), get)
class _Border(_BaseRow):
def __init__(self, prev, next, table, borders,
window_too_small=None, align=HorizontalAlign.JUSTIFY,
padding=D.exact(0), padding_char=None, padding_style='',
width=None, height=None, z_index=None,
modal=False, key_bindings=None, style=''):
assert prev or next
self.prev = prev
self.next = next
self.table = table
self.borders = borders
children = [_HorizontalBorder(borders=borders)] * self.columns
super().__init__(
children=children,
window_too_small=window_too_small,
align=align,
padding=padding,
padding_char=padding_char,
padding_style=padding_style,
width=width,
height=height or 1,
z_index=z_index,
modal=modal,
key_bindings=key_bindings,
style=style)
def has_borders(self, row):
yield None # first (outer) border
if not row:
# this row is undefined, none of the borders need to be marked
yield from [False] * (self.columns - 1)
else:
c = 0
for child in row.children:
yield from [False] * (child.merge - 1)
yield True
c += child.merge
yield from [True] * (self.columns - c)
yield None # last (outer) border
@property
def _all_children(self):
"""
List of child objects, including padding & borders.
"""
def get():
result = []
# Padding left.
if self.align in (HorizontalAlign.CENTER, HorizontalAlign.RIGHT):
result.append(Window(width=D(preferred=0)))
def char(i, pc=False, nc=False):
if i == 0:
if self.prev and self.next:
return self.borders.LEFT_T
elif self.prev:
return self.borders.BOTTOM_LEFT
else:
return self.borders.TOP_LEFT
if i == self.columns:
if self.prev and self.next:
return self.borders.RIGHT_T
elif self.prev:
return self.borders.BOTTOM_RIGHT
else:
return self.borders.TOP_RIGHT
if pc and nc:
return self.borders.INTERSECT
elif pc:
return self.borders.BOTTOM_T
elif nc:
return self.borders.TOP_T
else:
return self.borders.HORIZONTAL
# Border left is first inserted in children loop.
# The children with padding.
pcs = self.has_borders(self.prev)
ncs = self.has_borders(self.next)
for i, (child, pc, nc) in enumerate(zip(self.children, pcs, ncs)):
result.append(_UnitBorder(char=char(i, pc, nc)))
result.append(child)
# Border right.
result.append(_UnitBorder(char=char(self.columns)))
# Padding right.
if self.align in (HorizontalAlign.CENTER, HorizontalAlign.LEFT):
result.append(Window(width=D(preferred=0)))
return result
return self._children_cache.get(tuple(self.children), get)
class _Cell(HSplit):
def __init__(self, cell, table, row, merge=1,
padding=0, char=None,
padding_left=None, padding_right=None,
padding_top=None, padding_bottom=None,
window_too_small=None,
width=None, height=None, z_index=None,
modal=False, key_bindings=None, style=''):
self.table = table
self.row = row
self.merge = merge
if padding is None:
padding = D(preferred=0)
def get(value):
if value is None:
value = padding
return to_dimension(value)
self.padding_left = get(padding_left)
self.padding_right = get(padding_right)
self.padding_top = get(padding_top)
self.padding_bottom = get(padding_bottom)
children = []
children.append(Window(width=self.padding_left, char=char))
if cell:
children.append(cell)
children.append(Window(width=self.padding_right, char=char))
children = [
Window(height=self.padding_top, char=char),
VSplit(children),
Window(height=self.padding_bottom, char=char),
]
super().__init__(
children=children,
window_too_small=window_too_small,
width=width,
height=height,
z_index=z_index,
modal=modal,
key_bindings=key_bindings,
style=style)
def demo():
txt1 = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut purus nibh, sollicitudin at lorem eget, tristique fringilla purus. Donec sit amet lectus porta, aliquam ligula sed, sagittis eros. Proin in augue leo. Donec vitae erat pellentesque, hendrerit tellus malesuada, mollis urna. Nam varius, lorem id porttitor euismod, erat sapien tempus odio, ac porttitor eros lacus et magna. Nam in arcu pellentesque, bibendum est vel, viverra nulla. Ut ut accumsan risus. Donec at volutpat tortor. Nulla ac elementum lacus. Pellentesque nec nibh tempus, posuere massa nec, consequat lorem. Curabitur ac sollicitudin neque. Donec vel ante magna. Nunc nec sapien vitae sem bibendum volutpat. Donec posuere nulla felis, id mattis risus dictum ut. Fusce libero mi, varius aliquet commodo at, tempus ut eros."
txt2 = "Praesent eu ultrices massa. Cras et dui bibendum, venenatis urna nec, porttitor sem. Nullam commodo tempor pellentesque. Praesent leo odio, fermentum a ultrices a, ultrices eu nibh. Etiam commodo orci urna, vitae egestas enim ultricies vel. Cras diam eros, rutrum in congue ac, pretium sed quam. Cras commodo ut ipsum ut sollicitudin."
txt3 = "Proin in varius purus. <b>Aliquam nec nulla</b> eget lorem fermentum facilisis. Quisque eget ante quam. Quisque vel pharetra magna. Sed volutpat, ligula sed aliquam pharetra, felis tellus bibendum mi, at imperdiet risus augue eget eros. Suspendisse ut venenatis magna, at euismod magna. Phasellus bibendum maximus sem eget porttitor. Donec et eleifend mi, in ornare nisi."
txt4 = "Morbi viverra, justo eget pretium sollicitudin, magna ex sodales ligula, et convallis erat mauris eu urna. Curabitur tristique quis metus at sodales. Nullam tincidunt convallis lorem in faucibus. Donec nec turpis ante. Ut tincidunt neque eu ornare sagittis. Suspendisse potenti. Etiam tellus est, porttitor eget luctus sed, euismod et erat. Vivamus commodo, massa eget mattis eleifend, turpis sem porttitor dolor, eu finibus ex erat id tellus. Etiam viverra iaculis tellus, ut tempus tellus. Maecenas arcu lectus, euismod accumsan erat eu, blandit vehicula dui. Nulla id ante egestas, imperdiet nibh et, fringilla orci. Donec ut pretium est. Vivamus feugiat facilisis iaculis. Pellentesque imperdiet ex felis, ac elementum dolor tincidunt eget. Cras molestie tellus id massa suscipit, hendrerit vulputate metus tincidunt. Aliquam erat enim, rhoncus in metus eu, consequat cursus ipsum."
txt5 = "Integer at dictum justo. Vestibulum gravida nec diam a iaculis. Nullam non sollicitudin turpis, in mollis augue. In ut interdum magna. Ut tellus eros, blandit a ex a, suscipit varius tortor. Nulla pulvinar nibh vitae tristique tincidunt. Proin eu fringilla nibh. Mauris metus erat, laoreet sed eros ac, maximus finibus turpis. Sed tortor massa, congue nec lacus nec, fringilla fermentum purus. Vivamus eget pretium mi, vel ultricies orci. Phasellus semper viverra lorem. Phasellus velit nisl, scelerisque sit amet vulputate luctus, ullamcorper in velit. Fusce pellentesque elit ut leo tincidunt euismod. Integer nisl ante, dignissim id leo ut, interdum auctor ipsum. Fusce nunc ligula, imperdiet et nisl id, mollis imperdiet erat."
txt6 = "Ut egestas vel nisi et sodales. Etiam arcu massa, viverra in pellentesque quis, molestie a lacus. Nulla suscipit mi luctus blandit dignissim. Proin ac turpis sit amet enim luctus venenatis quis et orci. Donec sed tortor ex. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In et turpis sapien. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vivamus et purus et ex interdum commodo at non velit. Nullam eget diam et felis accumsan gravida non vitae nulla. Morbi tellus mauris, tristique non vulputate eu, efficitur at tellus. Morbi in erat et purus euismod luctus vel vel erat. Fusce auctor augue felis, quis ornare justo mattis vel."
txt7 = "Etiam quis eros eu urna consequat finibus. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Integer suscipit, est ac blandit cursus, sem diam egestas massa, sit amet dignissim velit nibh in tortor. Fusce scelerisque feugiat ligula vitae pharetra. Fusce mattis placerat volutpat. Aliquam fermentum ligula mauris, sit amet tempor dui elementum nec. Nunc augue felis, egestas ut lacus vitae, placerat condimentum turpis. Nulla volutpat quis felis id tristique. Vivamus non odio et magna dapibus suscipit vitae et dui. Aenean magna lectus, congue eu commodo luctus, dapibus vel sem. Quisque nec diam consequat, luctus nisi vitae, rutrum mi. Nullam consectetur non risus eleifend dignissim. Vivamus sit amet sagittis libero. Integer nec ipsum fringilla, pulvinar ex vitae, aliquet est. Etiam sit amet est finibus, facilisis erat aliquet, egestas lectus."
txt8 = "Donec placerat lacus egestas, aliquam enim vitae, congue ipsum. Praesent vitae eros cursus, pulvinar lectus et, ornare ipsum. Fusce luctus odio vitae hendrerit mollis. Morbi eu turpis vel elit tristique ullamcorper at sodales turpis. Curabitur in ante tincidunt, pellentesque lacus non, dignissim arcu. Mauris ut egestas mi, id elementum ipsum. Morbi justo nisi, laoreet nec lobortis nec, vulputate et justo. Quisque vel pretium quam. Cras consequat quam erat, eu finibus nisi pretium eu. Maecenas ac commodo lacus, non lobortis nunc."
txt9 = "Vivamus et leo eget turpis scelerisque blandit at vel tellus. Vestibulum ac arcu turpis. Cras iaculis suscipit justo, at cursus ex condimentum non. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Mauris eget tincidunt nisi, at imperdiet orci. Praesent bibendum bibendum nunc sit amet euismod. Nullam eu metus malesuada, faucibus purus eget, imperdiet justo. Donec in dui nunc. Sed vel sodales neque. Duis congue venenatis semper. Donec commodo in magna id tincidunt. Maecenas sollicitudin dignissim lorem id interdum."
txt10 = "Aliquam eleifend mi arcu, sit amet convallis tellus condimentum sit amet. Duis lobortis nisl lectus, et convallis augue bibendum sit amet. Maecenas vestibulum porta lorem eu pharetra. Aliquam erat volutpat. Pellentesque volutpat nunc sit amet sem vestibulum commodo. In consequat diam id eros tincidunt dignissim. Maecenas aliquam, elit vitae consectetur facilisis, enim lectus facilisis dui, sed sodales leo dui et augue. Phasellus convallis lacinia pellentesque. Mauris et vulputate ligula. Quisque et velit diam. Pellentesque maximus, augue sit amet semper malesuada, urna velit ultrices lorem, et commodo tortor nibh non justo. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas."
sht1 = "Hello World"
sht2 = "Buzz"
sht3 = "The quick brown fox jumps over the lazy dog."
kb = KeyBindings()
@kb.add('c-c')
def _(event):
" Abort when Control-C has been pressed. "
event.app.exit(exception=KeyboardInterrupt, style='class:aborting')
table = [
[TextArea(sht1), Label(txt2), TextArea(txt1)],
[Merge(TextArea(sht2), 2), TextArea(txt4)],
[Button(sht3), Merge(TextArea(txt6), 3)],
[Button(sht1), TextArea(txt8)],
[TextArea(sht2), TextArea(txt10)],
]
# table = TextArea(txt2)
layout = Layout(
Box(
Table(
table=table,
column_width=D.exact(15),
column_widths=[None],
borders=DoubleBorder),
padding=1,
),
)
return Application(layout, key_bindings=kb)
if __name__ == '__main__':
demo().run()

3
androguard/ui/util.py Normal file
View File

@ -0,0 +1,3 @@
def clamp(range_min: int, range_max: int, value: int) -> int:
"""Return value if its is within range_min and range_max else return the nearest bound"""
return max(min(range_max, value), range_min)

View File

@ -0,0 +1,61 @@
import json
from typing import Optional
from prompt_toolkit.filters import Condition
from prompt_toolkit.formatted_text import FormattedText
from prompt_toolkit.key_binding import KeyBindings
from prompt_toolkit.layout import AnyContainer, Dimension, HSplit, FormattedTextControl, Window
from prompt_toolkit.layout.dimension import AnyDimension
from androguard.ui.selection import SelectionViewList
from androguard.ui.data_types import DisplayTransaction
from androguard.ui.widget.frame import SelectableFrame
class DetailsFrame:
def __init__(self, transactions: SelectionViewList, max_lines: int) -> None:
self.transactions = transactions
self.max_lines = max_lines
self.transactions.on_selection_change += self.update_content
self.offset = 0
self.container = SelectableFrame(
title="Details",
body=self.get_content,
width=Dimension(min=56, preferred=100, max=100),
height=Dimension(preferred=max_lines)
)
@property
def activated(self) -> bool:
return self.container.activated
@activated.setter
def activated(self, value: bool):
self.container.activated = value
def update_content(self, _, offset=0):
self.offset = offset
self.container.body = self.get_content()
def get_content(self) -> AnyContainer:
return HSplit(
children=[
Window(
ignore_content_height=True,
content=FormattedTextControl(
text=self.get_current_details()
)
),
]
)
def get_current_details(self):
if self.transactions.selection_valid():
return json.dumps(self.transactions.selected().params, indent=2) + '\n' + json.dumps(self.transactions.selected().ret_value, indent=2)
return ''
def __pt_container__(self) -> AnyContainer:
return self.container

View File

@ -0,0 +1,78 @@
from prompt_toolkit.key_binding import KeyBindings
from prompt_toolkit.layout import AnyContainer, HSplit, VerticalAlign, VSplit
from prompt_toolkit.key_binding.bindings.focus import focus_next, focus_previous
from prompt_toolkit.widgets import Frame, CheckboxList, Box, TextArea, Label
from androguard.ui.filter import Filter
class TypeCheckboxlist(CheckboxList):
def __init__(self) -> None:
values=[
("call", "call"),
("return", "return"),
("oneway", "oneway"),
("error", "error"),
("unknown", "unknown"),
]
super().__init__(values)
self.show_scrollbar = False
class FiltersPanel:
def __init__(self) -> None:
self.visible = False
self.interface_textarea = TextArea(multiline=False, style="class:dialogger.textarea")
self.method_textarea = TextArea(multiline=False, style="class:dialogger.textarea")
self.type_filter_checkboxes = TypeCheckboxlist()
float_frame = Box(
padding_top=1,
padding_left=2,
padding_right=2,
body=HSplit(
padding=1,
width=50,
align=VerticalAlign.TOP,
children=[
VSplit(children=[
Label("Interface", width=10),
self.interface_textarea
]),
VSplit(children=[
Label("Method", width=10),
self.method_textarea
]),
VSplit(children=[
Label("Type", width=10, dont_extend_height=False),
self.type_filter_checkboxes,
]),
]
)
)
kb = KeyBindings()
kb.add("tab")(focus_next)
kb.add("s-tab")(focus_previous)
self.container = Frame(
title="Filters",
body=float_frame,
style="class:dialogger.background",
modal=True,
key_bindings=kb
)
def filter(self) -> Filter:
return Filter(
self.interface_textarea.text,
self.method_textarea.text,
self.type_filter_checkboxes.current_values)
def __pt_container__(self) -> AnyContainer:
return self.container

View File

@ -0,0 +1,121 @@
from functools import partial
from typing import Optional
from prompt_toolkit.filters import Condition
from prompt_toolkit.formatted_text import AnyFormattedText, Template
from prompt_toolkit.key_binding import KeyBindings
from prompt_toolkit.layout import (
AnyContainer,
AnyDimension,
ConditionalContainer,
Container,
DynamicContainer,
HSplit,
VSplit,
Window
)
from prompt_toolkit.widgets.base import Border, Label
class SelectableFrame:
"""
Draw a border around any container, optionally with a title text.
Changing the title and body of the frame is possible at runtime by
assigning to the `body` and `title` attributes of this class.
:param body: Another container object.
:param title: Text to be displayed in the top of the frame (can be formatted text).
:param style: Style string to be applied to this widget.
"""
def __init__(
self,
body: AnyContainer,
title: AnyFormattedText = "",
style: str = "",
width: AnyDimension = None,
height: AnyDimension = None,
key_bindings: Optional[KeyBindings] = None,
modal: bool = False,
activated: bool = False,
) -> None:
self.title = title
self.body = body
self.activated = activated
def get_style() -> str:
if self.activated:
return "class:frame.border.selected"
else:
return "class:frame.border"
fill = partial(Window, style=get_style)
style = "class:frame " + style
top_row_with_title = VSplit(
[
fill(width=1, height=1, char=Border.TOP_LEFT),
fill(char=Border.HORIZONTAL),
fill(width=1, height=1, char="|"),
# Notice: we use `Template` here, because `self.title` can be an
# `HTML` object for instance.
Label(
lambda: Template(" {} ").format(self.title),
style="class:frame.label",
dont_extend_width=True,
),
fill(width=1, height=1, char="|"),
fill(char=Border.HORIZONTAL),
fill(width=1, height=1, char=Border.TOP_RIGHT),
],
height=1,
)
top_row_without_title = VSplit(
[
fill(width=1, height=1, char=Border.TOP_LEFT),
fill(char=Border.HORIZONTAL),
fill(width=1, height=1, char=Border.TOP_RIGHT),
],
height=1,
)
@Condition
def has_title() -> bool:
return bool(self.title)
self.container = HSplit(
[
ConditionalContainer(content=top_row_with_title, filter=has_title),
ConditionalContainer(content=top_row_without_title, filter=~has_title),
VSplit(
[
fill(width=1, char=Border.VERTICAL),
DynamicContainer(self.body),
fill(width=1, char=Border.VERTICAL),
# Padding is required to make sure that if the content is
# too small, the right frame border is still aligned.
],
padding=0,
),
VSplit(
[
fill(width=1, height=1, char=Border.BOTTOM_LEFT),
fill(char=Border.HORIZONTAL),
fill(width=1, height=1, char=Border.BOTTOM_RIGHT),
],
# specifying height here will increase the rendering speed.
height=1,
),
],
width=width,
height=height,
style=style,
key_bindings=key_bindings,
modal=modal,
)
def __pt_container__(self) -> Container:
return self.container

View File

@ -0,0 +1,38 @@
from prompt_toolkit.layout import AnyContainer, HSplit
from prompt_toolkit.widgets import Frame, Box, Label
class HelpPanel:
def __init__(self) -> None:
self.visible = False
float_frame = Box(
padding_top=1,
padding_left=2,
padding_right=2,
body=HSplit(children=[
Label("up Move up"),
Label("down Move down"),
Label("shift + up Page up"),
Label("shift + down Page down"),
Label("home Go to top"),
Label("end Go to bottom"),
Label("tab Next pane"),
Label("shift + tab Previous pane"),
Label("ctrl + c Copy pane to clipboard"),
Label("f Open filter options"),
Label("h Help"),
Label("q Quit"),
]),
)
self.container = Frame(
title="Help",
body=float_frame,
style="class:dialogger.background",
modal=True,
)
def __pt_container__(self) -> AnyContainer:
return self.container

View File

@ -0,0 +1,29 @@
from typing import Sequence
from prompt_toolkit.formatted_text import AnyFormattedText, FormattedText
from prompt_toolkit.layout.containers import AnyContainer, DynamicContainer
from prompt_toolkit.widgets import FormattedTextToolbar
from androguard.ui.widget.filters import FiltersPanel
class StatusToolbar:
def __init__(self, transactions: Sequence, filters: FiltersPanel) -> None:
self.transactions = transactions
self.filters = filters
self.container = DynamicContainer(self.toolbar_container)
def toolbar_container(self) -> AnyContainer:
return FormattedTextToolbar(
text=self.toolbar_text(),
style="class:toolbar",
)
def toolbar_text(self) -> AnyFormattedText:
return FormattedText([
("class:toolbar.text", f"Transactions: {len(self.transactions)}, Filter: {self.filters.filter()}")
])
def __pt_container__(self) -> AnyContainer:
return self.container

View File

@ -0,0 +1,145 @@
import csv
import io
#import pyperclip
from prompt_toolkit.filters import Condition
from prompt_toolkit.key_binding import KeyBindings
from prompt_toolkit.layout.dimension import AnyDimension, Dimension
from prompt_toolkit.layout import AnyContainer
from androguard.ui import table
from androguard.ui.selection import SelectionViewList
from androguard.ui.widget.frame import SelectableFrame
class TransactionFrame:
def __init__(self, transactions: SelectionViewList, height: AnyDimension = None) -> None:
self.transactions = transactions
self.transactions.on_update_event += self.update_table
self.headings = [
table.Label(""),
table.Label("#"),
table.Label("From"),
table.Label("Method"),
]
self.table = table.Table(
table=[self.headings],
# height=height,
column_width=Dimension.exact(10),
column_widths=[
Dimension.exact(1),
Dimension(min=2, preferred=4, max=4),
Dimension(min=20, preferred=40),
Dimension(min=20, preferred=30),
],
borders=table.EmptyBorder,
)
self.pad_table()
self.container = SelectableFrame(
title="Transactions",
body=self.get_content,
)
def resize(self, height):
# Subtract one for the header row
height -= 1
self.transactions.resize_view(height)
def get_content(self):
return self.table
def update_table(self, _):
self.table.children.clear()
self.table.add_row(self.headings, "class:transactions.heading", id(self.headings))
for i in range(self.transactions.view.start, self.transactions.view.end):
row, style = self._to_row(self.transactions[i])
self.table.add_row(
row,
f"{style} reverse" if i == self.transactions.selection else style,
(id(self.transactions[i]), i == self.transactions.selection)
)
self.pad_table()
def pad_table(self):
padding = self.transactions.max_view_size - self.transactions.view.size()
empty_row = [
table.Label(""),
table.Label(""),
table.Label(""),
table.Label(""),
table.Label(""),
]
for _ in range(padding):
self.table.add_row(empty_row, "class:transaction.default", id(empty_row))
def key_bindings(self) -> KeyBindings:
kb = KeyBindings()
@kb.add('up', filter=Condition(lambda: self.activated))
def _(event):
self.transactions.move_selection(-1)
@kb.add('down', filter=Condition(lambda: self.activated))
def _(event):
self.transactions.move_selection(1)
@kb.add('s-up', filter=Condition(lambda: self.activated))
def _(event):
self.transactions.move_selection(-self.transactions.max_view_size)
@kb.add('s-down', filter=Condition(lambda: self.activated))
def _(event):
self.transactions.move_selection(self.transactions.max_view_size)
@kb.add('home', filter=Condition(lambda: self.activated))
def _(event):
self.transactions.move_selection(-self.transactions.selection)
@kb.add('end', filter=Condition(lambda: self.activated))
def _(event):
self.transactions.move_selection(len(self.transactions) - self.transactions.selection)
return kb
@property
def activated(self):
return self.container.activated
# Define a "name" setter
@activated.setter
def activated(self, value):
self.container.activated = value
#def copy_to_clipboard(self):
# if self.transactions.selection_valid():
# output = io.StringIO()
# writer = csv.writer(output, quoting=csv.QUOTE_NONE)
# for t in self.transactions.data:
# writer.writerow([
# t.interface,
# str(t.method_number),
# t.method,
# hex(len(t.raw_data))
# ])
# pyperclip.copy(output.getvalue())
def _to_row(self, transaction):
# TODO: Cache the rows so we don't need to recreate them.
return [
table.Label(transaction.direction_indicator),
table.Label(str(transaction.index)),
table.Label(transaction.from_method),
table.Label(transaction.to_method)
], transaction.style()
def __pt_container__(self) -> AnyContainer:
return self.container

61
cli.py
View File

@ -1,31 +1,25 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Androguard is a full Python tool to play with Android files."""
"""Androguard is a full Python tool to reverse Android Applications."""
# core modules
import sys, json
from loguru import logger
# 3rd party modules
import click
import androguard.core.apk
from loguru import logger
# local modules
import androguard
import androguard.core.apk
from androguard.core.androconf import show_logging
from main import (androarsc_main,
androaxml_main,
export_apps_to_format,
androsign_main,
androlyze_main,
androgui_main,
androdis_main,
androstrace_main,
androtrace_main,
androdump_main,
)
@ -47,6 +41,7 @@ def entry_point(verbosity):
logger.remove(0)
my_filter = MyFilter("INFO")
logger.add(sys.stderr, filter=my_filter, level=0)
logger.add("androguard.log", retention="10 days")
@entry_point.command()
@ -366,22 +361,6 @@ def apkid(apks):
print(json.dumps(results, indent=2))
@entry_point.command()
@click.option(
'--input_file', '-i',
help="Specify the inital file to load in the GUI",
type=click.Path(exists=True, dir_okay=False, file_okay=True),
)
@click.option(
'--input_plugin', '-p',
help="Additional Plugin (currently unused)",
type=click.Path(exists=True),
)
def gui(input_file, input_plugin):
"""Androguard GUI"""
androgui_main(input_file, input_plugin)
@entry_point.command()
@click.option(
'--session',
@ -418,25 +397,6 @@ def disassemble(offset, size, dex):
"""
androdis_main(offset, size, dex)
@entry_point.command()
@click.argument(
'apk',
default=None,
required=False,
type=click.Path(exists=True, dir_okay=False, file_okay=True),
)
def strace(apk):
"""
Push an APK on the phone and start to trace all syscalls.
Example:
\b
$ androguard strace test.APK
"""
androstrace_main(apk)
@entry_point.command()
@click.argument(
'apk',
@ -447,8 +407,12 @@ def strace(apk):
@click.option("-m", "--modules",
multiple=True, default=[],
help="A list of modules to load in frida")
def trace(apk, modules):
@click.option(
'--enable-ui', is_flag=True,
default=False,
help='Enable UI',
)
def trace(apk, modules, enable_ui):
"""
Push an APK on the phone and start to trace all interesting methods from the modules list
@ -456,8 +420,9 @@ def trace(apk, modules):
\b
$ androguard trace test.APK -m "ipc/*" -m "webviews/*" -m "modules/**"
$ androguard trace test.APK -m "ipc/*" -m "webviews/*" -m "modules/**" --enable-ui
"""
androtrace_main(apk, modules, False)
androtrace_main(apk, modules, False, enable_ui)
@entry_point.command()
@click.argument(
@ -492,7 +457,7 @@ def dtrace(package_name, modules):
def dump(package_name, modules):
"""
Start dynamically an installed APK on the phone and start to trace all interesting methods from the modules list
Start and dump dynamically an installed APK on the phone
Example:

101
main.py
View File

@ -3,21 +3,20 @@ import os
import re
import shutil
import sys
import yaml
# 3rd party modules
from lxml import etree
from loguru import logger
from pygments import highlight
from pygments.lexers import get_lexer_by_name
from pygments.formatters.terminal import TerminalFormatter
# internal modules
from androguard.core import androconf
from androguard.core import apk
from androguard.core.axml import AXMLPrinter
from androguard.util import readFile
from pygments import highlight
from pygments.lexers import get_lexer_by_name
from pygments.formatters.terminal import TerminalFormatter
from androguard.ui import DynamicUI
def androaxml_main(inp, outp=None, resource=None):
ret_type = androconf.is_android(inp)
@ -81,7 +80,7 @@ def export_apps_to_format(filename,
decompiler_type=None,
form=None):
from androguard.misc import clean_file_name
from androguard.core.bytecodes import dvm
from androguard.core.dex import DEX
from androguard.core.bytecode import method2dot, method2format
from androguard.decompiler import decompiler
print("Dump information {} in {}".format(filename, output))
@ -171,7 +170,7 @@ def export_apps_to_format(filename,
# Write SMALI like code
print("bytecodes ...", end=' ')
bytecode_buff = dvm.get_bytecodes_method(vm, vmx, method)
bytecode_buff = DEX.get_bytecodes_method(vm, vmx, method)
with open(filename + ".ag", "w") as fd:
fd.write(bytecode_buff)
print()
@ -188,39 +187,6 @@ def create_directory(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(session, filename):
"""
Start an interactive shell
@ -232,7 +198,8 @@ def androlyze_main(session, filename):
import colorama
import atexit
from IPython.terminal.embed import InteractiveShellEmbed
from IPython.terminal.embed import embed
from traitlets.config import Config
from androguard.core.androconf import ANDROGUARD_VERSION, CONF
@ -240,6 +207,7 @@ def androlyze_main(session, filename):
from androguard.core import dex, apk
from androguard.core.analysis.analysis import Analysis
from androguard.pentest import Pentest
from androguard.ui import DynamicUI
from androguard.misc import AnalyzeAPK
colorama.init()
@ -316,8 +284,7 @@ def androlyze_main(session, filename):
cfg = Config()
_version_string = "Androguard version {}".format(ANDROGUARD_VERSION)
ipshell = InteractiveShellEmbed(config=cfg, banner1="{} started"
.format(_version_string))
ipshell = embed(config=cfg, banner1="{} started".format(_version_string))
atexit.register(shutdown_hook)
ipshell()
@ -436,24 +403,7 @@ def androdis_main(offset, size, dex_file):
idx += i.get_length()
def androstrace_main(apk_file):
from androguard.pentest import Pentest
from androguard.session import Session
s = Session()
with open(apk_file, "rb") as fp:
raw = fp.read()
h = s.add(apk_file, raw)
logger.info("Added file to session: SHA256::{}".format(h))
p = Pentest()
p.print_devices()
p.connect_default_usb()
p.start_strace(apk_file, s, loop=True)
def androtrace_main(apk_file, list_modules, live=False):
def androtrace_main(apk_file, list_modules, live=False, enable_ui=False):
from androguard.pentest import Pentest
from androguard.session import Session
@ -469,7 +419,32 @@ def androtrace_main(apk_file, list_modules, live=False):
p = Pentest()
p.print_devices()
p.connect_default_usb()
p.start_trace(apk_file, s, list_modules, loop=True, live=live)
p.start_trace(apk_file, s, list_modules, live=live)
if enable_ui:
logger.remove(1)
from prompt_toolkit.eventloop.inputhook import InputHookContext, set_eventloop_with_inputhook
from prompt_toolkit.application import get_app
import time
time.sleep(1)
ui = DynamicUI(p.message_queue)
def inputhook(inputhook_context: InputHookContext):
while not inputhook_context.input_is_ready():
if ui.process_data():
get_app().invalidate()
else:
time.sleep(0.1)
set_eventloop_with_inputhook(inputhook=inputhook)
ui.run()
else:
logger.warning("Type 'e' to exit the strace ")
s = ""
while (s!='e') and (not p.is_detached()):
s = input("Type 'e' to exit:")
def androdump_main(package_name, list_modules):
@ -481,4 +456,4 @@ def androdump_main(package_name, list_modules):
p = Pentest()
p.print_devices()
p.connect_default_usb()
p.start_trace(package_name, s, list_modules, loop=True, live=True, dump=True)
p.start_trace(package_name, s, list_modules, live=True, dump=True)

View File

@ -1,15 +1,11 @@
pygments>=2.3.1
lxml>=4.3.0
colorama>=0.4.1
matplotlib>=3.0.2
asn1crypto>=0.24.0
click>=7.0
pydot>=1.4.1
ipython>=5.0.0
pyyaml
fastapi
uvicorn
loguru
mutf8
dataset
frida
loguru

View File

@ -11,7 +11,7 @@ class AnalysisTest(unittest.TestCase):
def testDex(self):
with open("examples/android/TestsAndroguard/bin/classes.dex",
"rb") as fd:
d = dvm.DalvikVMFormat(fd.read())
d = dvm.DEX(fd.read())
dx = analysis.Analysis(d)
self.assertIsInstance(dx, analysis.Analysis)
@ -46,7 +46,7 @@ class AnalysisTest(unittest.TestCase):
h, d, dx = AnalyzeDex("examples/tests/AnalysisTest.dex")
self.assertEqual(h, "4595fc25104f3fcd709163eb70ca476edf116753607ec18f09548968c71910dc")
self.assertIsInstance(d, dvm.DalvikVMFormat)
self.assertIsInstance(d, dvm.DEX)
self.assertIsInstance(dx, analysis.Analysis)
cls = ["Ljava/io/PrintStream;", "Ljava/lang/Object;",
@ -73,8 +73,8 @@ class AnalysisTest(unittest.TestCase):
c1 = myzip.read("classes.dex")
c2 = myzip.read("classes2.dex")
d1 = dvm.DalvikVMFormat(c1)
d2 = dvm.DalvikVMFormat(c2)
d1 = dvm.DEX(c1)
d2 = dvm.DEX(c2)
dx = analysis.Analysis()
@ -129,7 +129,7 @@ class AnalysisTest(unittest.TestCase):
def testXrefs(self):
"""Test if XREFs produce the correct results"""
with open("examples/android/TestsAndroguard/bin/classes.dex", "rb") as fd:
d = dvm.DalvikVMFormat(fd.read())
d = dvm.DEX(fd.read())
dx = analysis.Analysis(d)
dx.create_xref()

View File

@ -6,7 +6,7 @@ from androguard.core.bytecodes import dvm
class AnnotationTest(unittest.TestCase):
def testAnnotation(self):
with open("examples/android/TestsAnnotation/classes.dex", "rb") as f:
d = dvm.DalvikVMFormat(f.read())
d = dvm.DEX(f.read())
clazz = d.get_class('Landroid/support/v4/widget/SlidingPaneLayout$SlidingPanelLayoutImplJB;')
annotations = clazz._get_annotation_type_ids()

View File

@ -28,12 +28,12 @@ class APKTest(unittest.TestCase):
def testAPKWrapper(self):
from androguard.misc import AnalyzeAPK
from androguard.core.bytecodes.apk import APK
from androguard.core.bytecodes.dvm import DalvikVMFormat
from androguard.core.bytecodes.dvm import DEX
from androguard.core.analysis.analysis import Analysis
a, d, dx = AnalyzeAPK("examples/android/TestsAndroguard/bin/TestActivity.apk")
self.assertIsInstance(a, APK)
self.assertIsInstance(d[0], DalvikVMFormat)
self.assertIsInstance(d[0], DEX)
self.assertIsInstance(dx, Analysis)
self.assertEqual(a.get_signature_name(), "META-INF/CERT.RSA")
@ -44,7 +44,7 @@ class APKTest(unittest.TestCase):
def testAPKWrapperRaw(self):
from androguard.misc import AnalyzeAPK
from androguard.core.bytecodes.apk import APK
from androguard.core.bytecodes.dvm import DalvikVMFormat
from androguard.core.bytecodes.dvm import DEX
from androguard.core.analysis.analysis import Analysis
with open(
"examples/android/TestsAndroguard/bin/TestActivity.apk", 'rb') \
@ -52,7 +52,7 @@ class APKTest(unittest.TestCase):
file_contents = file_obj.read()
a, d, dx = AnalyzeAPK(file_contents, raw=True)
self.assertIsInstance(a, APK)
self.assertIsInstance(d[0], DalvikVMFormat)
self.assertIsInstance(d[0], DEX)
self.assertIsInstance(dx, Analysis)
self.assertEqual(a.get_signature_name(), "META-INF/CERT.RSA")
@ -63,12 +63,12 @@ class APKTest(unittest.TestCase):
def testMultiDexAPK(self):
from androguard.misc import AnalyzeAPK
from androguard.core.bytecodes.apk import APK
from androguard.core.bytecodes.dvm import DalvikVMFormat
from androguard.core.bytecodes.dvm import DEX
from androguard.core.analysis.analysis import Analysis
a, d, dx = AnalyzeAPK('examples/android/abcore/app-prod-debug.apk')
self.assertIsInstance(a, APK)
self.assertIsInstance(d[0], DalvikVMFormat)
self.assertIsInstance(d[1], DalvikVMFormat)
self.assertIsInstance(d[0], DEX)
self.assertIsInstance(d[1], DEX)
self.assertIsInstance(dx, Analysis)
def testAPKCert(self):
@ -348,12 +348,12 @@ class APKTest(unittest.TestCase):
def testAPKWrapperUnsigned(self):
from androguard.misc import AnalyzeAPK
from androguard.core.bytecodes.apk import APK
from androguard.core.bytecodes.dvm import DalvikVMFormat
from androguard.core.bytecodes.dvm import DEX
from androguard.core.analysis.analysis import Analysis
a, d, dx = AnalyzeAPK("examples/android/TestsAndroguard/bin/TestActivity_unsigned.apk")
self.assertIsInstance(a, APK)
self.assertIsInstance(d[0], DalvikVMFormat)
self.assertIsInstance(d[0], DEX)
self.assertIsInstance(dx, Analysis)
self.assertIsNone(a.get_signature_name())

View File

@ -2,8 +2,10 @@
import unittest
from lxml import etree
from androguard.core.bytecodes import apk, axml
from androguard.core.bytecodes.apk import APK
import sys
sys.path.append('.')
from androguard.core import apk, axml
from operator import itemgetter
TEST_APP_NAME = "TestsAndroguardApplication"
@ -120,7 +122,7 @@ class ARSCTest(unittest.TestCase):
"received unexpected resource types: %s" % unexpected_types)
def testFallback(self):
a = APK("examples/tests/com.teleca.jamendo_35.apk")
a = apk.APK("examples/tests/com.teleca.jamendo_35.apk")
# Should use the fallback
self.assertEqual(a.get_app_name(), "Jamendo")

View File

@ -1,10 +1,12 @@
import unittest
import sys
from xml.dom import minidom
from lxml import etree
from androguard.core.bytecodes import axml
import sys
sys.path.append("./")
from androguard.core import axml
from androguard.core import bytecode
def is_valid_manifest(tree):

View File

@ -4,7 +4,7 @@ from androguard.misc import AnalyzeDex
import sys
import re
from androguard.misc import AnalyzeAPK
from androguard.decompiler.dad.decompile import DvMethod, DvClass
from androguard.decompiler.decompile import DvMethod, DvClass
class DecompilerTest(unittest.TestCase):

View File

@ -1,16 +1,16 @@
"""Tests for def_use."""
import sys
sys.path.append('.')
import collections
import mock
import unittest
from androguard.decompiler.dad import dataflow
from androguard.decompiler.dad import graph
from androguard.decompiler.dad import instruction
from androguard.decompiler.dad import basic_blocks
from androguard.decompiler import dataflow
from androguard.decompiler import graph
from androguard.decompiler import instruction
from androguard.decompiler import basic_blocks
class DataflowTest(unittest.TestCase):

View File

@ -1,11 +1,10 @@
"""Tests for graph."""
import sys
sys.path.append('.')
import unittest
from androguard.decompiler.dad import graph
from androguard.decompiler import graph
class DominatorTest(unittest.TestCase):

View File

@ -5,8 +5,8 @@ import sys
sys.path.append('.')
import unittest
from androguard.decompiler.dad import graph
from androguard.decompiler.dad import node
from androguard.decompiler import graph
from androguard.decompiler import node
class NodeTest(node.Node):

View File

@ -1,45 +0,0 @@
import unittest
import sys
import re
import os
from androguard.misc import AnalyzeAPK
from androguard.decompiler.decompiler import DecompilerJADX
def which(program):
"""
Thankfully copied from https://stackoverflow.com/a/377028/446140
"""
def is_exe(fpath):
return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
fpath, fname = os.path.split(program)
if fpath:
if is_exe(program):
return program
else:
for path in os.environ["PATH"].split(os.pathsep):
exe_file = os.path.join(path, program)
if is_exe(exe_file):
return exe_file
return None
class DecompilerTest(unittest.TestCase):
@unittest.skipIf(which("jadx") is None, "Skipping JADX test as jadx "
"executable is not in path")
def testJadx(self):
a, d, dx = AnalyzeAPK("examples/tests/hello-world.apk")
decomp = DecompilerJADX(d[0], dx)
self.assertIsNotNone(decomp)
d[0].set_decompiler(decomp)
for c in d[0].get_classes():
self.assertIsNotNone(c.get_source())
if __name__ == '__main__':
unittest.main()

View File

@ -1,194 +1,79 @@
import unittest
import sys
import random
import binascii
import logging
from loguru import logger
from androguard.core.bytecodes import dvm
log = logging.getLogger("androguard.tests")
import sys
sys.path.append("./")
from androguard.core import dex
class DexTest(unittest.TestCase):
def testDex(self):
with open("examples/android/TestsAndroguard/bin/classes.dex", "rb") as fd:
d = dvm.DalvikVMFormat(fd.read())
self.assertTrue(d)
classes = d.get_classes()
self.assertTrue(classes)
self.assertEqual(len(classes), 340)
methods = d.get_methods()
self.assertTrue(methods)
self.assertEqual(len(methods), 2600)
fields = d.get_fields()
self.assertTrue(fields)
self.assertEqual(len(fields), 803)
def testBrokenDex(self):
"""Test various broken DEX headers"""
# really not a dex file
with self.assertRaises(ValueError) as cnx:
dvm.DalvikVMFormat(b'\x00\x00\x00\x00\x00\x00\x00\x00')
dex.DEX(b'\x00\x00\x00\x00\x00\x00\x00\x00')
self.assertIn('Header too small', str(cnx.exception))
# Adler32 will not match, zeroed out file
dex = binascii.unhexlify('6465780A303335001F6C4D5A6ACF889AF588F3237FC9F20B41F56A2408749D1B'
data_dex = binascii.unhexlify('6465780A303335001F6C4D5A6ACF889AF588F3237FC9F20B41F56A2408749D1B'
'C81E000070000000785634120000000000000000341E00009400000070000000'
'2E000000C0020000310000007803000011000000C4050000590000004C060000'
'090000001409000094140000340A0000' + ('00' * (7880 - 0x70)))
with self.assertRaises(ValueError) as cnx:
dvm.DalvikVMFormat(dex)
dex.DEX(data_dex)
self.assertIn("Adler32", str(cnx.exception))
# A very very basic dex file (without a map)
# But should parse...
dex = binascii.unhexlify('6465780A30333500460A4882696E76616C6964696E76616C6964696E76616C69'
data_dex = binascii.unhexlify('6465780A30333500460A4882696E76616C6964696E76616C6964696E76616C69'
'7000000070000000785634120000000000000000000000000000000000000000'
'0000000000000000000000000000000000000000000000000000000000000000'
'00000000000000000000000000000000')
with self.assertLogs(logger='androguard.dvm', level=logging.WARNING) as cnx:
self.assertIsNotNone(dvm.DalvikVMFormat(dex))
self.assertEqual(cnx.output, ["WARNING:androguard.dvm:no map list! This DEX file is probably empty."])
self.assertIsNotNone(dex.DEX(data_dex))
# Wrong header size
dex = binascii.unhexlify('6465780A30333500480A2C8D696E76616C6964696E76616C6964696E76616C69'
data_dex = binascii.unhexlify('6465780A30333500480A2C8D696E76616C6964696E76616C6964696E76616C69'
'7100000071000000785634120000000000000000000000000000000000000000'
'0000000000000000000000000000000000000000000000000000000000000000'
'0000000000000000000000000000000000')
with self.assertRaises(ValueError) as cnx:
dvm.DalvikVMFormat(dex)
dex.DEX(data_dex)
self.assertIn("Wrong header size", str(cnx.exception))
# Non integer version
dex = binascii.unhexlify('6465780AFF00AB00460A4882696E76616C6964696E76616C6964696E76616C69'
# Non integer version, but parse it
data_dex = binascii.unhexlify('6465780AFF00AB00460A4882696E76616C6964696E76616C6964696E76616C69'
'7000000070000000785634120000000000000000000000000000000000000000'
'0000000000000000000000000000000000000000000000000000000000000000'
'00000000000000000000000000000000')
with self.assertLogs(logger='androguard.dvm', level=logging.WARNING) as cnx:
dvm.DalvikVMFormat(dex)
self.assertEqual(cnx.output, ["WARNING:androguard.dvm:Wrong DEX version: b'dex\\n\\xff\\x00\\xab\\x00', trying to parse anyways",
"WARNING:androguard.dvm:no map list! This DEX file is probably empty."])
self.assertIsNotNone(dex.DEX(data_dex))
# Big Endian file
dex = binascii.unhexlify('6465780A30333500460AF480696E76616C6964696E76616C6964696E76616C69'
data_dex = binascii.unhexlify('6465780A30333500460AF480696E76616C6964696E76616C6964696E76616C69'
'7000000070000000123456780000000000000000000000000000000000000000'
'0000000000000000000000000000000000000000000000000000000000000000'
'00000000000000000000000000000000')
with self.assertRaises(NotImplementedError) as cnx:
dvm.DalvikVMFormat(dex)
dex.DEX(data_dex)
self.assertIn("swapped endian tag", str(cnx.exception))
# Weird endian file
dex = binascii.unhexlify('6465780A30333500AB0BC3E4696E76616C6964696E76616C6964696E76616C69'
data_dex = binascii.unhexlify('6465780A30333500AB0BC3E4696E76616C6964696E76616C6964696E76616C69'
'7000000070000000ABCDEF120000000000000000000000000000000000000000'
'0000000000000000000000000000000000000000000000000000000000000000'
'00000000000000000000000000000000')
with self.assertRaises(ValueError) as cnx:
dvm.DalvikVMFormat(dex)
dex.DEX(data_dex)
self.assertIn("Wrong endian tag", str(cnx.exception))
def testDexWrapper(self):
from androguard.misc import AnalyzeDex
from androguard.core.bytecodes.dvm import DalvikVMFormat
from androguard.core.analysis.analysis import Analysis
h, d, dx = AnalyzeDex("examples/android/TestsAndroguard/bin/classes.dex")
self.assertEqual(h, '2f24538b3064f1f88d3eb29ee7fbd2146779a4c9144aefa766d18965be8775c7')
self.assertIsInstance(d, DalvikVMFormat)
self.assertIsInstance(dx, Analysis)
classes = d.get_classes()
self.assertTrue(classes)
self.assertEqual(len(classes), 340)
methods = d.get_methods()
self.assertTrue(methods)
self.assertEqual(len(methods), 2600)
fields = d.get_fields()
self.assertTrue(fields)
self.assertEqual(len(fields), 803)
def testDexVersion(self):
dexfiles = [
('examples/dalvik/test/bin/classes_output.dex', 35),
('examples/dalvik/test/bin/classes.dex', 35),
('examples/obfu/classes_tc_diff_dasho.dex', 35),
('examples/obfu/classes_tc_dasho.dex', 35),
('examples/obfu/classes_tc_mark1.dex', 35),
('examples/obfu/classes_tc.dex', 35),
('examples/obfu/classes_tc_diff.dex', 35),
('examples/obfu/classes_tc_proguard.dex', 35),
('examples/android/TCDiff/bin/classes.dex', 35),
('examples/android/TestsAndroguard/bin/classes.dex', 35),
('examples/android/TC/bin/classes.dex', 35),
('examples/tests/Test.dex', 35),
('examples/tests/ExceptionHandling.dex', 35),
('examples/tests/InterfaceCls.dex', 35),
('examples/tests/FillArrays.dex', 35),
('examples/tests/StringTests.dex', 35),
('examples/tests/AnalysisTest.dex', 35),
('examples/tests/Switch.dex', 35),
# Dalvik 036
('examples/tests/2992e3a94a774ddfe2b50c6e8667d925a5684d71.36.dex', 36),
('examples/tests/921d74ac9568121d0ea1453922a369cb66739c68.36.dex', 36),
# Dalvik 037
('examples/tests/dc4b1bb9d58daa82f29e60f79d5662f731a3351f.37.dex', 37),
('examples/tests/fdroid/com.example.trigger_130.dex', 37),
('examples/tests/fdroid/net.eneiluj.nextcloud.phonetrack_2.dex', 37),
('examples/tests/fdroid/org.andstatus.app_254.dex', 37),
# Dalvik 038 / Requires a minimal API level of 26
('examples/tests/fdroid/cat.mvmike.minimalcalendarwidget_17.dex', 38),
('examples/tests/okhttp.d8.038.dex', 38),
('examples/tests/okhttp.dx.038.dex', 38), # contains invoke-custom opcode
# Dalvik 039 / Requires a minimal API level of 28
('examples/tests/okhttp.d8.039.dex', 39),
('examples/tests/okhttp.dx.039.dex', 39), # contains invoke-custom opcode
]
for dexf, dexver in dexfiles:
log.info("Testing {} -> Version {}".format(dexf, dexver))
with open(dexf, 'rb') as fp:
d = dvm.DalvikVMFormat(fp.read())
self.assertEqual(d.version, dexver)
self.assertGreater(d.header.string_ids_size, 0)
self.assertGreater(d.header.type_ids_size, 0)
self.assertGreater(d.header.proto_ids_size, 0)
self.assertGreater(d.header.method_ids_size, 0)
self.assertGreater(d.header.class_defs_size, 0)
self.assertGreater(d.header.data_size, 0)
for i in range(d.header.string_ids_size):
self.assertIsInstance(d.strings[i], dvm.StringDataItem)
for m in d.get_methods():
l = 0
i = -1
for i, ins in enumerate(m.get_instructions()):
l += ins.get_length()
if ins.get_op_value() <= 0xFF:
self.assertIsInstance(ins, dvm.Instruction)
else:
self.assertIn(ins.get_name(), ('fill-array-data-payload',
'packed-switch-payload',
'sparse-switch-payload'))
if m.get_code() is not None:
# Make sure all instructions are parsed
self.assertGreaterEqual(i + 1, 1)
self.assertEqual(l % 2, 0)
self.assertEqual(l, m.get_code().insns_size * 2)
class MockClassManager():
@property
def packer(self):
return dvm.DalvikPacker(0x12345678)
return dex.DalvikPacker(0x12345678)
def get_odex_format(self):
return False
@ -201,9 +86,9 @@ class InstructionTest(unittest.TestCase):
random.seed(1337)
for op_value in range(0, 256):
ins = dvm.DALVIK_OPCODES_FORMAT[op_value][0]
name = dvm.DALVIK_OPCODES_FORMAT[op_value][1]
self.assertEqual(issubclass(ins, dvm.Instruction), True)
ins = dex.DALVIK_OPCODES_FORMAT[op_value][0]
name = dex.DALVIK_OPCODES_FORMAT[op_value][1]
self.assertEqual(issubclass(ins, dex.Instruction), True)
# The Name should code for the length of the opcode
length = int(ins.__name__[11]) * 2
@ -211,7 +96,7 @@ class InstructionTest(unittest.TestCase):
if name[0] == 'unused':
# unused instructions should raise an error on invocation
with self.assertRaises(dvm.InvalidInstruction):
with self.assertRaises(dex.InvalidInstruction):
ins(MockClassManager(), bytearray([op_value, 0]))
# therefore, we can not test much else here...
continue
@ -219,7 +104,7 @@ class InstructionTest(unittest.TestCase):
# Test if instruction can be parsed
bytecode = bytearray([op_value] + [0] * (length - 1))
instruction = ins(MockClassManager(), bytecode)
self.assertIsInstance(instruction, dvm.Instruction)
self.assertIsInstance(instruction, dex.Instruction)
self.assertEqual(instruction.get_op_value(), op_value)
# And packed again
@ -234,13 +119,13 @@ class InstructionTest(unittest.TestCase):
else:
bytecode = bytearray([op_value] + [random.randint(0x00, 0xff) for _ in range(length - 1)])
instruction = ins(MockClassManager(), bytecode)
self.assertIsInstance(instruction, dvm.Instruction)
self.assertIsInstance(instruction, dex.Instruction)
self.assertEqual(instruction.get_op_value(), op_value)
self.assertEqual(instruction.get_raw(), bytecode)
def testNOP(self):
"""test if NOP instructions are parsed"""
instruction = dvm.Instruction10x(MockClassManager(), bytearray(b"\x00\x00"))
instruction = dex.Instruction10x(MockClassManager(), bytearray(b"\x00\x00"))
self.assertEqual(instruction.get_name(), "nop")
def testLinearSweep(self):
@ -249,8 +134,8 @@ class InstructionTest(unittest.TestCase):
instructions = ['nop', 'nop', 'nop', 'return-void']
l = 0
for ins in dvm.LinearSweepAlgorithm.get_instructions(MockClassManager(), 4, bytecode, 0):
self.assertIsInstance(ins, dvm.Instruction10x)
for ins in dex.LinearSweepAlgorithm.get_instructions(MockClassManager(), 4, bytecode, 0):
self.assertIsInstance(ins, dex.Instruction10x)
self.assertEqual(ins.get_length(), 2)
self.assertEqual(ins.get_name(), instructions.pop(0))
l += ins.get_length()
@ -285,8 +170,8 @@ class InstructionTest(unittest.TestCase):
]
l = 0
for ins in dvm.LinearSweepAlgorithm.get_instructions(MockClassManager(), 71, bytecode, 0):
self.assertIsInstance(ins, dvm.Instruction)
for ins in dex.LinearSweepAlgorithm.get_instructions(MockClassManager(), 71, bytecode, 0):
self.assertIsInstance(ins, dex.Instruction)
self.assertEqual(ins.get_name(), instructions.pop(0))
l += ins.get_length()
# check if all instructions were consumed
@ -316,11 +201,11 @@ class InstructionTest(unittest.TestCase):
]
l = 0
for ins in dvm.LinearSweepAlgorithm.get_instructions(MockClassManager(), 30, bytecode, 0):
for ins in dex.LinearSweepAlgorithm.get_instructions(MockClassManager(), 30, bytecode, 0):
if len(instructions) > 1:
self.assertIsInstance(ins, dvm.Instruction)
self.assertIsInstance(ins, dex.Instruction)
else:
self.assertIsInstance(ins, dvm.PackedSwitch)
self.assertIsInstance(ins, dex.PackedSwitch)
self.assertEqual(ins.get_name(), instructions.pop(0))
l += ins.get_length()
# check if all instructions were consumed
@ -358,12 +243,12 @@ class InstructionTest(unittest.TestCase):
# array information: (element_width, size)
arrays = [(1, 4), (4, 7), (2, 5), (2, 4)]
for ins in dvm.LinearSweepAlgorithm.get_instructions(MockClassManager(), 90, bytecode, 0):
for ins in dex.LinearSweepAlgorithm.get_instructions(MockClassManager(), 90, bytecode, 0):
self.assertEqual(ins.get_name(), instructions.pop(0))
if ins.get_name() != 'fill-array-data-payload':
self.assertIsInstance(ins, dvm.Instruction)
self.assertIsInstance(ins, dex.Instruction)
else:
self.assertIsInstance(ins, dvm.FillArrayData)
self.assertIsInstance(ins, dex.FillArrayData)
elem_size, size = arrays.pop(0)
self.assertEqual(ins.element_width, elem_size)
self.assertEqual(ins.size, size)
@ -375,79 +260,74 @@ class InstructionTest(unittest.TestCase):
self.assertEqual(len(bytecode), l)
def testWrongInstructions(self):
"""Test if unknown instructions log an error"""
with self.assertLogs(logger='androguard.dvm', level=logging.ERROR) as cnx:
ins = list(dvm.LinearSweepAlgorithm.get_instructions(MockClassManager(), 1, bytearray(b"\xff\xab"), 0))
self.assertEqual(cnx.output, ["ERROR:androguard.dvm:Invalid instruction encountered! "
"Stop parsing bytecode at idx 0. Message: Unknown Instruction '0xabff'"])
self.assertEqual(len(ins), 0) # No instruction should be parsed
"""Test if unknown instructions raise an InvalidInstruction error"""
with self.assertRaises(dex.InvalidInstruction):
ins = list(dex.LinearSweepAlgorithm.get_instructions(MockClassManager(), 1, bytearray(b"\xff\xab"), 0))
with self.assertLogs(logger='androguard.dvm', level=logging.ERROR) as cnx:
ins = list(dvm.LinearSweepAlgorithm.get_instructions(MockClassManager(), 2, bytearray(b"\x00\x00"
with self.assertRaises(dex.InvalidInstruction):
ins = list(dex.LinearSweepAlgorithm.get_instructions(MockClassManager(), 2, bytearray(b"\x00\x00"
b"\xff\xab"), 0))
self.assertEqual(cnx.output, ["ERROR:androguard.dvm:Invalid instruction encountered! "
"Stop parsing bytecode at idx 2. Message: Unknown Instruction '0xabff'"])
self.assertEqual(len(ins), 1) # One instruction should be parsed
def testIncompleteInstruction(self):
"""Test if incomplete bytecode log an error"""
# Test if instruction can be parsed
self.assertIsInstance(dvm.Instruction51l(MockClassManager(),
bytearray(b'\x18\x01\x23\x23\x00\xff\x99\x11\x22\x22')), dvm.Instruction51l)
self.assertIsInstance(dex.Instruction51l(MockClassManager(),
bytearray(b'\x18\x01\x23\x23\x00\xff\x99\x11\x22\x22')), dex.Instruction51l)
ins = list(dvm.LinearSweepAlgorithm.get_instructions(MockClassManager(), 5, bytearray(b"\x18\x01\xff\xff"), 0))
self.assertEqual(len(ins), 0)
with self.assertRaises(dex.InvalidInstruction):
ins = list(dex.LinearSweepAlgorithm.get_instructions(MockClassManager(), 5, bytearray(b"\x18\x01\xff\xff"), 0))
def testInstruction21h(self):
"""Test function of Instruction 21h used for const{,-wide}/high16"""
ins = dvm.Instruction21h(MockClassManager(), bytearray([0x15, 0x00, 0x42, 0x11]))
ins = dex.Instruction21h(MockClassManager(), bytearray([0x15, 0x00, 0x42, 0x11]))
self.assertEqual(ins.get_op_value(), 0x15)
self.assertEqual(ins.get_literals(), [0x11420000])
self.assertEqual(ins.get_operands(), [(dvm.Operand.REGISTER, 0x00), (dvm.Operand.LITERAL, 0x11420000)])
self.assertEqual(ins.get_operands(), [(dex.Operand.REGISTER, 0x00), (dex.Operand.LITERAL, 0x11420000)])
self.assertEqual(ins.get_name(), 'const/high16')
self.assertEqual(ins.get_output(), 'v0, 289538048')
self.assertEqual(ins.get_raw(), bytearray([0x15, 0x00, 0x42, 0x11]))
ins = dvm.Instruction21h(MockClassManager(), bytearray([0x19, 0x00, 0x42, 0x11]))
ins = dex.Instruction21h(MockClassManager(), bytearray([0x19, 0x00, 0x42, 0x11]))
self.assertEqual(ins.get_op_value(), 0x19)
self.assertEqual(ins.get_literals(), [0x1142000000000000])
self.assertEqual(ins.get_operands(), [(dvm.Operand.REGISTER, 0x00), (dvm.Operand.LITERAL, 0x1142000000000000)])
self.assertEqual(ins.get_operands(), [(dex.Operand.REGISTER, 0x00), (dex.Operand.LITERAL, 0x1142000000000000)])
self.assertEqual(ins.get_name(), 'const-wide/high16')
self.assertEqual(ins.get_output(), 'v0, 1243556447107678208')
self.assertEqual(ins.get_raw(), bytearray([0x19, 0x00, 0x42, 0x11]))
ins = dvm.Instruction21h(MockClassManager(), bytearray([0x19, 0x00, 0xbe, 0xff]))
ins = dex.Instruction21h(MockClassManager(), bytearray([0x19, 0x00, 0xbe, 0xff]))
self.assertEqual(ins.get_op_value(), 0x19)
self.assertEqual(ins.get_literals(), [-18577348462903296])
self.assertEqual(ins.get_operands(), [(dvm.Operand.REGISTER, 0x00), (dvm.Operand.LITERAL, -18577348462903296)])
self.assertEqual(ins.get_operands(), [(dex.Operand.REGISTER, 0x00), (dex.Operand.LITERAL, -18577348462903296)])
self.assertEqual(ins.get_name(), 'const-wide/high16')
self.assertEqual(ins.get_output(), 'v0, -18577348462903296')
self.assertEqual(ins.get_raw(), bytearray([0x19, 0x00, 0xbe, 0xff]))
def testInstruction51l(self):
"""test the functionality of const-wide"""
ins = dvm.Instruction51l(MockClassManager(), bytearray([0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]))
ins = dex.Instruction51l(MockClassManager(), bytearray([0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]))
self.assertEqual(ins.get_op_value(), 0x18)
self.assertEqual(ins.get_literals(), [0])
self.assertEqual(ins.get_operands(), [(dvm.Operand.REGISTER, 0x00), (dvm.Operand.LITERAL, 0)])
self.assertEqual(ins.get_operands(), [(dex.Operand.REGISTER, 0x00), (dex.Operand.LITERAL, 0)])
self.assertEqual(ins.get_name(), 'const-wide')
self.assertEqual(ins.get_output(), 'v0, 0')
self.assertEqual(ins.get_raw(), bytearray([0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]))
bytecode = bytearray([0x18, 0x00, 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x70])
ins = dvm.Instruction51l(MockClassManager(), bytecode)
ins = dex.Instruction51l(MockClassManager(), bytecode)
self.assertEqual(ins.get_op_value(), 0x18)
self.assertEqual(ins.get_literals(), [0x7034129078563412])
self.assertEqual(ins.get_operands(), [(dvm.Operand.REGISTER, 0x00), (dvm.Operand.LITERAL, 0x7034129078563412)])
self.assertEqual(ins.get_operands(), [(dex.Operand.REGISTER, 0x00), (dex.Operand.LITERAL, 0x7034129078563412)])
self.assertEqual(ins.get_name(), 'const-wide')
self.assertEqual(ins.get_output(), 'v0, 8085107642740388882')
self.assertEqual(ins.get_raw(), bytecode)
bytecode = bytearray([0x18, 0x00, 0xee, 0xcb, 0xa9, 0x87, 0x6f, 0xed, 0xcb, 0x8f])
ins = dvm.Instruction51l(MockClassManager(), bytecode)
ins = dex.Instruction51l(MockClassManager(), bytecode)
self.assertEqual(ins.get_op_value(), 0x18)
self.assertEqual(ins.get_literals(), [-8085107642740388882])
self.assertEqual(ins.get_operands(), [(dvm.Operand.REGISTER, 0x00), (dvm.Operand.LITERAL, -8085107642740388882)])
self.assertEqual(ins.get_operands(), [(dex.Operand.REGISTER, 0x00), (dex.Operand.LITERAL, -8085107642740388882)])
self.assertEqual(ins.get_name(), 'const-wide')
self.assertEqual(ins.get_output(), 'v0, -8085107642740388882')
self.assertEqual(ins.get_raw(), bytecode)
@ -466,11 +346,11 @@ class InstructionTest(unittest.TestCase):
(0x86, 6, -8),
]
for args, reg, lit in tests:
ins = dvm.Instruction11n(MockClassManager(), bytearray([0x12, args]))
ins = dex.Instruction11n(MockClassManager(), bytearray([0x12, args]))
self.assertEqual(ins.get_name(), 'const/4')
self.assertEqual(ins.get_literals(), [lit])
self.assertEqual(ins.get_raw(), bytearray([0x12, args]))
self.assertEqual(ins.get_operands(), [(dvm.Operand.REGISTER, reg), (dvm.Operand.LITERAL, lit)])
self.assertEqual(ins.get_operands(), [(dex.Operand.REGISTER, reg), (dex.Operand.LITERAL, lit)])
self.assertEqual(ins.get_output(), 'v{}, {}'.format(reg, lit))
def testInstruction21s(self):
@ -492,20 +372,20 @@ class InstructionTest(unittest.TestCase):
for args, reg, lit in tests:
# const/16
bytecode = bytearray([0x13] + args)
ins = dvm.Instruction21s(MockClassManager(), bytecode)
ins = dex.Instruction21s(MockClassManager(), bytecode)
self.assertEqual(ins.get_name(), 'const/16')
self.assertEqual(ins.get_literals(), [lit])
self.assertEqual(ins.get_raw(), bytecode)
self.assertEqual(ins.get_operands(), [(dvm.Operand.REGISTER, reg), (dvm.Operand.LITERAL, lit)])
self.assertEqual(ins.get_operands(), [(dex.Operand.REGISTER, reg), (dex.Operand.LITERAL, lit)])
self.assertEqual(ins.get_output(), 'v{}, {}'.format(reg, lit))
# const-wide/16
bytecode = bytearray([0x16] + args)
ins = dvm.Instruction21s(MockClassManager(), bytecode)
ins = dex.Instruction21s(MockClassManager(), bytecode)
self.assertEqual(ins.get_name(), 'const-wide/16')
self.assertEqual(ins.get_literals(), [lit])
self.assertEqual(ins.get_raw(), bytecode)
self.assertEqual(ins.get_operands(), [(dvm.Operand.REGISTER, reg), (dvm.Operand.LITERAL, lit)])
self.assertEqual(ins.get_operands(), [(dex.Operand.REGISTER, reg), (dex.Operand.LITERAL, lit)])
self.assertEqual(ins.get_output(), 'v{}, {}'.format(reg, lit))
def testInstruction31i(self):
@ -521,20 +401,20 @@ class InstructionTest(unittest.TestCase):
for args, reg, lit in tests:
# const
bytecode = bytearray([0x14] + args)
ins = dvm.Instruction31i(MockClassManager(), bytecode)
ins = dex.Instruction31i(MockClassManager(), bytecode)
self.assertEqual(ins.get_name(), 'const')
self.assertEqual(ins.get_literals(), [lit])
self.assertEqual(ins.get_raw(), bytecode)
self.assertEqual(ins.get_operands(), [(dvm.Operand.REGISTER, reg), (dvm.Operand.LITERAL, lit)])
self.assertEqual(ins.get_operands(), [(dex.Operand.REGISTER, reg), (dex.Operand.LITERAL, lit)])
self.assertEqual(ins.get_output(), 'v{}, {}'.format(reg, lit))
# const-wide/32
bytecode = bytearray([0x17] + args)
ins = dvm.Instruction31i(MockClassManager(), bytecode)
ins = dex.Instruction31i(MockClassManager(), bytecode)
self.assertEqual(ins.get_name(), 'const-wide/32')
self.assertEqual(ins.get_literals(), [lit])
self.assertEqual(ins.get_raw(), bytecode)
self.assertEqual(ins.get_operands(), [(dvm.Operand.REGISTER, reg), (dvm.Operand.LITERAL, lit)])
self.assertEqual(ins.get_operands(), [(dex.Operand.REGISTER, reg), (dex.Operand.LITERAL, lit)])
self.assertEqual(ins.get_output(), 'v{}, {}'.format(reg, lit))

View File

@ -1,317 +0,0 @@
# -*- coding: utf-8 -*-
# core modules
from pkg_resources import resource_filename
from tempfile import mkstemp, mkdtemp
import os
import shutil
import unittest
# 3rd party modules
from click.testing import CliRunner
# internal modules
from androguard.cli import entry_points
def get_apks():
"""Get a list of APKs for testing scripts"""
for root, _, files in os.walk(resource_filename('androguard', '..')):
for f in files:
if f == 'multidex.apk':
# This file does not have a manifest, hence everything fails
continue
if f.endswith('.apk') and 'signing' not in root:
yield os.path.join(root, f)
class EntryPointsTest(unittest.TestCase):
def test_entry_point_help(self):
runner = CliRunner()
result = runner.invoke(entry_points.entry_point, ['--help'])
assert result.exit_code == 0
def test_axml_help(self):
runner = CliRunner()
result = runner.invoke(entry_points.entry_point,
['axml', '--help'])
assert result.exit_code == 0
def test_axml_basic_call_by_positional_argument(self):
axml_path = resource_filename('androguard',
'../examples/axml/AndroidManifest.xml')
_, output_path = mkstemp(prefix='androguard_', suffix='decoded.txt')
runner = CliRunner()
arguments = ['axml', axml_path, '-o', output_path]
result = runner.invoke(entry_points.entry_point,
arguments)
assert result.exit_code == 0, arguments
os.remove(output_path)
def test_axml_basic_call_by_input_argument(self):
axml_path = resource_filename('androguard',
'../examples/axml/AndroidManifest.xml')
_, output_path = mkstemp(prefix='androguard_', suffix='decoded.txt')
runner = CliRunner()
arguments = ['axml', '-i', axml_path, '-o', output_path]
result = runner.invoke(entry_points.entry_point,
arguments)
assert result.exit_code == 0, arguments
os.remove(output_path)
def test_axml_error_call_two_arguments(self):
axml_path = resource_filename('androguard',
'../examples/axml/AndroidManifest.xml')
_, output_path = mkstemp(prefix='androguard_', suffix='decoded.txt')
runner = CliRunner()
arguments = ['axml', '-i', axml_path,
'-o', output_path,
axml_path]
result = runner.invoke(entry_points.entry_point, arguments)
assert result.exit_code == 1, arguments
os.remove(output_path)
def test_axml_error_call_no_arguments(self):
_, output_path = mkstemp(prefix='androguard_', suffix='decoded.txt')
runner = CliRunner()
arguments = ['axml', '-o', output_path]
result = runner.invoke(entry_points.entry_point, arguments)
assert result.exit_code == 1, arguments
os.remove(output_path)
def test_arsc_basic_call_positional_apk(self):
runner = CliRunner()
apk_path = resource_filename('androguard',
'../examples/dalvik/test/bin/'
'Test-debug.apk')
arguments = ['arsc', apk_path]
result = runner.invoke(entry_points.entry_point, arguments)
assert result.exit_code == 0, arguments
def test_arsc_error_filetype_py(self):
runner = CliRunner()
dex_path = resource_filename('androguard',
'../examples/dalvik/test/bin/'
'classes.dex')
arguments = ['arsc', dex_path]
result = runner.invoke(entry_points.entry_point, arguments)
assert result.exit_code == 1, arguments
def test_arsc_basic_call_keyword(self):
runner = CliRunner()
apk_path = resource_filename('androguard',
'../examples/dalvik/test/bin/'
'Test-debug.apk')
arguments = ['arsc', '-i', apk_path]
result = runner.invoke(entry_points.entry_point, arguments)
assert result.exit_code == 0, arguments
def test_arsc_basic_call_list_packages(self):
runner = CliRunner()
apk_path = resource_filename('androguard',
'../examples/dalvik/test/bin/'
'Test-debug.apk')
arguments = ['arsc', apk_path, '--list-packages']
result = runner.invoke(entry_points.entry_point, arguments)
assert result.exit_code == 0, arguments
def test_arsc_basic_call_list_locales(self):
runner = CliRunner()
apk_path = resource_filename('androguard',
'../examples/dalvik/test/bin/'
'Test-debug.apk')
arguments = ['arsc', apk_path, '--list-locales']
result = runner.invoke(entry_points.entry_point, arguments)
assert result.exit_code == 0, arguments
def test_arsc_basic_call_list_types(self):
runner = CliRunner()
apk_path = resource_filename('androguard',
'../examples/dalvik/test/bin/'
'Test-debug.apk')
arguments = ['arsc', apk_path, '--list-types']
result = runner.invoke(entry_points.entry_point, arguments)
assert result.exit_code == 0, arguments
def test_arsc_error_two_arguments(self):
runner = CliRunner()
apk_path = resource_filename('androguard',
'../examples/dalvik/test/bin/'
'Test-debug.apk')
arguments = ['arsc', apk_path, '-i', apk_path]
result = runner.invoke(entry_points.entry_point, arguments)
assert result.exit_code == 1, arguments
def test_arsc_basic_id_resolve(self):
runner = CliRunner()
apk_path = resource_filename('androguard',
'../examples/dalvik/test/bin/'
'Test-debug.apk')
arguments = ['arsc', apk_path, '--id', '7F030000']
result = runner.invoke(entry_points.entry_point, arguments)
assert result.exit_code == 0, arguments
def test_arsc_error_id_resolve(self):
runner = CliRunner()
apk_path = resource_filename('androguard',
'../examples/dalvik/test/bin/'
'Test-debug.apk')
arguments = ['arsc', apk_path, '--id', 'sdlkfjsdlkf']
result = runner.invoke(entry_points.entry_point, arguments)
assert result.exit_code == 1, arguments
def test_arsc_error_id_not_resolve(self):
runner = CliRunner()
apk_path = resource_filename('androguard',
'../examples/dalvik/test/bin/'
'Test-debug.apk')
arguments = ['arsc', apk_path, '--id', '12345678']
result = runner.invoke(entry_points.entry_point, arguments)
assert result.exit_code == 1, arguments
def test_arsc_error_no_arguments(self):
runner = CliRunner()
arguments = ['arsc']
result = runner.invoke(entry_points.entry_point, arguments)
assert result.exit_code == 1, arguments
def test_arsc_help(self):
runner = CliRunner()
result = runner.invoke(entry_points.entry_point, ['arsc', '--help'])
assert result.exit_code == 0
def test_cg_basic(self):
runner = CliRunner()
apk_path = resource_filename('androguard',
'../examples/dalvik/test/bin/'
'Test-debug.apk')
arguments = ['--debug', 'cg', apk_path]
result = runner.invoke(entry_points.entry_point, arguments)
assert result.exit_code == 0
def test_cg_help(self):
runner = CliRunner()
result = runner.invoke(entry_points.entry_point, ['cg', '--help'])
assert result.exit_code == 0
def test_decompile_basic_positional(self):
runner = CliRunner()
apk_path = resource_filename('androguard',
'../examples/dalvik/test/bin/'
'Test-debug.apk')
output_dir = mkdtemp(prefix='androguard_test_')
result = runner.invoke(entry_points.entry_point,
['decompile', apk_path, '-o', output_dir])
assert result.exit_code == 0
# Cleanup:
if os.path.exists(output_dir) and os.path.isdir(output_dir):
shutil.rmtree(output_dir)
def test_decompile_basic_input(self):
runner = CliRunner()
apk_path = resource_filename('androguard',
'../examples/dalvik/test/bin/'
'Test-debug.apk')
output_dir = mkdtemp(prefix='androguard_test_')
result = runner.invoke(entry_points.entry_point,
['decompile', '-i', apk_path, '-o', output_dir])
assert result.exit_code == 0
# Cleanup:
if os.path.exists(output_dir) and os.path.isdir(output_dir):
shutil.rmtree(output_dir)
def test_decompile_error_two_arguments(self):
runner = CliRunner()
apk_path = resource_filename('androguard',
'../examples/dalvik/test/bin/'
'Test-debug.apk')
output_dir = mkdtemp(prefix='androguard_test_')
arguments = ['decompile', '-i', apk_path, apk_path, '-o', output_dir]
result = runner.invoke(entry_points.entry_point, arguments)
assert result.exit_code == 1, arguments
# Cleanup:
if os.path.exists(output_dir) and os.path.isdir(output_dir):
shutil.rmtree(output_dir)
def test_decompile_error_no_arguments(self):
runner = CliRunner()
output_dir = mkdtemp(prefix='androguard_test_')
arguments = ['decompile', '-o', output_dir]
result = runner.invoke(entry_points.entry_point, arguments)
assert result.exit_code == 1, arguments
# Cleanup:
if os.path.exists(output_dir) and os.path.isdir(output_dir):
shutil.rmtree(output_dir)
def test_decompile_help(self):
runner = CliRunner()
result = runner.invoke(entry_points.entry_point,
['decompile', '--help'])
assert result.exit_code == 0
def test_sign_basic(self):
apk_path = resource_filename('androguard',
'../examples/dalvik/test/bin/'
'Test-debug.apk')
runner = CliRunner()
arguments = ['sign', apk_path]
result = runner.invoke(entry_points.entry_point, arguments)
assert result.exit_code == 0
def test_sign_help(self):
runner = CliRunner()
result = runner.invoke(entry_points.entry_point, ['sign', '--help'])
assert result.exit_code == 0
def test_gui_help(self):
runner = CliRunner()
result = runner.invoke(entry_points.entry_point,
['gui', '--help'])
assert result.exit_code == 0
def test_analyze_help(self):
runner = CliRunner()
result = runner.invoke(entry_points.entry_point,
['analyze', '--help'])
assert result.exit_code == 0
def test_androsign(self):
runner = CliRunner()
for apk in get_apks():
print("testing for {}".format(apk))
arguments = ['sign', apk]
result = runner.invoke(entry_points.entry_point, arguments)
assert result.exit_code == 0
def test_androaxml(self):
runner = CliRunner()
for apk in get_apks():
print("testing for {}".format(apk))
arguments = ['axml', apk]
result = runner.invoke(entry_points.entry_point, arguments)
assert result.exit_code == 0
def test_androarsc(self):
runner = CliRunner()
# TODO could check here more stuff for example returned lists etc
for apk in get_apks():
print("testing for {}".format(apk))
arguments = ['arsc', apk]
result = runner.invoke(entry_points.entry_point, arguments)
assert result.exit_code == 0
arguments = ['arsc', "-t", "string", apk]
result = runner.invoke(entry_points.entry_point, arguments)
assert result.exit_code == 0
arguments = ['arsc', "--list-packages", apk]
result = runner.invoke(entry_points.entry_point, arguments)
assert result.exit_code == 0
arguments = ['arsc', "--list-types", apk]
result = runner.invoke(entry_points.entry_point, arguments)
assert result.exit_code == 0
arguments = ['arsc', "--list-locales", apk]
result = runner.invoke(entry_points.entry_point, arguments)
assert result.exit_code == 0

View File

@ -1,4 +1,4 @@
from androguard.core.bytecodes.dvm_types import TypeMapItem
from androguard.core.bytecodes.dex_types import TypeMapItem
import unittest

View File

@ -1,12 +1,13 @@
from struct import pack, unpack
import sys
from binascii import hexlify
# This is a very simple DEX parser, to get the bytecodes for each method
# Output format will be:
# <class name> <method name> <bytecode as hex string>
import sys
sys.path.append('.')
from androguard.core.bytecodes.dvm import readuleb128, readsleb128, DalvikPacker
from androguard.core.dex import readuleb128, readsleb128, DalvikPacker
def read_null_terminated(f):

View File

@ -1,14 +1,17 @@
import unittest
from androguard.core.bytecodes import dvm
from androguard.core.analysis import analysis
import sys
sys.path.append('.')
from androguard.core import dex
from androguard.core.analysis import analysis
class RenameTest(unittest.TestCase):
def __init__(self, *args, **kwargs):
super(RenameTest, self).__init__(*args, **kwargs)
with open("examples/android/TestsAndroguard/bin/classes.dex",
"rb") as fd:
self.d = dvm.DalvikVMFormat(fd.read())
self.d = dex.DEX(fd.read())
self.dx = analysis.Analysis(self.d)
self.d.set_vmanalysis(self.dx)

View File

@ -82,7 +82,7 @@ class SessionTest(unittest.TestCase):
def testSessionClassesDex(self):
"""Test if all classes.dex are added into the session"""
from androguard.core.bytecodes.dvm import DalvikVMFormat
from androguard.core.bytecodes.dvm import DEX
from androguard.core.analysis.analysis import Analysis
s = session.Session()
@ -97,7 +97,7 @@ class SessionTest(unittest.TestCase):
self.assertEqual(len(dexfiles), 1)
df = dexfiles[0]
self.assertEqual(df[0], "0e1aa10d9ecfb1cb3781a3f885195f61505e0a4557026a07bd07bf5bd876c951")
self.assertIsInstance(df[1], DalvikVMFormat)
self.assertIsInstance(df[1], DEX)
self.assertIsInstance(df[2], Analysis)
self.assertIn(df[1], df[2].vms)

View File

@ -7,7 +7,6 @@ sys.path.append(".")
from androguard.core import mutf8
from androguard.core import dex
from androguard.core import analysis
class StringTest(unittest.TestCase):

View File

@ -1,23 +1,5 @@
#!/usr/bin/env python3
# This file is part of Androguard.
#
# Copyright (C) 2010, Anthony Desnos <desnos at t0t0.fr>
# All rights reserved.
#
# Androguard is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Androguard is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Androguard. If not, see <http://www.gnu.org/licenses/>.
import sys
import unittest
from struct import pack, unpack, calcsize

8
web.py
View File

@ -1,8 +0,0 @@
from fastapi import FastAPI
from androguard.core import apk
app = FastAPI()
@app.get("/")
async def root():
return {"message": "Hello World"}