mirror of
https://github.com/androguard/androguard.git
synced 2024-11-26 22:40:33 +00:00
Clean up and add some ui for dynamic analysis
This commit is contained in:
parent
1e89ba82e1
commit
ca1c12b94f
9
.gitignore
vendored
9
.gitignore
vendored
@ -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/
|
||||
|
@ -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");
|
||||
|
@ -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
|
||||
"""
|
||||
|
@ -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)
|
||||
|
@ -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()
|
@ -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'])
|
||||
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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": []}
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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" />
|
||||
|
@ -1 +1,3 @@
|
||||
import sys
|
||||
|
||||
sys.setrecursionlimit(5000)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -1 +0,0 @@
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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()
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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:
|
@ -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,
|
@ -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())
|
@ -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)
|
@ -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.
|
||||
|
@ -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)
|
@ -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
@ -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()) {
|
||||
|
254
androguard/pentest/modules/binder/intercept_binder.js
Normal file
254
androguard/pentest/modules/binder/intercept_binder.js
Normal 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);
|
||||
}
|
||||
}
|
||||
})
|
@ -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");
|
||||
|
||||
|
@ -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");
|
||||
|
@ -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");
|
||||
|
@ -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) {
|
||||
}
|
||||
|
@ -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") {
|
||||
|
@ -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');
|
||||
|
@ -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();
|
135
androguard/pentest/modules/intents/intents_creation.js
Normal file
135
androguard/pentest/modules/intents/intents_creation.js
Normal 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);
|
||||
}
|
||||
}
|
37
androguard/pentest/modules/intents/pending_intents.js
Normal file
37
androguard/pentest/modules/intents/pending_intents.js
Normal 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);
|
||||
}
|
@ -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);
|
||||
}
|
@ -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
239
androguard/ui/__init__.py
Normal 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)
|
92
androguard/ui/data_types.py
Normal file
92
androguard/ui/data_types.py
Normal 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
62
androguard/ui/filter.py
Normal 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
159
androguard/ui/selection.py
Normal 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
621
androguard/ui/table.py
Normal 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
3
androguard/ui/util.py
Normal 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)
|
61
androguard/ui/widget/details.py
Normal file
61
androguard/ui/widget/details.py
Normal 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
|
78
androguard/ui/widget/filters.py
Normal file
78
androguard/ui/widget/filters.py
Normal 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
|
121
androguard/ui/widget/frame.py
Normal file
121
androguard/ui/widget/frame.py
Normal 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
|
38
androguard/ui/widget/help.py
Normal file
38
androguard/ui/widget/help.py
Normal 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
|
29
androguard/ui/widget/toolbar.py
Normal file
29
androguard/ui/widget/toolbar.py
Normal 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
|
145
androguard/ui/widget/transactions.py
Normal file
145
androguard/ui/widget/transactions.py
Normal 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
61
cli.py
@ -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
101
main.py
@ -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)
|
||||
|
@ -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
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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())
|
||||
|
@ -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")
|
||||
|
@ -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):
|
||||
|
@ -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):
|
||||
|
@ -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):
|
@ -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):
|
@ -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):
|
@ -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()
|
@ -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))
|
||||
|
||||
|
||||
|
@ -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
|
@ -1,4 +1,4 @@
|
||||
from androguard.core.bytecodes.dvm_types import TypeMapItem
|
||||
from androguard.core.bytecodes.dex_types import TypeMapItem
|
||||
|
||||
import unittest
|
||||
|
||||
|
@ -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):
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user