0

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?

1 Answer 1

0

A simple problem I see in your code is

state.regs.rax = 0x100000

if you need to set constraint on a function argument - you 'll need to follow the calling convention. On my machine - Ubuntu Linux - the calling convention says first arg to be present in rdi

so by merely changing it to

state.regs.rdi = 0x100000

I see this as output

<SimState @ <BV64 mem_7ffffffffff0000_6980_64{UNINITIALIZED}>> : 0x4006ed
b'A\x00\...'
<SimState @ <BV64 mem_7ffffffffff0000_6981_64{UNINITIALIZED}>> : 0x4006ed
b'B\x00\...'
<SimState @ <BV64 mem_7ffffffffff0000_6982_64{UNINITIALIZED}>> : 0x4006ed
b'\x80\x80...'

Which matches correctly with what the code does.

1
  • Much appreciated! That was clearly the issue in this case. Apr 9, 2022 at 3:47

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.