Skip to content

Weekly Status

Derrick McKee edited this page Sep 24, 2018 · 17 revisions

25/9/18

I finished the function argument inference code, and ran it against musl libc. The results are the following:

2796 identified functions 1082 unidentified functions

Unidentified functions are present because all inputs cause a signal to be issued. The reasons for this are the following:

  • A function relies on global variables, such as mutex locks, which either are not mapped or uninitialized
    • srandom(unsigned), setpwent()
  • An unbounded string function executes until it finds the null terminator, which will never happen in our void* inputs. This function hits the guard page surrounding the allocated pointer. I am currently not testing for char* although I have made support for it. A quick test shows that at least some of these unidentified functions can be found by adding char* to the list of supported types.
    • strlen(char*), mkstemp(char*)
  • The function issues a trap instruction
    • __restore
  • The function allocates memory using malloc
    • strdup(char*)

Sources of function signature inaccuracy:

  • Failed check early in function does not exercise main functionality
    • malloc(void*)

18/9/18

Based on last week's discussion, I started the development of the function argument inference code. The design follows a similar line as fosbin-flop, in that I map the binary to the application's address space and use a binary descriptor to list out the location of functions. Then for each function in the binary, I invoke it N times, once for a unique combination of {int, double, char*, void*} for up to 5 arguments, plus once for no arguments. As input to the functions, I use nominally good values: 1 for int, 2.0 for double, a malloced string of length 48 for char*, and a malloced region 1024 bytes long for void*. I have not yet programmed the logic that determines success, but the logic is simple: I select the set of function types that minimizes the argument count but also returns without fault and does not change errno.

Once that is implemented I need to start generating the input-output mappings for library functions, and organizing them according to input types.

Topics for discussion:

  1. The number of type combinations is large. How to know which functions to actually test?
  2. Is my success criteria good enough?

11/9/18

Fixed several bugs in the pass and runtime library, including how floating point numbers are handled. This required adding a new runtime function to handle floating types, because casting a double into a void* changed the bit layout and I was losing information. The python script (id-parser.py) that reads in the JSON output from the test runs is capable of generating C++ classes for functions that only use primitive types. This was an attempt to get some initial results. Using the libc-test repository which musl uses, I can compile and run all the tests, but I am focusing on the src/math/pow.c test for initial results.

The pow function is called over 11,000 times in the course of the test, so I had id-parser.py cap the tests at 10 for now. As of this writing, the pow identifier does not find pow in the statically linked pow unit test, and I am investigating why. UPDATE: pow uses the GOT, and we don't know where that is going to be in the binary.

On the evaluation front, I found in GitHub the source of many different malware, including Mirai. Mirai looks to be ideal for a case study; it is statically linked, runs on embedded devices, well-known, and recent. Additionally, Mathias contacted a researcher who is compiling a large corpus of malware, and I have been granted access to the repository. I have not looked through it yet, but I am sure that there will be other malware case studies we can include.

Something to address in the paper from my presentation at the weekly meeting: Function call sequences that must happen in order (e.g. open-write-close) are not supported yet.

4/9/18

Created LLVM pass and runtime library to capture the input and return values for function calls. The pass is simple right now. It simply finds all CallInst, inserts a new function call to the runtime library lof_precall, and 0 or more calls to another runtime function lof_record_arg. Finally, a function call to lof_postcall is added immediately after the CallInst to record the return value. All function calls and function arguments are stored in a simple stack implementation using a linked list implementation I wrote. In other words, each function is represented as a stack of arguments, and each function argument stack is pushed onto a global stack. All data is written to a file called lof-output.json and is obviously in an easily parseable JSON format.

The library functions have the following function signatures:

lof_precall(void* funcaddr)

lof_record_arg(Data value, TypeID typeId, size_t size)

lof_postcall(Data value, TypeID typeId, size_t size)

Data is a union of various primitive types, and TypeID is an enum provided by LLVM that indicates if the value is a float, int, void*, etc.

Questions for discussion:

  1. How to handle void*? Currently I am just reading the first 4 bytes.
  2. How to handle structs? I'd like to punt this down the road for now until primitive types are handled.

28/8/18

Evaluated memcpy, memmove, strcpy, strncpy against busybox and nginx. 100% accuracy. Go me. This shows that we can identify functions post-execution by controlling their inputs, at least for some functions. Those functions that require some initial state probably won't work, but maybe we can find a lot of other functions with more function identifiers. We need an automated way to generate them.

Evaluated approaches for automating the generation of function identifiers. Problems I am trying to solve:

  1. A priori, I don't know how a valid function execution changes program state
  2. I don't know how to get valid input to a function to measure program state change

Unit tests seem to solve these problems, because the programmer writing the test would know best good inputs to a function (theoretically), and any relevant state change would be tested for in the unit test.

Two approaches were discussed:

  1. Modify the various existing testing frameworks libraries currently use to memory map the binary into the address space first, and then replace function unit test is testing with a function in the binary. If test passes with binary function, then binary function == unit test function.
  2. Instrument testing framework to capture the function parameters and return values all unit tests use when executing a function we want to describe. We save all input and return values (including struct subfields and following pointers recursively), and automatically create a fbf::FunctionIdentifier subclass containing the input. We then use the currently existing framework to compare functions in the BUA. A successful identification would be if the return value of the BUA function (or the underlying data if a pointer is returned) is the same as the unit test function return value, and any change in the input data pre- and post-execution should be the same in the unit test function and the binary function.

Clone this wiki locally