def new_object(cls, state, type_, symbolic=False, init_object=False, init_class=False): """ Creates a new object reference. :param state: State associated to the object. :param type_: Class of the object. :param init_object: Whether the objects initializer method should be run. :param init_class: Whether the class initializer method should be run. :return: Reference to the new object. """ # create reference obj_ref = cls(heap_alloc_id=state.memory.get_new_uuid(), type_=type_, symbolic=symbolic) # run initializer if init_object: l.info(">" * 15 + " Initialize object %r ... " + ">" * 15, obj_ref) # find initializer method # TODO: add support for non-default initializing methods init_method = resolve_method(state, '<init>', type_, init_class=init_class).address() # setup init state args = [SootArgument(obj_ref, obj_ref.type, is_this_ref=True)] init_state = state.project.simos.state_call(init_method, *args, base_state=state, ret_addr=SootAddressTerminator()) # run init state simgr = state.project.factory.simgr(init_state) simgr.run() # copy results from initialization to the state state.memory.vm_static_table = simgr.deadended[0].memory.vm_static_table.copy() state.memory.heap = simgr.deadended[0].memory.heap.copy() l.debug("<" * 15 + " Initialize object %r ... done " + "<" * 15, obj_ref) return obj_ref
def test_object_tracking(): binary_dir = os.path.join(test_location, "object_tracking") project = create_project(binary_dir, load_native_libs=False) bootstrap_state = project.factory.blank_state(addr=SootAddressTerminator()) mylib_object = SimSootValue_ThisRef.new_object(bootstrap_state, "MyLib", symbolic=True, init_object=False) soot_method = resolve_method( bootstrap_state, 'testGetterAndSetterConcrete', "MixedJava", ("mylib.MyLib",), init_class=False).address() call_state = project.factory.call_state( soot_method, SootArgument(mylib_object, mylib_object.type, is_this_ref=False), base_state=bootstrap_state, ret_addr=SootAddressTerminator()) call_state.options.add(angr.options.JAVA_IDENTIFY_GETTER_SETTER) call_state.options.add(angr.options.JAVA_TRACK_ATTRIBUTES) simgr = project.factory.simgr(call_state) simgr.run() assert len(simgr.deadended) == 1 final_state = simgr.deadended[0] assert final_state.solver.eval(mylib_object.get_field(final_state, 'myInt', 'int')) == 1 assert final_state.solver.eval(mylib_object.get_field(final_state, 'myShort', 'short')) == 1 assert final_state.solver.eval(mylib_object.get_field(final_state, 'myChar', 'char')) == ord('c') assert final_state.solver.eval(mylib_object.get_field(final_state, 'myLong', 'long')) == 2 assert final_state.solver.eval(mylib_object.get_field(final_state, 'myFloat', 'float')) == 1.5 assert final_state.solver.eval(mylib_object.get_field(final_state, 'myDouble', 'double')) == 1.5 string_ref = mylib_object.get_field(final_state, 'myString', 'java.lang.String') assert final_state.solver.eval(final_state.memory.load(string_ref)) == 'Hello!' array_ref = mylib_object.get_field(final_state, 'myArray', 'int[]') assert final_state.solver.eval(array_ref.size) == 3 object_ref = mylib_object.get_field(final_state, 'myObject', 'java.lang.Object') assert final_state.solver.eval(object_ref.get_field(final_state, 'a', 'int')) == 1 assert ('myInt', 'int') in mylib_object.attributes assert ('myChar', 'char') in mylib_object.attributes assert ('myShort', 'short') in mylib_object.attributes assert ('myLong', 'long') in mylib_object.attributes assert ('myFloat', 'float') in mylib_object.attributes assert ('myDouble', 'double') in mylib_object.attributes assert ('myString', 'java.lang.String') in mylib_object.attributes assert ('myArray', 'int[]') in mylib_object.attributes assert ('myObject', 'java.lang.Object') in mylib_object.attributes
def init_class(self, class_, step_func=None): """ This method simulates the loading of a class by the JVM, during which parts of the class (e.g. static fields) are initialized. For this, we run the class initializer method <clinit> (if available) and update the state accordingly. Note: Initialization is skipped, if the class has already been initialized (or if it's not loaded in CLE). """ if self.is_class_initialized(class_): l.debug("Class %r already initialized.", class_) return l.debug("Initialize class %r.", class_) self.initialized_classes.add(class_) if not class_.is_loaded: l.warning("Class %r is not loaded in CLE. Skip initializiation.", class_) return clinit_method = resolve_method(self.state, '<clinit>', class_.name, include_superclasses=False, init_class=False) if clinit_method.is_loaded: engine = UberEngine(self.state.project) # use a fresh engine, as the default engine instance may be in use at this time javavm_simos = self.state.project.simos clinit_state = javavm_simos.state_call( addr=SootAddressDescriptor(clinit_method, 0, 0), base_state=self.state, ret_addr=SootAddressTerminator()) simgr = self.state.project.factory.simgr(clinit_state) l.info(">" * 15 + " Run class initializer %r ... " + ">" * 15, clinit_method) simgr.run(step_func=step_func, engine=engine) l.debug( "<" * 15 + " Run class initializer %r ... done " + "<" * 15, clinit_method) # The only thing that can be updated during initialization are # static or rather global information, which are either stored on # the heap or in the vm_static_table self.state.memory.vm_static_table = simgr.deadended[ -1].memory.vm_static_table.copy() self.state.memory.heap = simgr.deadended[-1].memory.heap.copy() else: l.debug( "Class initializer <clinit> is not loaded in CLE. Skip initializiation." )
def setup_caller(self, caller): # returns true if the caller itself adds entropy params = tuple(caller[2]) method_name = caller[0] + '.' + caller[1] soot_method = self.angr_p.loader.main_object.get_soot_method( method_name, params=params) target_method = SootMethodDescriptor.from_soot_method(soot_method) base_state = self.angr_p.factory.blank_state() base_state.ip = SootAddressTerminator() args_target_method, caller_args = self._get_initialized_method_args( base_state, soot_method) return self.angr_p.factory.call_state( target_method.addr, *args_target_method, base_state=base_state), caller_args
def state_blank(self, addr=None, **kwargs): # pylint: disable=arguments-differ if not kwargs.get('mode', None): kwargs['mode'] = self.project._default_analysis_mode if not kwargs.get('arch', None): kwargs['arch'] = self.arch if not kwargs.get('os_name', None): kwargs['os_name'] = self.name # enable support for string analysis if not kwargs.get('add_options', None): kwargs['add_options'] = [] kwargs['add_options'] += [options.STRINGS_ANALYSIS, options.COMPOSITE_SOLVER] if self.is_javavm_with_jni_support: # If the JNI support is enabled (i.e. JNI libs are loaded), the SimState # needs to support both the Vex and the Soot engine. Therefore we start with # an initialized native state and extend this with the Soot initializations. # Note: Setting `addr` to a `native address` (i.e. not an SootAddressDescriptor). # makes sure that the SimState is not in "Soot-mode". # TODO: use state_blank function from the native simos and not the super class state = super(SimJavaVM, self).state_blank(addr=0, **kwargs) native_addr_size = self.native_simos.arch.bits # Let the JNIEnv pointer point to the function table state.memory.store(addr=self.jni_env, data=BVV(self.jni_function_table, native_addr_size), endness=self.native_arch.memory_endness) # Initialize the function table # => Each entry usually contains the address of the function, but since we hook all functions # with SimProcedures, we store the address of the corresponding hook instead. # This, by construction, is exactly the address of the function table entry itself. for idx in range(len(jni_functions)): jni_function_addr = self.jni_function_table + idx * native_addr_size//8 state.memory.store(addr=jni_function_addr, data=BVV(jni_function_addr, native_addr_size), endness=self.native_arch.memory_endness) else: # w/o JNI support, we can just use a blank state state = SimState(project=self.project, **kwargs) if not self.project.entry and not addr: raise ValueError("Failed to init blank state. Project entry is not set/invalid" "and no address was provided.") # init state register state.regs._ip = addr if addr else self.project.entry state.regs._ip_binary = self.project.loader.main_object state.regs._invoke_return_target = None state.regs._invoke_return_variable = None # add empty stack frame state.memory.push_stack_frame() # create bottom of callstack new_frame = state.callstack.copy() new_frame.ret_addr = SootAddressTerminator() state.callstack.push(new_frame) # initialize class containing the current method state.javavm_classloader.get_class(state.addr.method.class_name, init_class=True, step_func=kwargs.get('step_function', None)) # initialize the Java environment # TODO move this to `state_full_init? self.init_static_field(state, "java.lang.System", "in", "java.io.InputStream") self.init_static_field(state, "java.lang.System", "out", "java.io.PrintStream") return state
def state_call(self, addr, *args, **kwargs): """ Create a native or a Java call state. :param addr: Soot or native addr of the invoke target. :param args: List of SootArgument values. """ state = kwargs.pop('base_state', None) # check if we need to setup a native or a java callsite if isinstance(addr, SootAddressDescriptor): # JAVA CALLSITE # ret addr precedence: ret_addr kwarg > base_state.addr > terminator ret_addr = kwargs.pop('ret_addr', state.addr if state else SootAddressTerminator()) cc = kwargs.pop('cc', SimCCSoot(self.arch)) if state is None: state = self.state_blank(addr=addr, **kwargs) else: state = state.copy() state.regs.ip = addr cc.setup_callsite(state, ret_addr, args) return state else: # NATIVE CALLSITE # setup native argument values native_arg_values = [] for arg in args: if arg.type in ArchSoot.primitive_types or \ arg.type == "JNIEnv": # the value of primitive types and the JNIEnv pointer # are just getting copied into the native memory native_arg_value = arg.value if self.arch.bits == 32 and arg.type == "long": # On 32 bit architecture, long values (w/ 64 bit) are copied # as two 32 bit integer # TODO is this correct? upper = native_arg_value.get_bytes(0, 4) lower = native_arg_value.get_bytes(4, 4) idx = args.index(arg) args = args[:idx] \ + (SootArgument(upper, 'int'), SootArgument(lower, 'int')) \ + args[idx+1:] native_arg_values += [upper, lower] continue else: # argument has a relative type # => map Java reference to an opaque reference, which the native code # can use to access the Java object through the JNI interface native_arg_value = state.jni_references.create_new_reference(obj=arg.value) native_arg_values += [native_arg_value] # setup native return type ret_type = kwargs.pop('ret_type') native_ret_type = self.get_native_type(ret_type) # setup function prototype, so the SimCC know how to init the callsite arg_types = [self.get_native_type(arg.type) for arg in args] prototype = SimTypeFunction(args=arg_types, returnty=native_ret_type) native_cc = self.get_native_cc(func_ty=prototype) # setup native invoke state return self.native_simos.state_call(addr, *native_arg_values, base_state=state, ret_addr=self.native_return_hook_addr, cc=native_cc, **kwargs)
def _execute(self): # TODO: implement simprocedure to throw exception self._add_jmp_target(target=SootAddressTerminator(), condition=self.state.solver.true)
def state_call(self, addr, *args, **kwargs): """ Create a native or a Java call state. :param addr: Soot or native addr of the invoke target. :param args: List of SootArgument values. """ state = kwargs.pop('base_state', None) # check if we need to setup a native or a java callsite if isinstance(addr, SootAddressDescriptor): # JAVA CALLSITE # ret addr precedence: ret_addr kwarg > base_state.addr > terminator ret_addr = kwargs.pop( 'ret_addr', state.addr if state else SootAddressTerminator()) cc = kwargs.pop('cc', SimCCSoot(self.arch)) if state is None: state = self.state_blank(addr=addr, **kwargs) else: state = state.copy() state.regs.ip = addr cc.setup_callsite(state, ret_addr, args, kwargs.pop('prototype', None)) return state else: # NATIVE CALLSITE # setup native return type # TODO roll this into protytype ret_type = kwargs.pop('ret_type') native_ret_type = self.get_native_type(ret_type) # setup function prototype, so the SimCC know how to init the callsite prototype = kwargs.pop('prototype', None) if prototype is None: arg_types = [self.get_native_type(arg.type) for arg in args] prototype = SimTypeFunction(args=arg_types, returnty=native_ret_type) native_cc = kwargs.pop('cc', None) if native_cc is None: native_cc = self.get_native_cc() # setup native argument values native_arg_values = [] for arg, arg_ty in zip(args, prototype.args): if arg.type in ArchSoot.primitive_types or \ arg.type == "JNIEnv": # the value of primitive types and the JNIEnv pointer # are just getting copied into the native memory native_arg_value = arg.value if self.arch.bits == 32 and arg.type == "long": # On 32 bit architecture, long values (w/ 64 bit) are copied # as two 32 bit integer # TODO I _think_ all this logic can go away as long as the cc knows how to store large values # TODO this has been mostly implemented 11 Dec 2021 # unfortunately no test cases hit this branch so I don't wanna touch it :( upper = native_arg_value.get_bytes(0, 4) lower = native_arg_value.get_bytes(4, 4) idx = args.index(arg) args = args[:idx] \ + (SootArgument(upper, 'int'), SootArgument(lower, 'int')) \ + args[idx+1:] native_arg_values += [upper, lower] continue if type(arg.value) is BV and len(arg.value) > arg_ty.size: # hack??? all small primitives are passed around as 32bit but cc won't like that native_arg_value = native_arg_value[arg_ty.size - 1:0] else: # argument has a relative type # => map Java reference to an opaque reference, which the native code # can use to access the Java object through the JNI interface native_arg_value = state.jni_references.create_new_reference( obj=arg.value) native_arg_values += [native_arg_value] # setup native invoke state return self.native_simos.state_call( addr, *native_arg_values, base_state=state, ret_addr=self.native_return_hook_addr, cc=native_cc, prototype=prototype, **kwargs)