Overview
Static binary analysis is a very powerful tool but it has its limitations. Dealing with C++ code making use of virtual functions or with function calls being resolved at runtime are just two examples of nightmares for a reverse engineer. Another example would be to figure out which code paths are taken after processing a certain input. Let me show with an example why this is indeed an interesting problem for a security researcher. Imagine the case of an analyst evaluating the security of, let’s say, a PDF reader. Through fuzzing the application will be fed with a large amount of “mutated” PDF files (most of them deviating from the specification) in order to cause an application error. Some of these errors can be indicators of an underlying problem that could be leveraged by an attacker to take over the application.
An important measure in such an audit is the so called “code coverage”. Simply explained this measures which percentage of the total code that has been actually executed during our tests. A code coverage of 60% for example means that almost half of the application has not been tested. This means a lot of potential vulnerabilities left undiscovered.
Now that you have (hopefully) a good idea of how important is to trace the program’s execution let’s jump to a concrete example.
Tracing with Intel PIN
PIN is a framework from Intel to perform dynamic binary instrumentation). It would be pretentious to try to explain what it is better than their creators, so here is an excerpt from its manual:
[Quote]“Pin is a dynamic binary instrumentation framework for the IA-32 and x86-64 instruction-set architectures that enables the creation of dynamic program analysis tools. […] The tools created using Pin, called Pintools, can be used to perform program analysis on user space applications in Linux and Windows. As a dynamic binary instrumentation tool, instrumentation is performed at run time on the compiled binary files. Thus, it requires no recompiling of source code and can support instrumenting programs that dynamically generate code.
Pin provides a rich API that abstracts away the underlying instruction-set idiosyncrasies and allows context information such as register contents to be passed to the injected code as parameters. Pin automatically saves and restores the registers that are overwritten by the injected code so the application continues to work. Limited access to symbol and debug information is available as well.”[/Quote]
Using the PIN framework it is possible to create so called Pintools that will be compiled to a DLL (in Windows). These contain the guide to instrument the target program, something along the lines of “Keep track of all operations that write to memory” or “Increment this counter every time a new instruction is executed”. Very complex programs can be developed using PIN and several tools used internally in Intel are based on it. In our case we will develop a small Pintool which just executes a callback function every time a basic block is hit. This callback will log the address of the basic block to a file or optionally to a SQLite3 database.
The following is an excerpt of the main function. The code initializing the log file and the database has been omitted. Especially important here is the function TRACE_AddInstrumentFunction, which registers a function (Trace) in order to instrument every basic block found during execution.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
Other information will be logged as well, for example the addresses of code sections of all modules, through the use of IMG_AddInstrumentFunction and the corresponding callback (imageLoad_cb). This information is useful to avoid tracing into system DLLs, improving the tracer’s performance.
The next excerpt shows the very short Trace function. This analyzes the binary and inserts a call to the function LogBasicBlock at the beginning of each basic block of execution within every function.
1 2 3 4 5 6 7 8 |
|
Our last Pintool code excerpt shows part of the function LogBasicBlock, which actually implements the logging to a function and/or to a database. Its only argument is the address in memory of the basic block just hit.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
Some code avoiding tracing into system DLLs as well as code logging to a database has been omitted.
As mentioned before, this code is finally compiled to a DLL and used with the main PIN binary to instrument our target. We will be using a small test binary written for this purpose which takes two numbers as command line arguments, performs some arithmetic on them and prints messages depending on the result.
The exact command line arguments for Windows are shown here:
1
|
|
Executing this results in a text file containing a list of all addresses of basic blocks executed during the trace, one per row. Depending on the size of the binary this can be very large, containing multiple times the same address. In some situations, for example when measuring code coverage, having such detailed information about the trace is less relevant and a list of basic blocks hit (only once) is enough. To this end the Pintool has a “-hit” modifier which avoids logging basic blocks more than once.
Visualizing with IDAPython
At this point we have an execution trace in the form of a text file, which it is not very practical. We need a way to visualize this and optimally be able to overlay our trace information on top of another tool. We will be choosing IDA Pro, the professional disassembler. This tool can be used to analyze a binary and display its structure graphically, what makes it a perfect candidate for our purposes.
In order to import our trace data we will have to leverage the power of IDAPython. As you may have guessed already, these are the Python bindings for the IDA Pro API. Making use of this, it is possible to write complex scripts to automate several binary analysis tasks. Fortunately for us, in this case the script we need to develop is pretty straightforward.
The relevant code is shown below. Some auxiliary functions have been omitted.
IDA Pro knows only about the binary on disk but it assigns it some probably addresses in order to recreate where the binary would be loaded in memory. The default base address is 0x00400000
1 2 3 4 5 6 7 8 |
|
If the system used to record the trace was Windows XP or alike we are good, since this is the default base address for all main modules. On the other hand, it the system was Windows Vista or newer (with ASLR activated) the base address from the trace will be different from the one in IDA and it will be impossible to overlay our trace information on it. We fix this problem by reading from our file the base image at the time of the trace and using it to modify the addresses in IDA correspondingly. This process is called “rebasing”.
The next step is rather straightforward, the file is parsed to a list of addresses and these are marked with a specific color.
1 2 3 4 5 6 7 8 9 |
|
Finally, we would like to present this information in a convenient way. It would be nice to be able to go through the trace back and forth and inspect the instructions executed within a specific function. There are several ways to do this but the simplest would be to write a custom Chooser. It will show three columns: index, function and address of the traced instruction.
1 2 3 |
|
Putting it all together
The following screenshot shows our extension in action during a reversing session with the aforementioned test binary. Notice our trace explorer docked on the right side and how the marked basic blocks can be easily spotted on the left bottom side. By clicking on an element of the trace explorer the graph view jumps directly to the corresponding instruction within a function.
Figure 1. Using the extension. Notice the new dock (right) and the information about executed blocks (left, bottom)
Conclusion
Although simple, the tools showed in this post are very useful and can save an analyst a lot of time and more importantly, avoid headaches. With some effort it is possible to develop your own tools to assist you in specific reverse engineering tasks.
Written by
Carlos Garcia