angr provides a callable interface to either concretely or concolically run functions. The current documentation on angr does not describe the process of translating a function prototype to a callable interfaces very well.
This interface enables you perform program slice execution to see the results of an individual function, or test functions without the need of running previous code.
Building function prototypes isn't as hard as the documentation would have you believe. Using radare2 to pull function argument types is pretty fast.
$ r2 binaries/crackme/crackme0x04
[0x080483d0]> aaa
[0x080483d0]> s sym.check
[0x08048484]> afi~arg
args: 1
arg char * s @ ebp+0x8
With these arguments you can convert the string "char *" into an angr point with either explicitly stating it or having angr parse it for you and provide the right sim type:
A calling convention tells angr how to handle the arguments being passed to the given callable function and how to handle the return value out of it. Using the prototype from above, the calling convention should be pretty simple.
#Calling convention
cc = p.factory.cc(func_ty=prototype)
The final callable function will need the function address, and the calling convention. To ensure symbolic arguments work, concrete_only needs to explicitly be disabled. When running with exclusively concrete arguments the concrete_only mode will enable the "tracing" suite of options for angr which expect no symbolic values.
Callables work just like regular python functions and will transparently run a simulation manager in the background and return the results upon the call.
callables work great for concrete analysis and tracing, however when using symbolic values the callables will not return until ALL paths finish which can mean never returning callables. The below code will not finish and will eventually exhaust all memory.
#Does not returnmy_sym_arg = claripy.BVS('my_arg', 10*8)#10 byte long strret_val =check_func(my_sym_arg)stdout = check_func.result_state.posix.dumps(1)print("Stdout : {}".format(stdout))
To fix this issue, a callstate should be used instead.
Using a call state
A call state is an angr state which initialize an program state to get ready to call a single function and return. A callable will create a call state and continue to run until all paths are exhausted. A call state can use the explore and step functions provided by the simulation manager.
my_sym_arg = claripy.BVS('my_arg', 10*8)#10 byte long str#Same calling convention from earlierstate = p.factory.call_state(find_func.addr, my_sym_arg, cc=cc)simgr = p.factory.simgr(state)simgr.explore(find=crack_me_good_addr)
Once the simulation manager returns you can use the "my_sym_arg" to discover the constraints imposed on it to yield the final program state!