import logging
from . import Analysis
l = logging.getLogger(name=__name__)
[docs]class SootClassHierarchyError(Exception):
[docs] def __init__(self, msg):
self.msg = msg
def __str__(self):
return self.msg
[docs]class NoConcreteDispatch(SootClassHierarchyError):
[docs] def __init__(self, msg):
self.msg = msg
[docs]class SootClassHierarchy(Analysis):
"""
Generate complete hierarchy.
"""
[docs] def __init__(self):
if self.project.arch.name != "Soot":
raise SootClassHierarchyError("SootClassHierarchyError only supports analyzing Soot programs.")
self.interface_implementers = {}
self.sub_interfaces = {}
self.dir_sub_interfaces = {}
self.sub_classes = {}
self.dir_sub_classes = {}
# init data
self.init_hierarchy()
[docs] def init_hierarchy(self):
for class_name, cls in self.project.loader.main_object.classes.items():
if "INTERFACE" in cls.attrs:
self.interface_implementers[cls] = []
self.dir_sub_interfaces[cls] = []
else:
self.dir_sub_classes[cls] = []
for class_name, cls in self.project.loader.main_object.classes.items():
if self.has_super_class(cls):
if "INTERFACE" in cls.attrs:
# TODO
# super_interfaces
pass
else:
super_class = self.project.loader.main_object.classes[cls.super_class]
self.dir_sub_classes[super_class].append(cls)
for i_name in cls.interfaces:
# get interface
if i_name not in self.project.loader.main_object.classes:
continue
i = self.project.loader.main_object.classes[i_name]
self.interface_implementers[i].append(cls)
# fill direct implementers with subclasses
for class_name, cls in self.project.loader.main_object.classes.items():
if "INTERFACE" in cls.attrs:
implementers = self.interface_implementers[cls]
s = set()
for c in implementers:
s |= set(self.get_sub_classes_including(c))
self.interface_implementers[cls] = list(s)
[docs] def has_super_class(self, cls):
if cls.super_class:
try:
self.project.loader.main_object.classes[cls.super_class]
return True
except KeyError:
pass
return False
[docs] def is_subclass_including(self, cls_child, cls_parent):
parent_classes = self.get_super_classes_including(cls_child)
if cls_parent in parent_classes:
return True
return False
[docs] def is_subclass(self, cls_child, cls_parent):
parent_classes = self.get_super_classes(cls_child)
if cls_parent in parent_classes:
return True
return False
[docs] def is_visible_method(self, cls, method):
method_cls = self.project.loader.main_object.classes[method.class_name]
if not self.is_visible_class(cls, method_cls):
return False
if "PUBLIC" in method.attrs:
return True
if "PRIVATE" in method.attrs:
return cls == method_cls
package_from = cls.name.split(".")[:-1]
package_to = method_cls.name.split(".")[:-1]
if "PROTECTED" in method.attrs:
is_sub = self.is_subclass_including(cls, method_cls)
is_same_package = package_from == package_to
return is_sub or is_same_package
return package_from == package_to
[docs] def is_visible_class(self, cls_from, cls_to):
if "PUBLIC" in cls_to.attrs:
return True
if "PROTECTED" in cls_to.attrs or "PRIVATE" in cls_to.attrs:
return False
package_from = cls_from.name.split(".")[:-1]
package_to = cls_to.name.split(".")[:-1]
return package_from == package_to
[docs] def get_super_classes(self, cls):
if "INTERFACE" in cls.attrs:
raise SootClassHierarchyError("This is an Interface")
super_classes = []
current = cls
try:
while True:
current = self.project.loader.main_object.classes[current.super_class]
super_classes.append(current)
except KeyError:
return super_classes
[docs] def get_super_classes_including(self, cls):
super_classes = self.get_super_classes(cls)
res = []
res.append(cls)
res.extend(super_classes)
return res
[docs] def get_implementers(self, interface):
if "INTERFACE" not in interface.attrs:
raise SootClassHierarchyError("This is not an interface")
res_set = set()
for i in self.get_sub_interfaces_including(interface):
res_set |= set(self.interface_implementers[i])
return list(res_set)
[docs] def get_sub_interfaces_including(self, interface):
res = self.get_sub_interfaces(interface)
res.append(interface)
return res
[docs] def get_sub_interfaces(self, interface):
if "INTERFACE" not in interface.attrs:
raise SootClassHierarchyError("This is not an interface")
if interface in self.sub_interfaces:
return self.sub_interfaces[interface]
# Otherwise
res = []
for i in self.dir_sub_interfaces[interface]:
res.extend(self.get_sub_interfaces_including(i))
self.sub_interfaces[interface] = res
return res
[docs] def get_sub_classes(self, cls):
if "INTERFACE" in cls.attrs:
raise SootClassHierarchyError("This is an Interface. Class needed")
if cls in self.sub_classes:
return self.sub_classes[cls]
res = []
for c in self.dir_sub_classes[cls]:
res.extend(self.get_sub_classes_including(c))
self.sub_classes[cls] = res
return res
[docs] def get_sub_classes_including(self, cls):
if "INTERFACE" in cls.attrs:
raise SootClassHierarchyError("This is an Interface. Class needed")
res = []
res.extend(self.get_sub_classes(cls))
res.append(cls)
return res
[docs] def resolve_abstract_dispatch(self, cls, method):
if "INTERFACE" in cls.attrs:
classes_set = set()
for i in self.get_implementers(cls):
classes_set |= set(self.get_sub_classes_including(i))
classes = list(classes_set)
else:
classes = self.get_sub_classes_including(cls)
res_set = set()
for c in classes:
if "ABSTRACT" not in c.attrs:
res_set.add(self.resolve_concrete_dispatch(c, method))
return list(res_set)
[docs] def resolve_concrete_dispatch(self, cls, method):
if "INTERFACE" in cls.attrs:
raise SootClassHierarchyError("class needed!")
for c in self.get_super_classes_including(cls):
for m in c.methods:
if m.name == method.name and m.params == method.params:
if self.is_visible_method(c, method):
return m
raise NoConcreteDispatch("Could not resolve concrete dispatch!")
[docs] def resolve_special_dispatch(self, method, container):
# container is the method that contains the invoke
method_cls = self.project.loader.main_object.classes[method.class_name]
container_cls = self.project.loader.main_object.classes[container.class_name]
if method.name == "<init>" or "PRIVATE" in method.attrs:
return method
elif self.is_subclass(method_cls, container_cls):
return self.resolve_concrete_dispatch(container_cls, method)
else:
return method
[docs] def resolve_invoke(self, invoke_expr, method, container):
# Generic method to resolve invoke
# Given an invoke expression it figures out which "technique" should be applied
invoke_type = str(type(invoke_expr))
cls = self.project.loader.main_object.classes[method.class_name]
if "VirtualInvokeExpr" in invoke_type:
targets = self.resolve_abstract_dispatch(cls, method)
elif "DynamicInvokeExpr" in invoke_type:
targets = self.resolve_abstract_dispatch(cls, method)
elif "InterfaceInvokeExpr" in invoke_type:
targets = self.resolve_abstract_dispatch(cls, method)
elif "SpecialInvokeExpr" in invoke_type:
t = self.resolve_special_dispatch(method, container)
targets = [t]
elif "StaticInvokeExpr" in invoke_type:
targets = [method]
return targets
from angr.analyses import AnalysesHub
AnalysesHub.register_default("SootClassHierarchy", SootClassHierarchy)