Merge branch 'master' of https://github.com/androguard/androguard into opcodemadness

This commit is contained in:
Sebastian Bachmann 2019-09-25 19:47:12 +02:00
commit 6f2cf4981b
9 changed files with 430 additions and 324 deletions

View File

@ -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,23 +470,34 @@ class MethodAnalysis:
class StringAnalysis:
"""
StringAnalysis contains the XREFs of a string.
As Strings are only used as a source, they only contain
the XREF_FROM set, i.e. where the string is used.
This Array stores the information in which method the String is used.
"""
def __init__(self, value):
"""
StringAnalysis contains the XREFs of a string.
As Strings are only used as a source, they only contain
the XREF_FROM set, i.e. where the string is used.
This Array stores the information in which method the String is used.
: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`.
"""
return self.xreffrom
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
"""
return self.xrefwrite
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):
"""

View File

@ -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:
@ -148,8 +178,8 @@ def PrettyShow(m_a, basic_blocks, notes={}):
else:
if len(i.childs) == 2:
print_fct("{}[ {}{} ".format(branch_false_color,
i.childs[0][2].get_name(),
branch_true_color))
i.childs[0][2].get_name(),
branch_true_color))
print_fct(' '.join("%s" % c[2].get_name(
) for c in i.childs[1:]) + " ]%s" % normal_color)
else:
@ -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 = "&quot; &#92;<br />&quot;".join(map(escape, textwrap.wrap(operand[2], 64)))
return '<FONT color="{}">&quot;{}&quot;</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}
}}
d = pydot.graph_from_dot_data(buff)
if d:
{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 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,22 +713,16 @@ 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)()
return obj.get_raw()
class BuffHandle:

View File

@ -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,97 +8278,12 @@ 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:
"""
This class can parse the odex header
This class can parse the odex header
:param buff: a Buff object string which represents the odex dependencies
:param buff: a Buff object string which represents the odex dependencies
"""
def __init__(self, buff):
@ -8438,9 +8316,9 @@ class OdexHeaderItem:
class OdexDependencies:
"""
This class can parse the odex dependencies
This class can parse the odex dependencies
:param buff: a Buff object string which represents the odex dependencies
:param buff: a Buff object string which represents the odex dependencies
"""
def __init__(self, buff):

View File

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

View File

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

Binary file not shown.

View 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);
}
}

View File

@ -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 $^

View File

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