Binary tracing is sometimes useful in malware analysis in order to check the flow of executing for a giving sample (checking anti's, exit conditions, etc).

The easiest way to trace the execution of a binary is with a DBI framework. Personally I like Intel's Pin, but there are others that should accomplish this task as well (DynamoRIO, frida).

Tracing the execution of the main image

To start with we need to write a Pin tool that traces the execution on the main image of the executable. We chose to trace just the main image in order to avoid getting traces for API functions and reduce general overhead on our plugin.

Also note that packed binaries will probably not work as intended as the real application code will be most likely in a dynamically allocated region and thus out of the scope of the main executable image (not to mention that such regions won't even be present at all in the binary).

As such, we can use the following code for the aforementioned task:

/*! @file
 *  This is an example of the PIN tool that demonstrates some basic PIN APIs 
 *  and could serve as the starting point for developing your first PIN tool

#include "pin.H"
#include <iostream>
#include <iomanip>
#include <fstream>

/* ================================================================== */
// Global variables 
/* ================================================================== */

UINT64 bblCount = 0;        //number of dynamically executed basic blocks  
ADDRINT mainAddrRange[2] = { 0 };

std::ostream * out = &cerr;  
std::ofstream fLog;

/* ===================================================================== */
// Command line switches
/* ===================================================================== */
KNOB<string> KnobOutputFile(KNOB_MODE_WRITEONCE,  "pintool",  
    "o", "", "specify file name for MyPinTool output");

KNOB<BOOL>   KnobCount(KNOB_MODE_WRITEONCE,  "pintool",  
    "count", "1", "count instructions, basic blocks and threads in the application");

/* ===================================================================== */
// Utilities
/* ===================================================================== */

INT32 Usage()  
    cerr << "This tool prints out the number of dynamically executed " << endl <<
            "instructions, basic blocks and threads in the application." << endl << endl;

    cerr << KNOB_BASE::StringKnobSummary() << endl;

    return -1;

/* ===================================================================== */
// Analysis routines
/* ===================================================================== */

VOID TraceBlock(ADDRINT addr)  

VOID InstrumentFunction(char *insname, ADDRINT eip, THREADID tid, CONTEXT *pctx)  
    ADDRINT rva = eip - mainAddrRange[0];

    std::stringstream ss;
    ss << hex << rva;
    *out << ss.str() << std::endl;

/* ===================================================================== */
// Instrumentation callbacks
/* ===================================================================== */

void ImgLoad(IMG img, void *v) {  
    if (mainAddrRange[0] != NULL) return;

    if (IMG_Valid(img) && IMG_IsMainExecutable(img)){
        mainAddrRange[0] = IMG_LowAddress(img);
        mainAddrRange[1] = IMG_HighAddress(img);

        *out << "# Main executable: " << IMG_Name(img).c_str() << endl;
        *out << "# Address range: " << hex << mainAddrRange[0]
             << " - " << mainAddrRange[1] << endl;

VOID Trace(TRACE trace, VOID *v) {  
    ADDRINT addr = TRACE_Address(trace);
    if (mainAddrRange[0] == NULL || mainAddrRange[1] == NULL) return;
    if ( !(addr >= mainAddrRange[0] && addr <= mainAddrRange[1]) ) return;

    for (BBL bbl = TRACE_BblHead(trace); BBL_Valid(bbl); bbl = BBL_Next(bbl)) {
        BBL_InsertCall(bbl, IPOINT_BEFORE, (AFUNPTR) TraceBlock,
                       IARG_ADDRINT, addr - mainAddrRange[0], IARG_END);

        for (INS ins = BBL_InsHead(bbl); INS_Valid(ins); ins = INS_Next(ins)) {
            INS_InsertCall(ins, IPOINT_BEFORE, (AFUNPTR)InstrumentFunction,
                IARG_PTR, 0, //new string(INS_Disassemble(ins)),

 * Print out analysis results.
 * This function is called when the application exits.
 * @param[in]   code            exit code of the application
 * @param[in]   v               value specified by the tool in the 
 *                              PIN_AddFiniFunction function call
VOID Fini(INT32 code, VOID *v)  
    *out <<  "# ===============================================" << endl;
    *out <<  "# MyPinTool analysis results: " << endl;
    *out <<  "# Number of basic blocks: " << bblCount  << endl;
    *out <<  "# ===============================================" << endl;

int main(int argc, char *argv[])  
    // Initialize PIN library. Print help message if -h(elp) is specified
    // in the command line or the command line is invalid 
    if( PIN_Init(argc,argv) )
        return Usage();

    string fileName = KnobOutputFile.Value();
    if (!fileName.empty()) { out = new ofstream(fileName.c_str());}

    if (KnobCount)
        IMG_AddInstrumentFunction(ImgLoad, 0);
        TRACE_AddInstrumentFunction(Trace, 0);
        PIN_AddFiniFunction(Fini, 0);

    if (!KnobOutputFile.Value().empty()) 
        cerr << "See file " << KnobOutputFile.Value() << " for analysis results" << endl;
    cerr <<  "===============================================" << endl;

    // Start the program, never returns

    return 0;

The tool will generate a list of RVAs (one per line) for every instruction executed in chronological order (and some stats, which will be preceeded with a # character - which represents a comment). Now we just need to load this information in a more user-friendly format, like IDA graph representation.

To load it we can use this simple IDAPython script:

import idaapi  
import idc  
import struct  
import colorsys

def main():  
    filepath  = idaapi.askfile_c(False, "*.*", "Pin log file")
    imagebase = idaapi.get_imagebase()
        f = open(filepath)
        print("Failed to open log file")

    for rva in f.readlines():
        if rva.startswith("#"):
        idc.SetColor(imagebase + int(rva, 16), CIC_ITEM, 0xccd2ea)

if __name__ == "__main__":  

Which will tint executed lines with a reddish hue.

Tracing results of a random function in an EvilPony sample

Alright, we now have a working tracer, but it is a bit of a pain to have to spin up a VM, create a snapshot, trace, gather results and then revert to the snapshot. Thus, we can automate said process with Cuckoo.

Cuckoo automation

Cuckoo already does the whole VM management thing. The only thing which we need to include is a step to execute our sample under the Pin tracing tool and gather the generated results. That task is the perfect fit for a Cuckoo analysis package. Cuckoo analysis packages specify how to run the sample under analysis and, optionally, which files to gather (amongst other things).

Without further ado, the package code:

import os  
import logging  
from lib.api.process import Process  
from lib.common.abstracts import Package

log = logging.getLogger(__name__)

class Pintool(Package):  
    """Pintool analysis package."""
    OUTF = os.path.join(os.path.expanduser("~"), "trace.log")
    PATHS = [
        ("System32", "pin.exe"),

    pin_pid   = None
    log_files = None

    def start(self, path):
        pin  = self.get_path("pin")
        tool = self.options.get("tool", "tool.dll")
        args = [
            "-t", os.path.join("C:\\Windows\\System32", tool),
            "-o", self.OUTF, "--", path

        self.pin_pid = self.execute(pin, args=args)
        return self.pin_pid

    def finish(self):"Resquested analysis shutdown. Killing proc %d" % self.pin_pid)

        if os.path.isfile(self.OUTF):
  "Marking %s for upload" % self.OUTF)
            self.log_files = [(self.OUTF, "%s_trace.log" % self.pin_pid)]
            log.warn("Pin output could not be found at %s" % self.OUTF)

    def package_files(self):
        return self.log_files

As you can see the package expects pin binaries in System32 and runs the tool specified by the analysis option tool or tool.dll if none was specified. Therefore, this requires the VM snapshot to have all the pin-related binaries required, which is not optimal because it forces us to update the snapshot in order to include new pin tools.

It would be nice to provide pin binaries dynamically on a per-analysis basis but that would imply modifications in non-package-specific code and make it package-specific, which is not what we want (it could be provided just like monitor does, but that's a thing for the future).


After provisioning the VM we can start tracing samples. For an example I will use a GreenDispenser ATM malware sample with SHA256 b7e61f65e147885ec1fe6a787b62d9ee82d1f34f1c9ba8068d3570adca87c54f.
The submission should be done with the following options:

Once reported the trace will be available on the Dropped files section. We can now proceed to load it in IDA.


As we can see the sample is taking a early exit (function returns after the highlighted block) after comparing the result of GetSystemTime. Which means that GreenDispenser exits if the date is later than September 2015. We can patch the jump (by noping it out) and see what it does after.

We can see how execution has continued. However, this time it did not reach the end of main.

Execution never returns from the highlighted function

That's because GreenDispenser will wait indefinitely until the correct PIN is introduced on the keypad.

Thread spawning

Reading data from the pinpad

Comparing the read pin

Nota that the XFS emulator used to run this sample returns random pin-codes when asked to read data from the pinpad input device, so comparison may succeed or fail randomly.