SimEnginein order to understand what we're talking about at times! You may want to have the angr source open to follow along with this.
**kwargsand pass them along to the next function in the hierarchy, so you can pass parameters to any point in the hierarchy and they will trickle down to everything below.
SimulationManager.run()takes several optional parameters, all of which control when to break out of the stepping loop. Notably,
nis used immediately - the run function loops, calling the
step()function and passing on all its parameters until either
nsteps have happened or some other termination condition has occurred. If
nis not provided, it defaults to 1, unless an
untilfunction is provided, in which case there will be no numerical cap on the loop. Additionally, the stash that is being used is taken into consideration, as if it becomes empty execution must terminate.
step()will be called in a loop until any of the following:
nnumber of steps have elapsed
untilfunction returns true
complete()hooks (combined via the
SimulationManager.completion_modeparameter/attribute - it is by default the
anybuiltin function but can be changed to
allfor example) indicate that the analysis is complete
SimulationManager.explore()is a very thin wrapper around
run()which adds the
Explorerexploration technique, since performing one-off explorations is a very common action. Its code in its entirety is below:
SimulationManager.use_technique(), angr monkeypatches the simulation manager to replace any function implemented in the exploration technique's body with a function which will first call the exploration technique's function, and then on the second call will call the original function. This is somewhat messy to implement and certainly not thread safe by any means, but does produce a clean and powerful interface for exploration techniques to instrument stepping behavior, either before or after the original function is called, even choosing whether or not to call the original function whatsoever. Additionally, it allows multiple exploration techniques to hook the same function, as the monkeypatched function simply becomes the "original" function for the next-applied hook.
step()to handle degenerate cases - mostly implementing the population of the
save_unsatoption, and calling the
filter()exploration technique hooks. Beyond this, though, most of the logic is looping through the stash specified by the
stashargument and calling
step_state()on each state, then applying the dict result of
step_state()to the stash list. Finally, if the
step_funcparameter is provided, it is called with the simulation manager as a parameter before the step ends.
step_state(), which can be overridden or instrumented by exploration techniques, is also simple - it calls
successors(), which returns a
SimSuccessorsobject, and then translates it into a dict mapping stash names to new states which should be added to that stash. It also implements error handling - if
successors()throws an error, it will be caught and an
ErrorRecordwill be inserted into
successors(), which can also be instrumented by exploration techniques, is supposed to take a state and step it forward, returning a
SimSuccessorsobject categorizing its successors independently of any stash logic. If the
successor_funcparameter was provided, it is used and its return value is returned directly. If this parameter was not provided, we use the
project.factory.successorsmethod to tick the state forward and get our
SimEngineis a device that knows how to take a state and produce its successors. There is only one "default engine" per project, but you can provide the
engineparameter to specify which engine will be used to perform the step.
.run()or anything else that starts execution, and they will be filtered down to this level. Any additional parameters will continue being passed down, until they reach the part of the engine they are intended for. The engine will discard any parameters it doesn't understand.
SimEngine.process(), which can return whatever result it likes, but for simulation managers, engines are required to use
SuccessorsMixin, which provides a
process()method, which creates a
SimSuccessorsobject and then calls
process_successors()so that other mixins can fill it out.
UberEngine, contains several mixins which provide the
SimEngineFailure- handles stepping states with degenerate jumpkinds
SimEngineSyscall- handles stepping states which have performed a syscall and need it executed
HooksMixin- handles stepping states which have reached a hooked address and need the hook executed
SimEngineUnicorn- executes machine code via the unicorn engine
SootMixin- executes java bytecode via the SOOT IR
HeavyVEXMixin- executes machine code via the VEX IR
SimSuccessorsobject if they can handle the current state, otherwise they call
super()to pass the job on to the next class in the stack.
SimEngineFailurehandles error cases. It is only used when the previous jumpkind is one of
Ijk_NoDecode(but only if the address is not hooked), or
Ijk_Exit. In the first four cases, its action is to raise an exception. In the last case, its action is to simply produce no successors.
SimEngineSyscallservices syscalls. It is used when the previous jumpkind is anything of the form
Ijk_Sys*. It works by making a call into
SimOSto retrieve the SimProcedure that should be run to respond to this syscall, and then running it! Pretty simple.
HooksMixinprovides the hooking functionality in angr. It is used when a state is at an address that is hooked, and the previous jumpkind is not
Ijk_NoHook. It simply looks up the associated SimProcedure and runs it on the state! It also takes the parameter
procedure, which will cause the given procedure to be run for the current step even if the address is not hooked.
SimEngineUnicornperforms concrete execution with the Unicorn Engine. It is used when the state option
o.UNICORNis enabled, and a myriad of other conditions designed for maximum efficiency (described below) are met.
SootMixinperforms execution over the SOOT IR. Not very important unless you are analyzing java bytecode, in which case it is very important.
SimEngineVEXis the big fellow. It is used whenever any of the previous can't be used. It attempts to lift bytes from the current address into an IRSB, and then executes that IRSB symbolically. There are a huge number of parameters that can control this process, so I will merely link to the API reference describing them.
o.UNICORNstate option, at every step
SimEngineUnicornwill be invoked, and try to see if it is allowed to use Unicorn to execute concretely.
o.unicorn(lowercase) of options to your state:
logging.getLogger('angr.engines.unicorn_engine').setLevel('DEBUG'); logging.getLogger('angr.state_plugins.unicorn_engine').setLevel('DEBUG')from a sample run of unicorn.
state.unicornplugin) that wait for certain conditions to hold (i.e., no symbolic memory accesses for X blocks) before jumping back into unicorn when a unicorn run is aborted due to anything but a simprocedure or syscall. Here, the condition it's waiting for is for 100 blocks to be executed before jumping back in.