Add new xrefs: new_instance, const_class and xref_read/write to MethodAnalysis

To avoid iterating over all fields in order to find the ones a method
can read/write from/to, the Fields are now available through a
MethodAnalysis object. Additionally, the _new_instance and _cons_class
references are added to MethodAnalysis and ClassAnalysis for the same
reason as well as to avoid using xref_to to a non-existent method.
This commit is contained in:
Jakob 2019-11-22 11:51:10 +01:00
parent dbfa523bc0
commit b91ac1c150
2 changed files with 152 additions and 0 deletions

View File

@ -347,6 +347,12 @@ class MethodAnalysis:
self.xrefto = set()
self.xreffrom = set()
self.xrefread = set()
self.xrefwrite = set()
self.xrefnewinstance = set()
self.xrefconstclass = set()
# Reserved for further use
self.apilist = None
@ -450,6 +456,42 @@ class MethodAnalysis:
# setup exception by basic block
i.set_exception_analysis(self.exceptions.get_exception(i.start, i.end - 1))
def add_xref_read(self, classobj, fieldobj, offset):
"""
:param ClassAnalysis classobj:
:param FieldAnalysis fieldobj:
:param int offset: offset in the bytecode
"""
self.xrefread.add((classobj, fieldobj, offset))
def add_xref_write(self, classobj, fieldobj, offset):
"""
:param ClassAnalysis classobj:
:param FieldAnalysis fieldobj:
:param int offset: offset in the bytecode
"""
self.xrefwrite.add((classobj, fieldobj, offset))
def get_xref_read(self):
"""
Returns a list of xrefs where a field is read by this method.
The list contains tuples of the originating class and methods,
where the class is represented as a :class:`ClassAnalysis`,
while the Field is a :class:`FieldAnalysis`.
"""
return self.xrefread
def get_xref_write(self):
"""
Returns a list of xrefs where a field is written to by this method.
The list contains tuples of the originating class and methods,
where the class is represented as a :class:`ClassAnalysis`,
while the Field is a :class:`FieldAnalysis`.
"""
return self.xrefwrite
def add_xref_to(self, classobj, methodobj, offset):
"""
Add a crossreference to another method
@ -496,6 +538,47 @@ class MethodAnalysis:
"""
return self.xrefto
def add_xref_new_instance(self, classobj, offset):
"""
Add a crossreference to another class that is
instanced within this method.
:param classobj: :class:`~ClassAnalysis`
:param offset: integer where in the method the instantiation happens
"""
self.xrefnewinstance.add((classobj, offset))
def get_xref_new_instance(self):
"""
Returns a list of tuples containing the class and offset of
the creation of a new instance of a class by this method.
The list of tuples has the form:
(:class:`~ClassAnalysis`,
:class:`int`)
"""
return self.xrefnewinstance
def add_xref_const_class(self, classobj, offset):
"""
Add a crossreference to another classtype.
:param classobj: :class:`~ClassAnalysis`
:param offset: integer where in the method the classtype is referenced
"""
self.xrefconstclass.add((classobj, offset))
def get_xref_const_class(self):
"""
Returns a list of tuples containing the class and offset of
the references to another classtype by this method.
The list of tuples has the form:
(:class:`~ClassAnalysis`,
:class:`int`)
"""
return self.xrefconstclass
def is_external(self):
"""
Returns True if the underlying method is external
@ -900,6 +983,9 @@ class ClassAnalysis:
self.xrefto = collections.defaultdict(set)
self.xreffrom = collections.defaultdict(set)
self.xrefnewinstance = set()
self.xrefconstclass = set()
# Reserved for further use
self.apilist = None
@ -1161,6 +1247,48 @@ class ClassAnalysis:
"""
return self.xrefto
def add_xref_new_instance(self, methobj, offset):
"""
Add a crossreference to another method that is
instancing this class.
:param classobj: :class:`~MethodAnalysis`
:param offset: integer where in the method the instantiation happens
"""
self.xrefnewinstance.add((methobj, offset))
def get_xref_new_instance(self):
"""
Returns a list of tuples containing the set of methods
with offsets that instance this class
The list of tuples has the form:
(:class:`~MathodAnalysis`,
:class:`int`)
"""
return self.xrefnewinstance
def add_xref_const_class(self, methobj, offset):
"""
Add a crossreference to a method referencing this classtype.
:param classobj: :class:`~MethodAnalysis`
:param offset: integer where in the method the classtype is referenced
"""
self.xrefconstclass.add((methobj, offset))
def get_xref_const_class(self):
"""
Returns a list of tuples containing the method and offset
referencing this classtype.
The list of tuples has the form:
(:class:`~MethodAnalysis`,
:class:`int`)
"""
return self.xrefconstclass
def get_vm_class(self):
"""
Returns the original Dalvik VM class or the external class object.
@ -1366,9 +1494,18 @@ class Analysis:
# In this case that means, that current_method calls the class oth_class.
# Hence, on xref_to the method info is the calling method not the called one,
# as there is no called method!
# With the _new_instance and _const_class can this be deprecated?
# Removing these does not impact tests
cur_cls.add_xref_to(REF_TYPE(op_value), oth_cls, cur_meth, off)
oth_cls.add_xref_from(REF_TYPE(op_value), cur_cls, cur_meth, off)
if op_value == 0x1c:
cur_meth.add_xref_const_class(oth_cls, off)
oth_cls.add_xref_const_class(cur_meth, off)
if op_value == 0x22:
cur_meth.add_xref_new_instance(oth_cls, off)
oth_cls.add_xref_new_instance(cur_meth, off)
# 2) check for method calls: invoke-* (0x6e ... 0x72), invoke-xxx/range (0x74 ... 0x78)
elif (0x6e <= op_value <= 0x72) or (0x74 <= op_value <= 0x78):
idx_meth = instruction.get_ref_kind()
@ -1419,9 +1556,11 @@ class Analysis:
if (0x52 <= op_value <= 0x58) or (0x60 <= op_value <= 0x66):
# read access to a field
self.classes[cur_cls_name].add_field_xref_read(cur_meth, cur_cls, field_item, off)
cur_meth.add_xref_read(cur_cls, field_item, off)
else:
# write access to a field
self.classes[cur_cls_name].add_field_xref_write(cur_meth, cur_cls, field_item, off)
cur_meth.add_xref_write(cur_cls, field_item, off)
def get_method(self, method):
"""

View File

@ -240,6 +240,19 @@ class AnalysisTest(unittest.TestCase):
self.assertTrue(other[0].is_android_api())
self.assertIn(testmeth, map(itemgetter(1), other[0].get_xref_from()))
# Testing new_instance
testmeth = list(filter(lambda x: x.name == 'testString', testcls.get_methods()))[0]
self.assertIsInstance(testmeth, analysis.MethodAnalysis)
self.assertFalse(testmeth.is_external())
self.assertIsInstance(testmeth.method, dvm.EncodedMethod)
self.assertEqual(testmeth.name, 'testString')
stringcls = dx.classes['Ljava/lang/String;']
self.assertIsInstance(stringcls, analysis.ClassAnalysis)
self.assertIn(stringcls, map(itemgetter(0), testmeth.get_xref_new_instance()))
self.assertIn(testmeth, map(itemgetter(0), stringcls.get_xref_new_instance()))
# Not testing println, as it has too many variants...
def testXrefOffsets(self):