simgr.run()
.SimulationManager
, ExplorationTechnique
, SimState
, and SimEngine
in order to understand what we're talking about at times! You may want to have the angr source open to follow along with this.**kwargs
and 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.run()
SimulationManager.run()
takes several optional parameters, all of which control when to break out of the stepping loop. Notably, n
, and until
. n
is used immediately - the run function loops, calling the step()
function and passing on all its parameters until either n
steps have happened or some other termination condition has occurred. If n
is not provided, it defaults to 1, unless an until
function 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.run()
, step()
will be called in a loop until any of the following:n
number of steps have elapseduntil
function returns truecomplete()
hooks (combined via the SimulationManager.completion_mode
parameter/attribute - it is by default the any
builtin function but can be changed to all
for example) indicate that the analysis is completeSimulationManager.explore()
is a very thin wrapper around run()
which adds the Explorer
exploration 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()
step()
to handle degenerate cases - mostly implementing the population of the deadended
stash, the save_unsat
option, and calling the filter()
exploration technique hooks. Beyond this, though, most of the logic is looping through the stash specified by the stash
argument and calling step_state()
on each state, then applying the dict result of step_state()
to the stash list. Finally, if the step_func
parameter is provided, it is called with the simulation manager as a parameter before the step ends.step_state()
step_state()
, which can be overridden or instrumented by exploration techniques, is also simple - it calls successors()
, which returns a SimSuccessors
object, 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 ErrorRecord
will be inserted into SimulationManager.errored
.successors()
successors()
, which can also be instrumented by exploration techniques, is supposed to take a state and step it forward, returning a SimSuccessors
object categorizing its successors independently of any stash logic. If the successor_func
parameter was provided, it is used and its return value is returned directly. If this parameter was not provided, we use the project.factory.successors
method to tick the state forward and get our SimSuccessors
.SimEngine
is 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 engine
parameter to specify which engine will be used to perform the step..step()
, .explore()
, .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 SimSuccessors
object and then calls process_successors()
so that other mixins can fill it out.UberEngine
, contains several mixins which provide the process_successors()
method:SimEngineFailure
- handles stepping states with degenerate jumpkindsSimEngineSyscall
- handles stepping states which have performed a syscall and need it executedHooksMixin
- handles stepping states which have reached a hooked address and need the hook executedSimEngineUnicorn
- executes machine code via the unicorn engineSootMixin
- executes java bytecode via the SOOT IRHeavyVEXMixin
- executes machine code via the VEX IRSimSuccessors
object if they can handle the current state, otherwise they call super()
to pass the job on to the next class in the stack.SimEngineFailure
handles error cases. It is only used when the previous jumpkind is one of Ijk_EmFail
, Ijk_MapFail
, Ijk_Sig*
, 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.SimEngineSyscall
services syscalls. It is used when the previous jumpkind is anything of the form Ijk_Sys*
. It works by making a call into SimOS
to retrieve the SimProcedure that should be run to respond to this syscall, and then running it! Pretty simple.HooksMixin
provides 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.SimEngineUnicorn
performs concrete execution with the Unicorn Engine. It is used when the state option o.UNICORN
is enabled, and a myriad of other conditions designed for maximum efficiency (described below) are met.SootMixin
performs execution over the SOOT IR. Not very important unless you are analyzing java bytecode, in which case it is very important.SimEngineVEX
is 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.UNICORN
state option, at every step SimEngineUnicorn
will be invoked, and try to see if it is allowed to use Unicorn to execute concretely.o.unicorn
(lowercase) of options to your state:state.unicorn
plugin.logging.getLogger('angr.engines.unicorn_engine').setLevel('DEBUG'); logging.getLogger('angr.state_plugins.unicorn_engine').setLevel('DEBUG')
from a sample run of unicorn.state.unicorn.max_steps
variable.state.unicorn
plugin) 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.