-
Notifications
You must be signed in to change notification settings - Fork 2
Weekly Status
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:
- How to handle
void*? Currently I am just reading the first 4 bytes. - How to handle structs? I'd like to punt this down the road for now until primitive types are handled.
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:
- A priori, I don't know how a valid function execution changes program state
- 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:
- 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.
- 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::FunctionIdentifiersubclass 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.