mirror of
https://github.com/androguard/androguard.git
synced 2024-10-07 18:23:25 +00:00
Merge branch 'master' of https://github.com/androguard/androguard into opcodemadness
This commit is contained in:
commit
6f2cf4981b
@ -1,5 +1,6 @@
|
||||
import re
|
||||
import collections
|
||||
from operator import itemgetter
|
||||
import time
|
||||
import warnings
|
||||
from androguard.core.androconf import is_ascii_problem, load_api_specific_resource_module
|
||||
@ -469,7 +470,6 @@ class MethodAnalysis:
|
||||
|
||||
|
||||
class StringAnalysis:
|
||||
def __init__(self, value):
|
||||
"""
|
||||
StringAnalysis contains the XREFs of a string.
|
||||
|
||||
@ -478,14 +478,26 @@ class StringAnalysis:
|
||||
|
||||
This Array stores the information in which method the String is used.
|
||||
"""
|
||||
def __init__(self, value):
|
||||
"""
|
||||
|
||||
:param str value: the original string value
|
||||
"""
|
||||
self.value = value
|
||||
self.orig_value = value
|
||||
self.xreffrom = set()
|
||||
|
||||
def AddXrefFrom(self, classobj, methodobj):
|
||||
self.xreffrom.add((classobj, methodobj))
|
||||
def add_xref_from(self, classobj, methodobj, off):
|
||||
"""
|
||||
Adds a xref from the given method to this string
|
||||
|
||||
def get_xref_from(self):
|
||||
:param ClassAnalysis classobj:
|
||||
:param androguard.core.bytecodes.dvm.EncodedMethod methodobj:
|
||||
:param int off: offset in the bytecode of the call
|
||||
"""
|
||||
self.xreffrom.add((classobj, methodobj, off))
|
||||
|
||||
def get_xref_from(self, withoffset=False):
|
||||
"""
|
||||
Returns a list of xrefs accessing the String.
|
||||
|
||||
@ -493,7 +505,9 @@ class StringAnalysis:
|
||||
where the class is represented as a :class:`ClassAnalysis`,
|
||||
while the method is a :class:`androguard.core.bytecodes.dvm.EncodedMethod`.
|
||||
"""
|
||||
if withoffset:
|
||||
return self.xreffrom
|
||||
return set(map(itemgetter(slice(0, 2)), self.xreffrom))
|
||||
|
||||
def set_value(self, value):
|
||||
"""
|
||||
@ -584,7 +598,7 @@ class MethodClassAnalysis:
|
||||
"""Returns classname + name + descriptor, separated by spaces (no access flags)"""
|
||||
return self.method.full_name
|
||||
|
||||
def AddXrefTo(self, classobj, methodobj, offset):
|
||||
def add_xref_to(self, classobj, methodobj, offset):
|
||||
"""
|
||||
Add a crossreference to another method
|
||||
(this method calls another method)
|
||||
@ -595,7 +609,7 @@ class MethodClassAnalysis:
|
||||
"""
|
||||
self.xrefto.add((classobj, methodobj, offset))
|
||||
|
||||
def AddXrefFrom(self, classobj, methodobj, offset):
|
||||
def add_xref_from(self, classobj, methodobj, offset):
|
||||
"""
|
||||
Add a crossrefernece from another method
|
||||
(this method is called by another method)
|
||||
@ -711,45 +725,65 @@ class FieldClassAnalysis:
|
||||
def name(self):
|
||||
return self.field.get_name()
|
||||
|
||||
def AddXrefRead(self, classobj, methodobj):
|
||||
self.xrefread.add((classobj, methodobj))
|
||||
def add_xref_read(self, classobj, methodobj, offset):
|
||||
"""
|
||||
:param ClassAnalysis classobj:
|
||||
:param androguard.core.bytecodes.dvm.EncodedMethod methodobj:
|
||||
:param int offset: offset in the bytecode
|
||||
"""
|
||||
self.xrefread.add((classobj, methodobj, offset))
|
||||
|
||||
def AddXrefWrite(self, classobj, methodobj):
|
||||
self.xrefwrite.add((classobj, methodobj))
|
||||
def add_xref_write(self, classobj, methodobj, offset):
|
||||
"""
|
||||
:param ClassAnalysis classobj:
|
||||
:param androguard.core.bytecodes.dvm.EncodedMethod methodobj:
|
||||
:param int offset: offset in the bytecode
|
||||
"""
|
||||
self.xrefwrite.add((classobj, methodobj, offset))
|
||||
|
||||
def get_xref_read(self):
|
||||
def get_xref_read(self, withoffset=False):
|
||||
"""
|
||||
Returns a list of xrefs where the field is read.
|
||||
|
||||
The list contains tuples of the originating class and methods,
|
||||
where the class is represented as a :class:`ClassAnalysis`,
|
||||
while the method is a :class:`androguard.core.bytecodes.dvm.EncodedMethod`.
|
||||
"""
|
||||
return self.xrefread
|
||||
|
||||
def get_xref_write(self):
|
||||
:param bool withoffset: return the xrefs including the offset
|
||||
"""
|
||||
if withoffset:
|
||||
return self.xrefread
|
||||
# Legacy option, might be removed in the future
|
||||
return set(map(itemgetter(slice(0, 2)), self.xrefread))
|
||||
|
||||
def get_xref_write(self, withoffset=False):
|
||||
"""
|
||||
Returns a list of xrefs where the field is written to.
|
||||
|
||||
The list contains tuples of the originating class and methods,
|
||||
where the class is represented as a :class:`ClassAnalysis`,
|
||||
while the method is a :class:`androguard.core.bytecodes.dvm.EncodedMethod`.
|
||||
|
||||
:param bool withoffset: return the xrefs including the offset
|
||||
"""
|
||||
if withoffset:
|
||||
return self.xrefwrite
|
||||
# Legacy option, might be removed in the future
|
||||
return set(map(itemgetter(slice(0, 2)), self.xrefwrite))
|
||||
|
||||
def get_field(self):
|
||||
return self.field
|
||||
|
||||
def __str__(self):
|
||||
data = "XREFRead for %s\n" % self.field
|
||||
for ref_class, ref_method in self.xrefread:
|
||||
for ref_class, ref_method, off in self.xrefread:
|
||||
data += "in\n"
|
||||
data += "{}:{}\n".format(ref_class.get_vm_class().get_name(), ref_method)
|
||||
data += "{}:{} @{}\n".format(ref_class.get_vm_class().get_name(), ref_method, off)
|
||||
|
||||
data += "XREFWrite for %s\n" % self.field
|
||||
for ref_class, ref_method in self.xrefwrite:
|
||||
for ref_class, ref_method, off in self.xrefwrite:
|
||||
data += "in\n"
|
||||
data += "{}:{}\n".format(ref_class.get_vm_class().get_name(), ref_method)
|
||||
data += "{}:{} @{}\n".format(ref_class.get_vm_class().get_name(), ref_method, off)
|
||||
|
||||
return data
|
||||
|
||||
@ -997,41 +1031,43 @@ class ClassAnalysis:
|
||||
self._inherits_methods[key] = ExternalMethod(self.orig_class.get_name(), name, descriptor)
|
||||
return self._inherits_methods[key]
|
||||
|
||||
def AddFXrefRead(self, method, classobj, field):
|
||||
def add_field_xref_read(self, method, classobj, field, off):
|
||||
"""
|
||||
Add a Field Read to this class
|
||||
|
||||
:param method:
|
||||
:param classobj:
|
||||
:param field:
|
||||
:param androguard.core.bytecodes.dvm.EncodedMethod method:
|
||||
:param ClassAnalysis classobj:
|
||||
:param str field:
|
||||
:param int off:
|
||||
:return:
|
||||
"""
|
||||
if field not in self._fields:
|
||||
self._fields[field] = FieldClassAnalysis(field)
|
||||
self._fields[field].AddXrefRead(classobj, method)
|
||||
self._fields[field].add_xref_read(classobj, method, off)
|
||||
|
||||
def AddFXrefWrite(self, method, classobj, field):
|
||||
def add_field_xref_write(self, method, classobj, field, off):
|
||||
"""
|
||||
Add a Field Write to this class
|
||||
Add a Field Write to this class in a given method
|
||||
|
||||
:param method:
|
||||
:param classobj:
|
||||
:param field:
|
||||
:param androguard.core.bytecodes.dvm.EncodedMethod method:
|
||||
:param ClassAnalysis classobj:
|
||||
:param str field:
|
||||
:param int off:
|
||||
:return:
|
||||
"""
|
||||
if field not in self._fields:
|
||||
self._fields[field] = FieldClassAnalysis(field)
|
||||
self._fields[field].AddXrefWrite(classobj, method)
|
||||
self._fields[field].add_xref_write(classobj, method, off)
|
||||
|
||||
def AddMXrefTo(self, method1, classobj, method2, offset):
|
||||
def add_method_xref_to(self, method1, classobj, method2, offset):
|
||||
if method1 not in self._methods:
|
||||
self._methods[method1] = MethodClassAnalysis(method1)
|
||||
self._methods[method1].AddXrefTo(classobj, method2, offset)
|
||||
self._methods[method1].add_xref_to(classobj, method2, offset)
|
||||
|
||||
def AddMXrefFrom(self, method1, classobj, method2, offset):
|
||||
def add_method_xref_from(self, method1, classobj, method2, offset):
|
||||
if method1 not in self._methods:
|
||||
self._methods[method1] = MethodClassAnalysis(method1)
|
||||
self._methods[method1].AddXrefFrom(classobj, method2, offset)
|
||||
self._methods[method1].add_xref_from(classobj, method2, offset)
|
||||
|
||||
def AddXrefTo(self, ref_kind, classobj, methodobj, offset):
|
||||
"""
|
||||
@ -1216,6 +1252,9 @@ class Analysis:
|
||||
for c in self._get_all_classes():
|
||||
self._create_xref(c)
|
||||
|
||||
# TODO: After we collected all the information, we should add field and
|
||||
# string xrefs to each MethodClassAnalysis
|
||||
|
||||
log.info("End of creating cross references (XREF) "
|
||||
"run time: {:0d}min {:02d}s".format(*divmod(int(time.time() - tic), 60)))
|
||||
|
||||
@ -1283,10 +1322,13 @@ class Analysis:
|
||||
class_info = method_info[0].lstrip(b'[')
|
||||
if class_info[0] != b'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
|
||||
|
||||
method_item = None
|
||||
# TODO: should create get_method_descriptor inside Analysis
|
||||
# TODO: should create get_method_descriptor inside Analysis,
|
||||
# otherwise we need to search in all DalvikVMFormat objects
|
||||
# for the corrent method
|
||||
for vm in self.vms:
|
||||
method_item = vm.get_method_descriptor(class_info, method_info[1], mutf8.MUTF8String.join(method_info[2]))
|
||||
if method_item:
|
||||
@ -1300,8 +1342,8 @@ class Analysis:
|
||||
self.classes[class_info] = ClassAnalysis(ExternalClass(class_info))
|
||||
method_item = self.classes[class_info].get_fake_method(method_info[1], method_info[2])
|
||||
|
||||
self.classes[cur_cls_name].AddMXrefTo(current_method, self.classes[class_info], method_item, off)
|
||||
self.classes[class_info].AddMXrefFrom(method_item, self.classes[cur_cls_name], current_method, off)
|
||||
self.classes[cur_cls_name].add_method_xref_to(current_method, self.classes[class_info], method_item, off)
|
||||
self.classes[class_info].add_method_xref_from(method_item, self.classes[cur_cls_name], current_method, off)
|
||||
|
||||
# Internal xref related to class manipulation
|
||||
if class_info in self.classes and class_info != cur_cls_name:
|
||||
@ -1314,8 +1356,7 @@ class Analysis:
|
||||
if string_value not in self.strings:
|
||||
self.strings[string_value] = StringAnalysis(string_value)
|
||||
|
||||
# TODO: The bytecode offset is stored for classes but not here?
|
||||
self.strings[string_value].AddXrefFrom(self.classes[cur_cls_name], current_method)
|
||||
self.strings[string_value].add_xref_from(self.classes[cur_cls_name], current_method, off)
|
||||
|
||||
# TODO maybe we should add a step 3a) here and check for all const fields. You can then xref for integers etc!
|
||||
# But: This does not work, as const fields are usually optimized internally to const calls...
|
||||
@ -1325,16 +1366,15 @@ class Analysis:
|
||||
idx_field = instruction.get_ref_kind()
|
||||
field_info = instruction.cm.vm.get_cm_field(idx_field)
|
||||
field_item = instruction.cm.vm.get_field_descriptor(field_info[0], field_info[2], field_info[1])
|
||||
# TODO: The bytecode offset is stored for classes but not here?
|
||||
if not field_item:
|
||||
continue
|
||||
|
||||
if (0x52 <= op_value <= 0x58) or (0x60 <= op_value <= 0x66):
|
||||
# read access to a field
|
||||
self.classes[cur_cls_name].AddFXrefRead(current_method, self.classes[cur_cls_name], field_item)
|
||||
self.classes[cur_cls_name].add_field_xref_read(current_method, self.classes[cur_cls_name], field_item, off)
|
||||
else:
|
||||
# write access to a field
|
||||
self.classes[cur_cls_name].AddFXrefWrite(current_method, self.classes[cur_cls_name], field_item)
|
||||
self.classes[cur_cls_name].add_field_xref_write(current_method, self.classes[cur_cls_name], field_item, off)
|
||||
|
||||
def get_method(self, method):
|
||||
"""
|
||||
|
@ -1,12 +1,14 @@
|
||||
import hashlib
|
||||
from collections import defaultdict
|
||||
from xml.sax.saxutils import escape
|
||||
from struct import unpack, pack
|
||||
from struct import pack
|
||||
import textwrap
|
||||
|
||||
import json
|
||||
from androguard.core.androconf import CONF, enable_colors, remove_colors, save_colors, color_range
|
||||
import logging
|
||||
|
||||
from androguard.core.androconf import CONF, enable_colors, remove_colors, save_colors, color_range
|
||||
from androguard.core.bytecodes import dvm_types
|
||||
|
||||
log = logging.getLogger("androguard.bytecode")
|
||||
|
||||
|
||||
@ -20,12 +22,6 @@ def enable_print_colors(colors):
|
||||
enable_colors(colors)
|
||||
|
||||
|
||||
# Handle exit message
|
||||
def Exit(msg):
|
||||
log.warning("Error : " + msg)
|
||||
raise Exception("oops")
|
||||
|
||||
|
||||
def _PrintBanner():
|
||||
print_fct = CONF["PRINT_FCT"]
|
||||
print_fct("*" * 75 + "\n")
|
||||
@ -56,10 +52,6 @@ def _Print(name, arg):
|
||||
buff += "0x%x" % arg
|
||||
elif type(arg).__name__ == 'str':
|
||||
buff += "%s" % arg
|
||||
elif isinstance(arg, SV):
|
||||
buff += "0x%x" % arg.get_value()
|
||||
elif isinstance(arg, SVs):
|
||||
buff += arg.get_value().__str__()
|
||||
|
||||
print(buff)
|
||||
|
||||
@ -95,9 +87,47 @@ def _PrintDefault(msg):
|
||||
print_fct(msg)
|
||||
|
||||
|
||||
def _colorize_operands(operands, colors):
|
||||
"""
|
||||
Return strings with color coded operands
|
||||
"""
|
||||
for operand in operands:
|
||||
if operand[0] == dvm_types.OPERAND_REGISTER:
|
||||
yield "%sv%d%s" % (colors["registers"], operand[1],
|
||||
colors["normal"])
|
||||
|
||||
elif operand[0] == dvm_types.OPERAND_LITERAL:
|
||||
yield "%s%d%s" % (colors["literal"], operand[1],
|
||||
colors["normal"])
|
||||
|
||||
elif operand[0] == dvm_types.OPERAND_RAW:
|
||||
yield "{}{}{}".format(colors["raw"], operand[1], colors["normal"])
|
||||
|
||||
elif operand[0] == dvm_types.OPERAND_OFFSET:
|
||||
yield "%s%d%s" % (colors["offset"], operand[1], colors["normal"]
|
||||
)
|
||||
|
||||
elif operand[0] & dvm_types.OPERAND_KIND:
|
||||
if operand[0] == (dvm_types.OPERAND_KIND + dvm_types.KIND_STRING):
|
||||
yield "{}{}{}".format(colors["string"], operand[2],
|
||||
colors["normal"])
|
||||
elif operand[0] == (dvm_types.OPERAND_KIND + dvm_types.KIND_METH):
|
||||
yield "{}{}{}".format(colors["meth"], operand[2],
|
||||
colors["normal"])
|
||||
elif operand[0] == (dvm_types.OPERAND_KIND + dvm_types.KIND_FIELD):
|
||||
yield "{}{}{}".format(colors["field"], operand[2],
|
||||
colors["normal"])
|
||||
elif operand[0] == (dvm_types.OPERAND_KIND + dvm_types.KIND_TYPE):
|
||||
yield "{}{}{}".format(colors["type"], operand[2],
|
||||
colors["normal"])
|
||||
else:
|
||||
yield "%s" % repr(operands[2])
|
||||
else:
|
||||
yield "%s" % repr(operands[1])
|
||||
|
||||
|
||||
def PrettyShow(m_a, basic_blocks, notes={}):
|
||||
idx = 0
|
||||
nb = 0
|
||||
|
||||
offset_color = CONF["COLORS"]["OFFSET"]
|
||||
offset_addr_color = CONF["COLORS"]["OFFSET_ADDR"]
|
||||
@ -112,7 +142,7 @@ def PrettyShow(m_a, basic_blocks, notes={}):
|
||||
|
||||
colors = CONF["COLORS"]["OUTPUT"]
|
||||
|
||||
for i in basic_blocks:
|
||||
for nb, i in enumerate(basic_blocks):
|
||||
print_fct("{}{}{} : \n".format(bb_color, i.get_name(), normal_color))
|
||||
instructions = list(i.get_instructions())
|
||||
for ins in instructions:
|
||||
@ -129,7 +159,7 @@ def PrettyShow(m_a, basic_blocks, notes={}):
|
||||
operands = ins.get_operands()
|
||||
print_fct(
|
||||
"%s" %
|
||||
", ".join(m_a.get_vm().colorize_operands(operands, colors)))
|
||||
", ".join(_colorize_operands(operands, colors)))
|
||||
|
||||
op_value = ins.get_op_value()
|
||||
if ins == instructions[-1] and i.childs:
|
||||
@ -158,7 +188,6 @@ def PrettyShow(m_a, basic_blocks, notes={}):
|
||||
normal_color)
|
||||
|
||||
idx += ins.get_length()
|
||||
nb += 1
|
||||
|
||||
print_fct("\n")
|
||||
|
||||
@ -170,6 +199,49 @@ def PrettyShow(m_a, basic_blocks, notes={}):
|
||||
print_fct("\n")
|
||||
|
||||
|
||||
def _get_operand_html(operand, registers_colors, colors):
|
||||
"""
|
||||
Return a HTML representation of the operand.
|
||||
The HTML should be compatible with pydot/graphviz to be used
|
||||
inside a node label.
|
||||
|
||||
This is solely used in :func:`~androguard.core.bytecodes.method2dot`
|
||||
|
||||
:param operand: tuple containing the operand type and operands
|
||||
:param dict register_colors: key: register number, value: register color
|
||||
:param dict colors: dictionary containing the register colors
|
||||
:returns: HTML code of the operands
|
||||
"""
|
||||
if operand[0] == dvm_types.OPERAND_REGISTER:
|
||||
return '<FONT color="{}">v{}</FONT>'.format(registers_colors[operand[1]], operand[1])
|
||||
|
||||
if operand[0] == dvm_types.OPERAND_LITERAL:
|
||||
return '<FONT color="{}">0x{:x}</FONT>'.format(colors["literal"], operand[1])
|
||||
|
||||
if operand[0] == dvm_types.OPERAND_RAW:
|
||||
wrapped_adjust = '<br />'.join(escape(repr(i)[1:-1]) for i in textwrap.wrap(operand[1], 64))
|
||||
return '<FONT color="{}">{}</FONT>'.format(colors["raw"], wrapped_adjust)
|
||||
|
||||
if operand[0] == dvm_types.OPERAND_OFFSET:
|
||||
return '<FONT FACE="Times-Italic" color="{}">@0x{:x}</FONT>'.format(colors["offset"], operand[1])
|
||||
|
||||
if operand[0] & dvm_types.OPERAND_KIND:
|
||||
if operand[0] == (dvm_types.OPERAND_KIND + dvm_types.KIND_STRING):
|
||||
wrapped_adjust = "" \<br />"".join(map(escape, textwrap.wrap(operand[2], 64)))
|
||||
return '<FONT color="{}">"{}"</FONT>'.format(colors["string"], wrapped_adjust)
|
||||
|
||||
if operand[0] == (dvm_types.OPERAND_KIND + dvm_types.KIND_METH):
|
||||
return '<FONT color="{}">{}</FONT>'.format(colors["method"], escape(operand[2]))
|
||||
if operand[0] == (dvm_types.OPERAND_KIND + dvm_types.KIND_FIELD):
|
||||
return '<FONT color="{}">{}</FONT>'.format(colors["field"], escape(operand[2]))
|
||||
if operand[0] == (dvm_types.OPERAND_KIND + dvm_types.KIND_TYPE):
|
||||
return '<FONT color="{}">{}</FONT>'.format(colors["type"], escape(operand[2]))
|
||||
|
||||
return escape(str(operand[2]))
|
||||
|
||||
return escape(str(operand[1]))
|
||||
|
||||
|
||||
def method2dot(mx, colors=None):
|
||||
"""
|
||||
Export analysis method to dot format
|
||||
@ -180,6 +252,8 @@ def method2dot(mx, colors=None):
|
||||
:returns: a string which contains the dot graph
|
||||
"""
|
||||
|
||||
font_face = "monospace"
|
||||
|
||||
if not colors:
|
||||
colors = {
|
||||
"true_branch": "green",
|
||||
@ -202,30 +276,44 @@ def method2dot(mx, colors=None):
|
||||
"registers_range": ("#999933", "#6666FF")
|
||||
}
|
||||
|
||||
node_tpl = "\nstruct_%s [label=<\n<TABLE BORDER=\"0\" CELLBORDER=\"0\" CELLSPACING=\"3\">\n%s</TABLE>>];\n"
|
||||
label_tpl = "<TR><TD ALIGN=\"LEFT\" BGCOLOR=\"%s\"> <FONT FACE=\"Times-Bold\" color=\"%s\">%x</FONT> </TD><TD ALIGN=\"LEFT\" BGCOLOR=\"%s\"> <FONT FACE=\"Times-Bold\" color=\"%s\">%s </FONT> %s </TD></TR>\n"
|
||||
node_tpl = """
|
||||
struct_%s [label=<
|
||||
<TABLE BORDER="0" CELLBORDER="0" CELLSPACING="3">
|
||||
%s
|
||||
</TABLE>
|
||||
>];
|
||||
"""
|
||||
label_tpl = """
|
||||
<TR>
|
||||
<TD ALIGN="LEFT" BGCOLOR="%s">
|
||||
<FONT FACE="{font_face}" color="%s">%04x</FONT>
|
||||
</TD>
|
||||
<TD ALIGN="LEFT" BGCOLOR="%s">
|
||||
<FONT FACE="{font_face}" color="%s">%s</FONT> %s
|
||||
</TD>
|
||||
</TR>
|
||||
""".format(font_face=font_face)
|
||||
|
||||
link_tpl = "<TR><TD PORT=\"%s\"></TD></TR>\n"
|
||||
|
||||
edges_html = ""
|
||||
blocks_html = ""
|
||||
|
||||
method = mx.get_method()
|
||||
|
||||
sha256 = hashlib.sha256(bytearray("{}{}{}".format(
|
||||
mx.get_method().get_class_name(), mx.get_method().get_name(),
|
||||
mx.get_method().get_descriptor()), "UTF-8")).hexdigest()
|
||||
|
||||
registers = {}
|
||||
# Collect all used Registers and how often the register is used
|
||||
registers = defaultdict(int)
|
||||
if method.get_code():
|
||||
for DVMBasicMethodBlock in mx.basic_blocks.gets():
|
||||
for DVMBasicMethodBlockInstruction in DVMBasicMethodBlock.get_instructions():
|
||||
operands = DVMBasicMethodBlockInstruction.get_operands(0)
|
||||
for register in operands:
|
||||
if register[0] == 0:
|
||||
if register[1] not in registers:
|
||||
registers[register[1]] = 0
|
||||
registers[register[1]] += 1
|
||||
# for i in range(method.get_code().get_registers_size()):
|
||||
# registers[i] = 0
|
||||
for basic_block in mx.basic_blocks.gets():
|
||||
for ins in basic_block.get_instructions():
|
||||
for operand in ins.get_operands(0):
|
||||
if operand[0] == dvm_types.OPERAND_REGISTER:
|
||||
# FIXME: actually this counter is never used
|
||||
registers[operand[1]] += 1
|
||||
|
||||
if registers:
|
||||
registers_colors = color_range(colors["registers_range"][0],
|
||||
@ -243,8 +331,7 @@ def method2dot(mx, colors=None):
|
||||
content = link_tpl % 'header'
|
||||
|
||||
for DVMBasicMethodBlockInstruction in DVMBasicMethodBlock.get_instructions():
|
||||
if DVMBasicMethodBlockInstruction.get_op_value(
|
||||
) == 0x2b or DVMBasicMethodBlockInstruction.get_op_value() == 0x2c:
|
||||
if DVMBasicMethodBlockInstruction.get_op_value() in (0x2b, 0x2c):
|
||||
new_links.append((DVMBasicMethodBlock, ins_idx,
|
||||
DVMBasicMethodBlockInstruction.get_ref_off() * 2 + ins_idx))
|
||||
elif DVMBasicMethodBlockInstruction.get_op_value() == 0x26:
|
||||
@ -252,11 +339,9 @@ def method2dot(mx, colors=None):
|
||||
DVMBasicMethodBlockInstruction.get_ref_off() * 2 + ins_idx))
|
||||
|
||||
operands = DVMBasicMethodBlockInstruction.get_operands(ins_idx)
|
||||
output = ", ".join(mx.get_vm().get_operand_html(
|
||||
i, registers, colors, escape, textwrap.wrap) for i in operands)
|
||||
output = ", ".join(_get_operand_html(i, registers, colors) for i in operands)
|
||||
|
||||
formatted_operands = DVMBasicMethodBlockInstruction.get_formatted_operands(
|
||||
)
|
||||
formatted_operands = DVMBasicMethodBlockInstruction.get_formatted_operands()
|
||||
if formatted_operands:
|
||||
output += " ; %s" % str(formatted_operands)
|
||||
|
||||
@ -285,13 +370,10 @@ def method2dot(mx, colors=None):
|
||||
val = colors["jump_branch"]
|
||||
|
||||
values = None
|
||||
if (last_instru.get_op_value() == 0x2b or
|
||||
last_instru.get_op_value() == 0x2c
|
||||
) and len(DVMBasicMethodBlock.childs) > 1:
|
||||
if last_instru.get_op_value() in (0x2b, 0x2c) and len(DVMBasicMethodBlock.childs) > 1:
|
||||
val = colors["default_branch"]
|
||||
values = ["default"]
|
||||
values.extend(DVMBasicMethodBlock.get_special_ins(
|
||||
ins_idx - last_instru.get_length()).get_values())
|
||||
values.extend(DVMBasicMethodBlock.get_special_ins(ins_idx - last_instru.get_length()).get_values())
|
||||
|
||||
# updating dot edges
|
||||
for DVMBasicMethodBlockChild in DVMBasicMethodBlock.childs:
|
||||
@ -300,10 +382,9 @@ def method2dot(mx, colors=None):
|
||||
if values:
|
||||
label_edge = values.pop(0)
|
||||
|
||||
child_id = hashlib.md5(
|
||||
bytearray(sha256 + str(DVMBasicMethodBlockChild[-1].get_name()), "UTF-8")).hexdigest()
|
||||
edges_html += "struct_{}:tail -> struct_{}:header [color=\"{}\", label=\"{}\"];\n".format(
|
||||
block_id, child_id, val, label_edge)
|
||||
child_id = hashlib.md5(bytearray(sha256 + str(DVMBasicMethodBlockChild[-1].get_name()), "UTF-8")).hexdigest()
|
||||
edges_html += "struct_{}:tail -> struct_{}:header [color=\"{}\", label=\"{}\"];\n".format(block_id, child_id, val, label_edge)
|
||||
|
||||
# color switch
|
||||
if val == colors["false_branch"]:
|
||||
val = colors["true_branch"]
|
||||
@ -315,8 +396,7 @@ def method2dot(mx, colors=None):
|
||||
for exception_elem in exception_analysis.exceptions:
|
||||
exception_block = exception_elem[-1]
|
||||
if exception_block:
|
||||
exception_id = hashlib.md5(
|
||||
bytearray(sha256 + exception_block.get_name(), "UTF-8")).hexdigest()
|
||||
exception_id = hashlib.md5(bytearray(sha256 + exception_block.get_name(), "UTF-8")).hexdigest()
|
||||
edges_html += "struct_{}:tail -> struct_{}:header [color=\"{}\", label=\"{}\"];\n".format(
|
||||
block_id, exception_id, "black", exception_elem[0])
|
||||
|
||||
@ -325,16 +405,13 @@ def method2dot(mx, colors=None):
|
||||
DVMBasicMethodBlockChild = mx.basic_blocks.get_basic_block(link[2])
|
||||
|
||||
if DVMBasicMethodBlockChild:
|
||||
block_id = hashlib.md5(bytearray(sha256 + DVMBasicMethodBlock.get_name(
|
||||
), "UTF-8")).hexdigest()
|
||||
child_id = hashlib.md5(bytearray(sha256 + DVMBasicMethodBlockChild.get_name(
|
||||
), "UTF-8")).hexdigest()
|
||||
block_id = hashlib.md5(bytearray(sha256 + DVMBasicMethodBlock.get_name(), "UTF-8")).hexdigest()
|
||||
child_id = hashlib.md5(bytearray(sha256 + DVMBasicMethodBlockChild.get_name(), "UTF-8")).hexdigest()
|
||||
|
||||
edges_html += "struct_{}:tail -> struct_{}:header [color=\"{}\", label=\"data(0x{:x}) to @0x{:x}\", style=\"dashed\"];\n".format(
|
||||
block_id, child_id, "yellow", link[1], link[2])
|
||||
|
||||
method_label = method.get_class_name() + "." + method.get_name(
|
||||
) + "->" + method.get_descriptor()
|
||||
method_label = method.get_class_name() + "." + method.get_name() + "->" + method.get_descriptor()
|
||||
|
||||
method_information = method.get_information()
|
||||
if method_information:
|
||||
@ -351,40 +428,77 @@ def method2dot(mx, colors=None):
|
||||
|
||||
def method2format(output, _format="png", mx=None, raw=None):
|
||||
"""
|
||||
Export method to a specific file format
|
||||
Export method structure as a graph to a specific file format using dot from the graphviz package.
|
||||
The result is written to the file specified via :code:`output`.
|
||||
|
||||
@param output : output filename
|
||||
@param _format : format type (png, jpg ...) (default : png)
|
||||
@param mx : specify the MethodAnalysis object
|
||||
@param raw : use directly a dot raw buffer if None
|
||||
There are two possibilites to give input for this method:
|
||||
|
||||
1) use :code:`raw` argument and pass a dictionary containing the keys
|
||||
:code:`name`, :code:`nodes` and :code:`edges`.
|
||||
This can be created using :func:`method2dot`.
|
||||
2) give a :class:`~androguard.core.analysis.analysis.MethodAnalysis`.
|
||||
|
||||
This function requires pydot!
|
||||
|
||||
There is a special format :code:`raw` which saves the dot buffer before it
|
||||
is handled by pydot.
|
||||
|
||||
:param str output: output filename
|
||||
:param str _format: format type (png, jpg ...). Can use all formats which are understood by pydot.
|
||||
:param androguard.core.analysis.analysis.MethodAnalysis mx: specify the MethodAnalysis object
|
||||
:param dict raw: use directly a dot raw buffer if None
|
||||
"""
|
||||
# pydot is optional!
|
||||
import pydot
|
||||
|
||||
buff = "digraph {\n"
|
||||
buff += "graph [rankdir=TB]\n"
|
||||
buff += "node [shape=plaintext]\n"
|
||||
|
||||
if raw:
|
||||
data = raw
|
||||
else:
|
||||
data = method2dot(mx)
|
||||
|
||||
# subgraphs cluster
|
||||
buff += "subgraph cluster_{} ".format(hashlib.md5(bytearray(output, "UTF-8")).hexdigest())
|
||||
buff += "{\n"
|
||||
buff += "label=\"{}\"\n".format(data['name'])
|
||||
buff += data['nodes']
|
||||
buff += "}\n"
|
||||
buff = """
|
||||
digraph {{
|
||||
graph [rankdir=TB]
|
||||
node [shape=plaintext]
|
||||
|
||||
# subgraphs edges
|
||||
buff += data['edges']
|
||||
buff += "}\n"
|
||||
subgraph cluster_{clustername}
|
||||
{{
|
||||
label="{classname}"
|
||||
{nodes}
|
||||
}}
|
||||
|
||||
{edges}
|
||||
}}
|
||||
""".format(clustername=hashlib.md5(output.encode("UTF-8")).hexdigest().decode('ascii'),
|
||||
classname=data['name'],
|
||||
nodes=data['nodes'],
|
||||
edges=data['edges'],
|
||||
)
|
||||
|
||||
# NOTE: In certain cases the graph_from_dot_data function might fail.
|
||||
# There is a bug in the code that certain html strings are interpreted as comment
|
||||
# and therefore the dot buffer which is passed to graphviz is invalid.
|
||||
# We can not really do anything here to prevent this (except for heavily
|
||||
# escaping and replacing all characters).
|
||||
# We hope, that this issue get's fixed in pydot, so we do not need to patch
|
||||
# stuff here.
|
||||
# In order to be able to debug the problems better, we will write the dot
|
||||
# data here if the format `raw` is requested, instead of creating the graph
|
||||
# and then writing the dot data.
|
||||
# If you have problems with certain data, export it as dot and then run
|
||||
# graphviz manually to see if the problem persists.
|
||||
if _format == "raw":
|
||||
with open(output, "w") as fp:
|
||||
fp.write(buff)
|
||||
else:
|
||||
d = pydot.graph_from_dot_data(buff)
|
||||
if d:
|
||||
if len(d) > 1:
|
||||
# Not sure what to do in this case?!
|
||||
log.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)
|
||||
break
|
||||
|
||||
|
||||
def method2png(output, mx, raw=False):
|
||||
@ -446,7 +560,6 @@ def vm2json(vm):
|
||||
|
||||
|
||||
class TmpBlock:
|
||||
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
|
||||
@ -600,24 +713,18 @@ def object_to_bytes(obj):
|
||||
"""
|
||||
if isinstance(obj, str):
|
||||
return bytearray(obj, "UTF-8")
|
||||
elif isinstance(obj, bool):
|
||||
if isinstance(obj, bool):
|
||||
return bytearray()
|
||||
elif isinstance(obj, int):
|
||||
if isinstance(obj, int):
|
||||
return pack("<L", obj)
|
||||
elif obj is None:
|
||||
if obj is None:
|
||||
return bytearray()
|
||||
elif isinstance(obj, bytearray):
|
||||
if isinstance(obj, bytearray):
|
||||
return obj
|
||||
else:
|
||||
|
||||
return obj.get_raw()
|
||||
|
||||
|
||||
class MethodBC:
|
||||
|
||||
def show(self, value):
|
||||
getattr(self, "show_" + value)()
|
||||
|
||||
|
||||
class BuffHandle:
|
||||
"""
|
||||
BuffHandle is a wrapper around bytes.
|
||||
|
@ -1,10 +1,3 @@
|
||||
from androguard.core import bytecode
|
||||
from androguard.core.bytecodes.apk import APK
|
||||
from androguard.core.androconf import CONF
|
||||
|
||||
from androguard.core import mutf8
|
||||
from androguard.core.bytecodes.dvm_types import TypeMapItem, ACCESS_FLAGS, TYPE_DESCRIPTOR
|
||||
|
||||
import sys
|
||||
import re
|
||||
import struct
|
||||
@ -17,6 +10,21 @@ import zlib
|
||||
import hashlib
|
||||
from enum import IntEnum
|
||||
|
||||
from androguard.core import bytecode
|
||||
from androguard.core.bytecodes.apk import APK
|
||||
from androguard.core.androconf import CONF
|
||||
|
||||
from androguard.core import mutf8
|
||||
from androguard.core.bytecodes.dvm_types import (
|
||||
TypeMapItem,
|
||||
ACCESS_FLAGS,
|
||||
TYPE_DESCRIPTOR,
|
||||
Kind,
|
||||
Operand,
|
||||
)
|
||||
|
||||
|
||||
|
||||
log = logging.getLogger("androguard.dvm")
|
||||
|
||||
# TODO: have some more generic magic...
|
||||
@ -426,14 +434,15 @@ def determineException(vm, m):
|
||||
(try_value.get_insn_count() * 2) - 1]
|
||||
|
||||
handler_catch = value[1]
|
||||
if handler_catch.get_size() <= 0:
|
||||
z.append(["Ljava/lang/Throwable;",
|
||||
handler_catch.get_catch_all_addr() * 2])
|
||||
|
||||
for handler in handler_catch.get_handlers():
|
||||
z.append([vm.get_cm_type(handler.get_type_idx()),
|
||||
handler.get_addr() * 2])
|
||||
|
||||
if handler_catch.get_size() <= 0:
|
||||
z.append(["Ljava/lang/Throwable;",
|
||||
handler_catch.get_catch_all_addr() * 2])
|
||||
|
||||
exceptions.append(z)
|
||||
|
||||
# print m.get_name(), exceptions
|
||||
@ -3969,52 +3978,6 @@ class EncodedCatchHandlerList:
|
||||
return length
|
||||
|
||||
|
||||
class Kind(IntEnum):
|
||||
"""
|
||||
This Enum is used to determine the kind of argument
|
||||
inside an Dalvik instruction.
|
||||
|
||||
It is used to reference the actual item instead of the refernece index
|
||||
from the :class:`ClassManager` when disassembling the bytecode.
|
||||
"""
|
||||
# Indicates a method reference
|
||||
METH = 0
|
||||
# Indicates that opcode argument is a string index
|
||||
STRING = 1
|
||||
# Indicates a field reference
|
||||
FIELD = 2
|
||||
# Indicates a type reference
|
||||
TYPE = 3
|
||||
# indicates a prototype reference
|
||||
PROTO = 9
|
||||
# indicates method reference and proto reference (invoke-polymorphic)
|
||||
METH_PROTO = 10
|
||||
# indicates call site item
|
||||
CALL_SITE = 11
|
||||
|
||||
# TODO: not very well documented
|
||||
VARIES = 4
|
||||
# inline lined stuff
|
||||
INLINE_METHOD = 5
|
||||
# static linked stuff
|
||||
VTABLE_OFFSET = 6
|
||||
FIELD_OFFSET = 7
|
||||
RAW_STRING = 8
|
||||
|
||||
|
||||
class Operand(IntEnum):
|
||||
"""
|
||||
Enumeration used for the operand type of opcodes
|
||||
"""
|
||||
REGISTER = 0
|
||||
LITERAL = 1
|
||||
RAW = 2
|
||||
OFFSET = 3
|
||||
# FIXME: KIND is used in combination with others, ie the Kind enum, therefore it is 0x100...
|
||||
# thus we could use an IntFlag here as well
|
||||
KIND = 0x100
|
||||
|
||||
|
||||
def get_kind(cm, kind, value):
|
||||
"""
|
||||
Return the value of the 'kind' argument
|
||||
@ -8315,91 +8278,6 @@ class DalvikVMFormat(bytecode.BuffHandle):
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
def colorize_operands(self, operands, colors):
|
||||
for operand in operands:
|
||||
if operand[0] == Operand.REGISTER:
|
||||
yield "%sv%d%s" % (colors["registers"], operand[1],
|
||||
colors["normal"])
|
||||
|
||||
elif operand[0] == Operand.LITERAL:
|
||||
yield "%s%d%s" % (colors["literal"], operand[1],
|
||||
colors["normal"])
|
||||
|
||||
elif operand[0] == Operand.RAW:
|
||||
yield "{}{}{}".format(colors["raw"], operand[1], colors["normal"])
|
||||
|
||||
elif operand[0] == Operand.OFFSET:
|
||||
yield "%s%d%s" % (colors["offset"], operand[1], colors["normal"]
|
||||
)
|
||||
|
||||
elif operand[0] & Operand.KIND:
|
||||
if operand[0] == (Operand.KIND + Kind.STRING):
|
||||
yield "{}{}{}".format(colors["string"], operand[2],
|
||||
colors["normal"])
|
||||
elif operand[0] == (Operand.KIND + Kind.METH):
|
||||
yield "{}{}{}".format(colors["meth"], operand[2],
|
||||
colors["normal"])
|
||||
elif operand[0] == (Operand.KIND + Kind.FIELD):
|
||||
yield "{}{}{}".format(colors["field"], operand[2],
|
||||
colors["normal"])
|
||||
elif operand[0] == (Operand.KIND + Kind.TYPE):
|
||||
yield "{}{}{}".format(colors["type"], operand[2],
|
||||
colors["normal"])
|
||||
else:
|
||||
yield "%s" % repr(operands[2])
|
||||
else:
|
||||
yield "%s" % repr(operands[1])
|
||||
|
||||
def get_operand_html(self, operand, registers_colors, colors, escape_fct,
|
||||
wrap_fct):
|
||||
if operand[0] == Operand.REGISTER:
|
||||
return "<FONT color=\"{}\">v{}</FONT>".format(
|
||||
registers_colors[operand[1]], operand[1])
|
||||
|
||||
elif operand[0] == Operand.LITERAL:
|
||||
return "<FONT color=\"{}\">0x{:x}</FONT>".format(colors["literal"],
|
||||
operand[1])
|
||||
|
||||
elif operand[0] == Operand.RAW:
|
||||
if len(operand[1]) > 32:
|
||||
wrapped = wrap_fct(operand[1], 32)
|
||||
wrapped_adjust = "<br/>" + "<br/>".join(
|
||||
escape_fct(repr(i)[1:-1]) for i in wrapped)
|
||||
return "<FONT color=\"{}\">{}</FONT>".format(colors["raw"],
|
||||
wrapped_adjust)
|
||||
|
||||
return "<FONT color=\"{}\">{}</FONT>".format(
|
||||
colors["raw"], escape_fct(repr(operand[1])[1:-1]))
|
||||
|
||||
elif operand[0] == Operand.OFFSET:
|
||||
return "<FONT FACE=\"Times-Italic\" color=\"{}\">0x{:x}</FONT>".format(
|
||||
colors["offset"], operand[1])
|
||||
|
||||
elif operand[0] & Operand.KIND:
|
||||
if operand[0] == (Operand.KIND + Kind.STRING):
|
||||
if len(operand[2]) > 32:
|
||||
wrapped = wrap_fct(operand[2], 32)
|
||||
wrapped_adjust = "<br/>" + "<br/>".join(escape_fct(i)
|
||||
for i in wrapped)
|
||||
return "<FONT color=\"{}\">{}</FONT>".format(colors["string"],
|
||||
wrapped_adjust)
|
||||
|
||||
return "<FONT color=\"{}\">{}</FONT>".format(colors["string"],
|
||||
escape_fct(operand[2]))
|
||||
elif operand[0] == (Operand.KIND + Kind.METH):
|
||||
return "<FONT color=\"{}\">{}</FONT>".format(colors["method"],
|
||||
escape_fct(operand[2]))
|
||||
elif operand[0] == (Operand.KIND + Kind.FIELD):
|
||||
return "<FONT color=\"{}\">{}</FONT>".format(colors["field"],
|
||||
escape_fct(operand[2]))
|
||||
elif operand[0] == (Operand.KIND + Kind.TYPE):
|
||||
return "<FONT color=\"{}\">{}</FONT>".format(colors["type"],
|
||||
escape_fct(operand[2]))
|
||||
|
||||
return escape_fct(str(operand[2]))
|
||||
|
||||
return escape_fct(str(operand[1]))
|
||||
|
||||
|
||||
class OdexHeaderItem:
|
||||
"""
|
||||
|
@ -3,6 +3,24 @@ from collections import OrderedDict
|
||||
|
||||
# This file contains dictionaries used in the Dalvik Format.
|
||||
|
||||
# Used to identify different types of operands
|
||||
KIND_METH = 0
|
||||
KIND_STRING = 1
|
||||
KIND_FIELD = 2
|
||||
KIND_TYPE = 3
|
||||
VARIES = 4
|
||||
INLINE_METHOD = 5
|
||||
VTABLE_OFFSET = 6
|
||||
FIELD_OFFSET = 7
|
||||
KIND_RAW_STRING = 8
|
||||
|
||||
OPERAND_REGISTER = 0
|
||||
OPERAND_LITERAL = 1
|
||||
OPERAND_RAW = 2
|
||||
OPERAND_OFFSET = 3
|
||||
OPERAND_KIND = 0x100
|
||||
|
||||
|
||||
# https://source.android.com/devices/tech/dalvik/dex-format#type-codes
|
||||
class TypeMapItem(IntEnum):
|
||||
HEADER_ITEM = 0x0
|
||||
|
@ -1,6 +1,6 @@
|
||||
from PyQt5 import QtGui, QtCore
|
||||
|
||||
from androguard.core.bytecodes import dvm
|
||||
from androguard.core.bytecodes import dvm_types
|
||||
from androguard.gui import TextSelection
|
||||
from androguard.gui.ViewMode import ViewMode
|
||||
from androguard.gui.cemu import ConsoleEmulator, Directions
|
||||
@ -37,29 +37,29 @@ class InstructionView:
|
||||
offset = 0
|
||||
for operand in self.ins.get_operands(0):
|
||||
value = None
|
||||
if operand[0] == dvm.OPERAND_REGISTER:
|
||||
if operand[0] == dvm_types.OPERAND_REGISTER:
|
||||
value = [operand[0], "v%d" % operand[1]]
|
||||
|
||||
elif operand[0] == dvm.OPERAND_LITERAL:
|
||||
elif operand[0] == dvm_types.OPERAND_LITERAL:
|
||||
value = [operand[0], "%d" % operand[1]]
|
||||
|
||||
elif operand[0] == dvm.OPERAND_RAW:
|
||||
elif operand[0] == dvm_types.OPERAND_RAW:
|
||||
value = [operand[0], "%s" % operand[1]]
|
||||
|
||||
elif operand[0] == dvm.OPERAND_OFFSET:
|
||||
elif operand[0] == dvm_types.OPERAND_OFFSET:
|
||||
value = [operand[0], "%d" % operand[1]]
|
||||
|
||||
elif operand[0] & dvm.OPERAND_KIND:
|
||||
if operand[0] == (dvm.OPERAND_KIND + dvm.KIND_STRING):
|
||||
elif operand[0] & dvm_types.OPERAND_KIND:
|
||||
if operand[0] == (dvm_types.OPERAND_KIND + dvm_types.KIND_STRING):
|
||||
value = [operand[0], "%s" % operand[2]]
|
||||
|
||||
elif operand[0] == (dvm.OPERAND_KIND + dvm.KIND_METH):
|
||||
elif operand[0] == (dvm_types.OPERAND_KIND + dvm_types.KIND_METH):
|
||||
value = [operand[0], "%s" % operand[2]]
|
||||
|
||||
elif operand[0] == (dvm.OPERAND_KIND + dvm.KIND_FIELD):
|
||||
elif operand[0] == (dvm_types.OPERAND_KIND + dvm_types.KIND_FIELD):
|
||||
value = [operand[0], "%s" % operand[2]]
|
||||
|
||||
elif operand[0] == (dvm.OPERAND_KIND + dvm.KIND_TYPE):
|
||||
elif operand[0] == (dvm_types.OPERAND_KIND + dvm_types.KIND_TYPE):
|
||||
value = [operand[0], "%s" % operand[2]]
|
||||
|
||||
if value:
|
||||
@ -568,36 +568,36 @@ class DisasmViewMode(ViewMode):
|
||||
for operand in operands:
|
||||
qp.save()
|
||||
|
||||
if operand[0] == dvm.OPERAND_REGISTER:
|
||||
if operand[0] == dvm_types.OPERAND_REGISTER:
|
||||
qp.setPen(QtGui.QPen(QtGui.QColor('white')))
|
||||
cemu.write("%s" % operand[1])
|
||||
|
||||
elif operand[0] == dvm.OPERAND_LITERAL:
|
||||
elif operand[0] == dvm_types.OPERAND_LITERAL:
|
||||
qp.setPen(QtGui.QPen(QtGui.QColor('yellow')))
|
||||
cemu.write("%s" % operand[1])
|
||||
|
||||
elif operand[0] == dvm.OPERAND_RAW:
|
||||
elif operand[0] == dvm_types.OPERAND_RAW:
|
||||
qp.setPen(QtGui.QPen(QtGui.QColor('red')))
|
||||
cemu.write("%s" % operand[1])
|
||||
|
||||
elif operand[0] == dvm.OPERAND_OFFSET:
|
||||
elif operand[0] == dvm_types.OPERAND_OFFSET:
|
||||
qp.setPen(QtGui.QPen(QtGui.QColor('purple')))
|
||||
cemu.write("%s" % operand[1])
|
||||
|
||||
elif operand[0] & dvm.OPERAND_KIND:
|
||||
if operand[0] == (dvm.OPERAND_KIND + dvm.KIND_STRING):
|
||||
elif operand[0] & dvm_types.OPERAND_KIND:
|
||||
if operand[0] == (dvm_types.OPERAND_KIND + dvm_types.KIND_STRING):
|
||||
qp.setPen(QtGui.QPen(QtGui.QColor('red')))
|
||||
cemu.write("%s" % operand[1])
|
||||
|
||||
elif operand[0] == (dvm.OPERAND_KIND + dvm.KIND_METH):
|
||||
elif operand[0] == (dvm_types.OPERAND_KIND + dvm_types.KIND_METH):
|
||||
qp.setPen(QtGui.QPen(QtGui.QColor('cyan')))
|
||||
cemu.write("%s" % operand[1])
|
||||
|
||||
elif operand[0] == (dvm.OPERAND_KIND + dvm.KIND_FIELD):
|
||||
elif operand[0] == (dvm_types.OPERAND_KIND + dvm_types.KIND_FIELD):
|
||||
qp.setPen(QtGui.QPen(QtGui.QColor('green')))
|
||||
cemu.write("%s" % operand[1])
|
||||
|
||||
elif operand[0] == (dvm.OPERAND_KIND + dvm.KIND_TYPE):
|
||||
elif operand[0] == (dvm_types.OPERAND_KIND + dvm_types.KIND_TYPE):
|
||||
qp.setPen(QtGui.QPen(QtGui.QColor('blue')))
|
||||
cemu.write("%s" % operand[1])
|
||||
|
||||
|
BIN
examples/tests/FieldsTest.dex
Normal file
BIN
examples/tests/FieldsTest.dex
Normal file
Binary file not shown.
18
examples/tests/FieldsTest.java
Normal file
18
examples/tests/FieldsTest.java
Normal file
@ -0,0 +1,18 @@
|
||||
public class FieldsTest {
|
||||
|
||||
public String afield = "hello world";
|
||||
private String bfield = "sdf";
|
||||
|
||||
public static String cfield = "i am static";
|
||||
|
||||
public void foonbar() {
|
||||
System.out.println(this.afield);
|
||||
System.out.println(this.bfield);
|
||||
|
||||
this.afield = "hello mars";
|
||||
|
||||
System.out.println(this.afield);
|
||||
System.out.println(cfield);
|
||||
}
|
||||
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
all: Test.dex Switch.dex FillArrays.dex StringTests.dex AnalysisTest.dex ExceptionHandling.dex InterfaceCls.dex
|
||||
all: Test.dex Switch.dex FillArrays.dex StringTests.dex AnalysisTest.dex ExceptionHandling.dex InterfaceCls.dex FieldsTest.dex
|
||||
|
||||
%.class: %.java
|
||||
javac -source 1.7 -target 1.7 $^
|
||||
|
@ -117,20 +117,20 @@ class AnalysisTest(unittest.TestCase):
|
||||
|
||||
cls = dx.classes['LInterfaceCls;']
|
||||
self.assertIn('Ljavax/net/ssl/X509TrustManager;', cls.implements)
|
||||
self.assertEquals(cls.name, 'LInterfaceCls;')
|
||||
self.assertEqual(cls.name, 'LInterfaceCls;')
|
||||
|
||||
def testExtends(self):
|
||||
h, d, dx = AnalyzeDex('examples/tests/ExceptionHandling.dex')
|
||||
|
||||
cls = dx.classes['LSomeException;']
|
||||
self.assertEquals(cls.extends, 'Ljava/lang/Exception;')
|
||||
self.assertEquals(cls.name, 'LSomeException;')
|
||||
self.assertEqual(cls.extends, 'Ljava/lang/Exception;')
|
||||
self.assertEqual(cls.name, 'LSomeException;')
|
||||
self.assertFalse(cls.is_external())
|
||||
|
||||
cls = dx.classes['Ljava/lang/Exception;']
|
||||
self.assertEquals(cls.extends, 'Ljava/lang/Object;')
|
||||
self.assertEquals(cls.name, 'Ljava/lang/Exception;')
|
||||
self.assertEquals(cls.implements, [])
|
||||
self.assertEqual(cls.extends, 'Ljava/lang/Object;')
|
||||
self.assertEqual(cls.name, 'Ljava/lang/Exception;')
|
||||
self.assertEqual(cls.implements, [])
|
||||
self.assertTrue(cls.is_external())
|
||||
|
||||
def testXrefs(self):
|
||||
@ -152,25 +152,25 @@ class AnalysisTest(unittest.TestCase):
|
||||
self.assertIsInstance(testmeth, analysis.MethodClassAnalysis)
|
||||
self.assertFalse(testmeth.is_external())
|
||||
self.assertIsInstance(testmeth.method, dvm.EncodedMethod)
|
||||
self.assertEquals(testmeth.name, 'onCreate')
|
||||
self.assertEqual(testmeth.name, 'onCreate')
|
||||
|
||||
xrefs = list(map(lambda x: x.full_name, map(itemgetter(1), sorted(testmeth.get_xref_to(), key=itemgetter(2)))))
|
||||
self.assertEqual(len(xrefs), 5)
|
||||
|
||||
# First, super is called:
|
||||
self.assertEquals(xrefs.pop(0), 'Landroid/app/Activity; onCreate (Landroid/os/Bundle;)V')
|
||||
self.assertEqual(xrefs.pop(0), 'Landroid/app/Activity; onCreate (Landroid/os/Bundle;)V')
|
||||
# then setContentView (which is in the current class but the method is external)
|
||||
self.assertEquals(xrefs.pop(0), 'Ltests/androguard/TestActivity; setContentView (I)V')
|
||||
self.assertEqual(xrefs.pop(0), 'Ltests/androguard/TestActivity; setContentView (I)V')
|
||||
# then getApplicationContext (inside the Toast)
|
||||
self.assertEquals(xrefs.pop(0), 'Ltests/androguard/TestActivity; getApplicationContext ()Landroid/content/Context;')
|
||||
self.assertEqual(xrefs.pop(0), 'Ltests/androguard/TestActivity; getApplicationContext ()Landroid/content/Context;')
|
||||
# then Toast.makeText
|
||||
self.assertEquals(xrefs.pop(0), 'Landroid/widget/Toast; makeText (Landroid/content/Context; Ljava/lang/CharSequence; I)Landroid/widget/Toast;')
|
||||
self.assertEqual(xrefs.pop(0), 'Landroid/widget/Toast; makeText (Landroid/content/Context; Ljava/lang/CharSequence; I)Landroid/widget/Toast;')
|
||||
# then show()
|
||||
self.assertEquals(xrefs.pop(0), 'Landroid/widget/Toast; show ()V')
|
||||
self.assertEqual(xrefs.pop(0), 'Landroid/widget/Toast; show ()V')
|
||||
|
||||
# Now, test if the reverse is true
|
||||
other = list(dx.find_methods('^Landroid/app/Activity;$', '^onCreate$'))
|
||||
self.assertEquals(len(other), 1)
|
||||
self.assertEqual(len(other), 1)
|
||||
self.assertIsInstance(other[0], analysis.MethodClassAnalysis)
|
||||
self.assertTrue(other[0].is_external())
|
||||
self.assertTrue(other[0].is_android_api())
|
||||
@ -178,7 +178,7 @@ class AnalysisTest(unittest.TestCase):
|
||||
|
||||
other = list(dx.find_methods('^Ltests/androguard/TestActivity;$', '^setContentView$'))
|
||||
# External because not overwritten in class:
|
||||
self.assertEquals(len(other), 1)
|
||||
self.assertEqual(len(other), 1)
|
||||
self.assertIsInstance(other[0], analysis.MethodClassAnalysis)
|
||||
self.assertTrue(other[0].is_external())
|
||||
self.assertFalse(other[0].is_android_api())
|
||||
@ -186,21 +186,21 @@ class AnalysisTest(unittest.TestCase):
|
||||
|
||||
other = list(dx.find_methods('^Ltests/androguard/TestActivity;$', '^getApplicationContext$'))
|
||||
# External because not overwritten in class:
|
||||
self.assertEquals(len(other), 1)
|
||||
self.assertEqual(len(other), 1)
|
||||
self.assertIsInstance(other[0], analysis.MethodClassAnalysis)
|
||||
self.assertTrue(other[0].is_external())
|
||||
self.assertFalse(other[0].is_android_api())
|
||||
self.assertIn(testmeth.method, map(itemgetter(1), other[0].get_xref_from()))
|
||||
|
||||
other = list(dx.find_methods('^Landroid/widget/Toast;$', '^makeText$'))
|
||||
self.assertEquals(len(other), 1)
|
||||
self.assertEqual(len(other), 1)
|
||||
self.assertIsInstance(other[0], analysis.MethodClassAnalysis)
|
||||
self.assertTrue(other[0].is_external())
|
||||
self.assertTrue(other[0].is_android_api())
|
||||
self.assertIn(testmeth.method, map(itemgetter(1), other[0].get_xref_from()))
|
||||
|
||||
other = list(dx.find_methods('^Landroid/widget/Toast;$', '^show$'))
|
||||
self.assertEquals(len(other), 1)
|
||||
self.assertEqual(len(other), 1)
|
||||
self.assertIsInstance(other[0], analysis.MethodClassAnalysis)
|
||||
self.assertTrue(other[0].is_external())
|
||||
self.assertTrue(other[0].is_android_api())
|
||||
@ -215,32 +215,32 @@ class AnalysisTest(unittest.TestCase):
|
||||
self.assertIsInstance(testmeth, analysis.MethodClassAnalysis)
|
||||
self.assertFalse(testmeth.is_external())
|
||||
self.assertIsInstance(testmeth.method, dvm.EncodedMethod)
|
||||
self.assertEquals(testmeth.name, 'testCalls')
|
||||
self.assertEqual(testmeth.name, 'testCalls')
|
||||
|
||||
xrefs = list(map(lambda x: x.full_name, map(itemgetter(1), sorted(testmeth.get_xref_to(), key=itemgetter(2)))))
|
||||
self.assertEqual(len(xrefs), 4)
|
||||
|
||||
self.assertEquals(xrefs.pop(0), 'Ltests/androguard/TestActivity; testCall2 (J)V')
|
||||
self.assertEquals(xrefs.pop(0), 'Ltests/androguard/TestIfs; testIF (I)I')
|
||||
self.assertEquals(xrefs.pop(0), 'Ljava/lang/Object; getClass ()Ljava/lang/Class;')
|
||||
self.assertEquals(xrefs.pop(0), 'Ljava/io/PrintStream; println (Ljava/lang/Object;)V')
|
||||
self.assertEqual(xrefs.pop(0), 'Ltests/androguard/TestActivity; testCall2 (J)V')
|
||||
self.assertEqual(xrefs.pop(0), 'Ltests/androguard/TestIfs; testIF (I)I')
|
||||
self.assertEqual(xrefs.pop(0), 'Ljava/lang/Object; getClass ()Ljava/lang/Class;')
|
||||
self.assertEqual(xrefs.pop(0), 'Ljava/io/PrintStream; println (Ljava/lang/Object;)V')
|
||||
|
||||
other = list(dx.find_methods('^Ltests/androguard/TestActivity;$', '^testCall2$'))
|
||||
self.assertEquals(len(other), 1)
|
||||
self.assertEqual(len(other), 1)
|
||||
self.assertIsInstance(other[0], analysis.MethodClassAnalysis)
|
||||
self.assertFalse(other[0].is_external())
|
||||
self.assertFalse(other[0].is_android_api())
|
||||
self.assertIn(testmeth.method, map(itemgetter(1), other[0].get_xref_from()))
|
||||
|
||||
other = list(dx.find_methods('^Ltests/androguard/TestIfs;$', '^testIF$'))
|
||||
self.assertEquals(len(other), 1)
|
||||
self.assertEqual(len(other), 1)
|
||||
self.assertIsInstance(other[0], analysis.MethodClassAnalysis)
|
||||
self.assertFalse(other[0].is_external())
|
||||
self.assertFalse(other[0].is_android_api())
|
||||
self.assertIn(testmeth.method, map(itemgetter(1), other[0].get_xref_from()))
|
||||
|
||||
other = list(dx.find_methods('^Ljava/lang/Object;$', '^getClass$'))
|
||||
self.assertEquals(len(other), 1)
|
||||
self.assertEqual(len(other), 1)
|
||||
self.assertIsInstance(other[0], analysis.MethodClassAnalysis)
|
||||
self.assertTrue(other[0].is_external())
|
||||
self.assertTrue(other[0].is_android_api())
|
||||
@ -248,6 +248,51 @@ class AnalysisTest(unittest.TestCase):
|
||||
|
||||
# Not testing println, as it has too many variants...
|
||||
|
||||
|
||||
def testXrefOffsets(self):
|
||||
"""Tests if String offsets in bytecode are correctly stored"""
|
||||
_, _, dx = AnalyzeDex('examples/tests/AnalysisTest.dex')
|
||||
|
||||
self.assertEqual(len(dx.get_strings()), 1)
|
||||
self.assertIsInstance(dx.strings['Hello world'], analysis.StringAnalysis)
|
||||
|
||||
sa = dx.strings['Hello world']
|
||||
|
||||
self.assertEqual(len(sa.get_xref_from()), 1)
|
||||
self.assertEqual(len(sa.get_xref_from(withoffset=True)), 1)
|
||||
self.assertEqual(next(iter(sa.get_xref_from(withoffset=True)))[2], 4) # offset is 4
|
||||
|
||||
def testXrefOffsetsFields(self):
|
||||
"""Tests if Field offsets in bytecode are correctly stored"""
|
||||
_, _, dx = AnalyzeDex('examples/tests/FieldsTest.dex')
|
||||
|
||||
self.assertEqual(len(dx.get_strings()), 4)
|
||||
self.assertIn('hello world', dx.strings.keys())
|
||||
self.assertIn('sdf', dx.strings.keys())
|
||||
self.assertIn('hello mars', dx.strings.keys())
|
||||
self.assertIn('i am static', dx.strings.keys())
|
||||
|
||||
afield = next(dx.find_fields(fieldname='afield'))
|
||||
|
||||
self.assertEqual(len(afield.get_xref_read()), 1) # always same method
|
||||
self.assertEqual(len(afield.get_xref_read(withoffset=True)), 2)
|
||||
self.assertListEqual(list(sorted(map(itemgetter(2), afield.get_xref_read(withoffset=True)))), [4, 40])
|
||||
self.assertListEqual(list(map(lambda x: x.name, map(itemgetter(1),
|
||||
afield.get_xref_read(withoffset=True)))), ["foonbar", "foonbar"])
|
||||
|
||||
self.assertEqual(len(afield.get_xref_write()), 2)
|
||||
self.assertEqual(len(afield.get_xref_write(withoffset=True)), 2)
|
||||
self.assertListEqual(list(sorted(map(itemgetter(2), afield.get_xref_write(withoffset=True)))), [10, 32])
|
||||
self.assertListEqual(list(sorted(map(lambda x: x.name, map(itemgetter(1),
|
||||
afield.get_xref_write(withoffset=True))))), sorted(["<init>", "foonbar"]))
|
||||
|
||||
cfield = next(dx.find_fields(fieldname='cfield'))
|
||||
# this one is static, hence it must have a write in <clinit>
|
||||
self.assertListEqual(list(sorted(map(lambda x: x.name, map(itemgetter(1),
|
||||
cfield.get_xref_write(withoffset=True))))), sorted(["<clinit>"]))
|
||||
self.assertListEqual(list(sorted(map(lambda x: x.name, map(itemgetter(1),
|
||||
cfield.get_xref_read(withoffset=True))))), sorted(["foonbar"]))
|
||||
|
||||
def testPermissions(self):
|
||||
"""Test the get_permissions and get_permission_usage methods"""
|
||||
a, _, dx = AnalyzeAPK("examples/android/TestsAndroguard/bin/TestActivity.apk")
|
||||
|
Loading…
Reference in New Issue
Block a user