I have a binary compiled for x86_64 that looks like this:
#include <stdio.h>
#include <string.h>
void options(char *input) {
if (strcmp(input, "A") == 0) {
printf("You picked 'A'\n");
} else if (strcmp(input, "B") == 0) {
printf("You picked 'B'\n");
} else {
printf("You picked something That wasn't 'A' or 'B'\n");
}
return;
}
int main(int argc, char **argv) {
options("Z");
return 0;
}
It has been compiled with the following command:
gcc main.c -static -o demo
I would now like to use angr to find inputs for as much code coverage as possible.
The desired outputs are A
, B
, and something similar to Anything
.
This article (https://breaking-bits.gitbook.io/breaking-bits/vulnerability-discovery/reverse-engineering/modern-approaches-toward-embedded-research) shows that its possible to do something like this. I've had success with this technique from time to time, but often I'm left with no inputs generated. The following example is based off of the example given in the link. It is the script I am using to attempt to generate inputs:
#! /usr/bin/env python3
import angr
import angr.sim_options as so
import claripy
import sys
sys.setrecursionlimit(15000)
symbol = "options"
# Create a project with history tracking
p = angr.Project('/home/user/Documents/demo')
extras = {so.REVERSE_MEMORY_NAME_MAP, so.TRACK_ACTION_HISTORY}
# User input will be 200 symbolic bytes
user_arg = claripy.BVS("user_arg", 200*8)
# State starts at function address
start_addr = p.loader.find_symbol(symbol).rebased_addr
state = p.factory.blank_state(addr=start_addr, add_options=extras)
# Store symbolic user_input buffer
state.memory.store(0x100000, user_arg)
state.regs.rax = 0x100000
# Run to exhaustion
simgr = p.factory.simgr(state)
# Exploration technique to prevent infinite loops
simgr.use_technique(angr.exploration_techniques.LoopSeer(bound=50))
simgr.explore()
i = 0;
# Print each path and the inputs required
for path in simgr.unconstrained:
print("{} : {}".format(path,hex([x for x in path.history.bbl_addrs][-1])))
u_input = path.solver.eval(user_arg, cast_to=bytes)
print(u_input)
with open('corpus/output'+str(i)+'.bin', 'wb') as file:
file.write(u_input)
i = i + 1
The main differences from the article are that this script is to be used for an x86_64 binary (register changed), and discovered inputs are saved to files.
When I run the script, I get a handful of errors and warnings:
WARNING | 2022-04-04 20:23:59,667 | claripy.vsa.strided_interval | Tried to cast_low an interval to an interval shorter than its stride.
ERROR | 2022-04-04 20:23:59,703 | angr.analyses.cfg.indirect_jump_resolvers.jumptable.JumpTableProcessor | Unsupported Binop Iop_InterleaveHI64x2.
...
WARNING | 2022-04-04 20:24:03,531 | claripy.vsa.strided_interval | Tried to cast_low an interval to an interval shorter than its stride.
...
ERROR | 2022-04-04 20:24:05,909 | angr.analyses.cfg.cfg_fast | Decoding error occurred at address 0x428b82 of function 0x426e20.
...
WARNING | 2022-04-04 20:24:26,416 | angr.analyses.loopfinder | Bad loop: more than one entry point (<BlockNode at 0x46a2d0 (size 19)>, <BlockNode at 0x46a2f4 (size 15)>)
I can provide the full output, but it is rather long and will exceed the post length limit.
When the script finished, it provides no unconstrained outputs. This is the approach I thought I should have taken based on the article I read. This could be the wrong approach entirely, but I'll boil it down to one question. What can one do to generate a list of inputs that provide maximum coverage using angr?