A simple PIN tool unpacker for the Linux version of Skype

Some time ago I wanted to take a look to Skype to see how it works and get the classes diagram of this program but, surprise: It’s packed. The Windows version is protected with a crypter of their own, (UPDATE: this statement was wrong: the last time I checked it, was protected with Themida. It was Spotify the application protected with Themida). However, as I expected, the Linux version was simply packed (not protected) and with something easy to unpack. To unpack Skype and be able to analyse it in IDA and, also, to learn a bit how Intel PIN works, I have written a PIN tool to “automatically” unpack Skype.

Skype packer for Linux

The packer used in Skype is pretty straightforward to unpack and we don’t really need an unpacker for it: if we just want to analyse it in IDA Pro we can simply do the following:

  1. Open it in IDA and let it finish the auto analysis.
  2. Put an “execute” hardware breakpoint at entry point.
  3. Execute it until the breakpoint is hit the 2nd time.
  4. Take a memory snapshot of the loader segments in IDA.

This is how it looks like before unpacking, right after the initial auto-analysis performed by IDA Pro:

Skype binary before unpacking it in IDA

Skype binary before unpacking it in IDA

And this is how it looks like after the hardware breakpoint is hit the 2nd time:

Skype unpacked

Skype unpacked, displaying the typical GCC’s compiled code entry point

But, as previously stated, for learning a bit how Intel PIN works I decided to write a simple “write and exec” unpacker for Skype and connect IDA Pro with PIN via GDB server to take a memory snapshot when done. Also, it will be useful to unpack other simple packers, not just to unpack the Skype’s Linux binary.

Intel PIN

PIN is a binary instrumentation framework created by Intel for x86 and x86_64 that let us instrument code for any application written for those processors (in the past there was support for ARM and Itanium too, IIRC). Basically, it works by rewriting the real code the application executes inserting our instrumentation code at different granularities (instruction level, basic block level, etc…) A simple PIN tool looks like the following (extracted from the PIN example tool):

// Instruction count example
  1. // Actual instrumentation code
  2. VOID docount() { icount++; }
  3.  
  4. // Code to check if we need to instrument an instruction
  5. VOID Instruction(INS ins, VOID *v)
  6. {
  7.     // Insert a call to docount before every instruction, no arguments are passed
  8.     INS_InsertCall(ins, IPOINT_BEFORE, (AFUNPTR)docount, IARG_END);
  9. }
  10.  
  11. void Usage(void)
  12. {
  13. }
  14.  
  15. // PIN stuff and instrumentation initialization
  16. int main(int argc, char * argv[])
  17. {
  18.     // Initialize pin
  19.     if (PIN_Init(argc, argv)) return Usage();
  20.  
  21.     // Register Instruction to be called to instrument instructions
  22.     INS_AddInstrumentFunction(Instruction, 0);
  23.  
  24.     // Start the program, never returns
  25.     PIN_StartProgram();
  26.  
  27.     return 0;
  28. }

In main we initialize PIN stuff, setup instruction level instrumentation and executes the program (PIN_StartProgram). Then, for every new instruction discovered by PIN, the callback “Instruction” will be called. In this callback we decide what instructions we want to actually instrument by calling INS_InsertCall. Then, before the instruction is executed the callback “docount” will be executed. And that is, we have a working example to count the number of instructions a program executes.

GDB Server

In my opinion, one of the best features supported by Intel PIN is the “-appdebug” command line switch. This switch tells PIN to start a GDB server to debug the application. We can use this feature to debug from IDA Pro any application using PIN using the remote GDB debugger. The unique “problem” (not really a problem, just annoying) is that we cannot specify the port PIN will listen in as it will be randomly selected and we need to change it in Debugger -> Process Options every time we execute PIN. For example, let’s say we want to debug skype running the inscount0 example from IDA with the GDB server we would execute a command like the following:

$ pin -appdebug -t source/tools/ManualExamples/obj-ia32/inscount0.so -- `which skype`
Application stopped until continued from debugger.
Start GDB, then issue this command at the (gdb) prompt:
target remote :12587

And setup the remote GDB connection from IDA Pro using the specified port in the output of the command (Debugger -> Process Options):

After setting it up, click OK and select Debugger -> Attach to process from IDA. In the next dialog, just press OK when asked to which process we want to attach and that’s all, we are debugging the process with PIN from IDA.

A simple “write and exec” unpacker

Let’s go back to the main purpose of this post: writing an unpacker for Skype as a PIN tool. What I will do is to check if any instruction in the main binary (skype) modifies any of the application’s segments (for example, if it writes to the .text section), save them and, if the application jumps to execute code to any of the modified sections, raise an application breakpoint to inform the debugger the process seems to be unpacked. Is a pretty simple idea that works for simple packers, like the one used in Skype.

What I do in the PIN tool is, in the function main setup instrumentation granularity at trace level (basic block level) and install another callback that will be called right before the application starts:

//————————————————————————–
  1. int main(int argc, char *argv[])
  2. {
  3.   // Initialize PIN library. Print help message if -h(elp) is specified
  4.   // in the command line or the command line is invalid
  5.   if( PIN_Init(argc,argv) )
  6.     return Usage();
  7.  
  8.   // Register function to be called to instrument traces
  9.   TRACE_AddInstrumentFunction(trace_cb, 0);
  10.  
  11.   // Register function to be called at application start time
  12.   PIN_AddApplicationStartFunction(app_start_cb, 0);
  13.  
  14.   // Register function to be called when the application exits
  15.   PIN_AddFiniFunction(fini_cb, 0);
  16.  
  17.   // Start the program, never returns
  18.   PIN_StartProgram();
  19.  
  20.   return 0;
  21. }

In the “app_start_cb” function callback we will save the application’s segments in a std::map:

(…)
  1. struct segdata_t
  2. {
  3.   size_t  size;
  4.   ADDRINT check;
  5.   bool    written;
  6. };
  7.  
  8. typedef std::map segmap_t;
  9. segmap_t seg_bytes;
  10. ()
  11. //————————————————————————–
  12. static VOID app_start_cb(VOID *v)
  13. {
  14.   IMG img = APP_ImgHead();
  15.   for( SEC sec= IMG_SecHead(img); SEC_Valid(sec); sec = SEC_Next(sec) )
  16.   {
  17.     ADDRINT sec_ea = SEC_Address(sec);
  18.     // is the segment loaded in the process memory?
  19.     if ( sec_ea != 0 )
  20.     {
  21.       ADDRINT check;
  22.       // copy the first DWORD/QWORD to check if it was really changed
  23.       size_t bytes = PIN_SafeCopy(&check, (void*)sec_ea, sizeof(ADDRINT));
  24.       if ( bytes == sizeof(ADDRINT) )
  25.       {
  26.         if ( min_ea > sec_ea || min_ea == 0 )
  27.           min_ea = sec_ea;
  28.         if ( max_ea < sec_ea || max_ea == (unsigned)-1 )
  29.           max_ea = sec_ea;
  30.  
  31.         segdata_t seg;
  32.         seg.size = SEC_Size(sec);
  33.         seg.check = check;
  34.         seg.written = false;
  35.  
  36.         // save the segment information
  37.         seg_bytes[sec_ea] = seg;
  38.       }
  39.     }
  40.   }
  41. }

We iterate over all the segments in the application that will be loaded in the process memory and save information about them. Now, in the “trace_cb” callback, we will check in every instruction of every basic block that is going to be executed if the code modifies memory in the limits of the previously recorded segments or if the process is going to execute an instruction in a previously written application’s segment:

//————————————————————————–
  1. static VOID trace_cb(TRACE trace, VOID *v)
  2. {
  3.   // Visit every basic block in the trace
  4.   for ( BBL bbl = TRACE_BblHead(trace); BBL_Valid(bbl); bbl = BBL_Next(bbl) )
  5.   {
  6.     // Visit every instruction in the basic block
  7.     for( INS ins = BBL_InsHead(bbl); INS_Valid(ins); ins=INS_Next(ins) )
  8.     {
  9.       // check if the address is in the limits of the application's segments
  10.       ADDRINT ea = INS_Address(ins);
  11.       if ( !valid_ea(ea) )
  12.         continue;
  13.  
  14.       // if that address was already written and is going to be executed, we consider it's unpacked
  15.       if ( was_writen(ea) )
  16.       {
  17.         INS_InsertCall(ins, IPOINT_BEFORE, (AFUNPTR)check_unpacked_cb,
  18.             IARG_INST_PTR,
  19.             IARG_CONST_CONTEXT,
  20.             IARG_THREAD_ID,
  21.             IARG_END);
  22.       }
  23.  
  24.       // Instruments memory accesses using a predicated call, i.e.
  25.       // the instrumentation is called iff the instruction will actually be executed.
  26.       //
  27.       // The IA-64 architecture has explicitly predicated instructions.
  28.       // On the IA-32 and Intel(R) 64 architectures conditional moves and REP
  29.       // prefixed instructions appear as predicated instructions in Pin.
  30.       UINT32 mem_operands = INS_MemoryOperandCount(ins);
  31.  
  32.       // Iterate over each memory operand of the instruction.
  33.       for ( UINT32 mem_op = 0; mem_op < mem_operands; mem_op++ )
  34.       {
  35.         // Note that in some architectures a single memory operand can be
  36.         // both read and written (for instance incl (%eax) on IA-32)
  37.         // In that case we instrument it once for read and once for write.
  38.         if ( INS_MemoryOperandIsWritten(ins, mem_op) )
  39.         {
  40.           // is the memory address to be modified in the limits of the application's segments?
  41.           INS_InsertIfPredicatedCall(ins, IPOINT_BEFORE, (AFUNPTR)valid_ea,
  42.             IARG_MEMORYOP_EA,
  43.             mem_op,
  44.             IARG_END);
  45.  
  46.           // if so, add our instrumentation code
  47.           INS_InsertThenPredicatedCall(
  48.               ins, IPOINT_BEFORE, (AFUNPTR)record_mem_write_cb,
  49.               IARG_INST_PTR,
  50.               IARG_MEMORYOP_EA, mem_op,
  51.               IARG_END);
  52.         }
  53.       }
  54.     }
  55.   }
  56. }

In the “record_mem_write_cb” callback the PIN tool checks if the actual memory write affects any of the application’s segments. If so, the “written” flag of the corresponding segment element is set to true:

//————————————————————————–
  1. // Handle memory write records
  2. VOID record_mem_write_cb(VOID * ip, VOID * addr)
  3. {
  4.   ADDRINT ea = (ADDRINT)addr;
  5.   segmap_t::iterator p;
  6.   for ( p = seg_bytes.begin(); p != seg_bytes.end() && !p->second.written; ++p )
  7.   {
  8.     ADDRINT start_ea = p->first;
  9.     if ( ea >= start_ea )
  10.     {
  11.       segdata_t *seg = &p->second;
  12.       if ( ea size )
  13.       {
  14.         fprintf(stderr, "%p: W %p\n", ip, addr);
  15.         write_address.push_back((ADDRINT)addr);
  16.         seg->written = true;
  17.         break;
  18.       }
  19.     }
  20.   }
  21. }

And, finally, in the callback “check_unpacked_cb” that we installed in the “trace_cb” callback, we set again the “written” member to false and raise an application breakpoint that will be catch in IDA Pro:

//————————————————————————–
  1. VOID check_unpacked_cb(VOID * ip, const CONTEXT *ctxt, THREADID tid)
  2. {
  3.   ADDRINT ea = (ADDRINT)ip;
  4.   addrdeq_t::iterator it = std::find(write_address.begin(), write_address.end(), ea);
  5.   if ( it != write_address.end() )
  6.     write_address.erase(it);
  7.   fprintf(stderr, "Layer unpacked: %p\n", ip);
  8.   PIN_ApplicationBreakpoint(ctxt, tid, false, "Layer unpacked!");
  9. }

OK, we have our simple unpacker, it’s time to compile it, execute this PIN tool with the -appdebug command line switch, connect from IDA to PIN and let the application run. When the breakpoint is hit, the application (Skype in this case) is unpacked and we can take a memory snapshot. In the terminal where we execute the command we will see something like this:


$ ./pin -appdebug -t source/tools/MyPinTool/obj-ia32/pinpack.so -- /path/to/skype
Application stopped until continued from debugger.
Start GDB, then issue this command at the (gdb) prompt:
target remote :47643
0x83d95b9: W 0x840c35f
0x840bc5e: W 0x805c050
0x840bd3f: W 0x8058ed0
Layer unpacked: 0x805c050

And in IDA we will receive an application breakpoint at the entry point with the message “Layer unpacked” displayed in the output window:

Skype finally unpacked with the PIN tool "pinpack"

Skype finally unpacked with the PIN tool “pinpack”

And that’s all! We have a working “write and exec” unpacker in the form of a PIN tool. You can download the source code of the unpacker here.

Extra

What I really wanted to do before writing the PIN tool was to get a classes diagram of the Skype application. Now that the application is unpacked in IDA we can easily do it (after taking a memory snapshot and re-analysing the whole database). I’ll use the scripts written by Igor Skochinsky released after his RECON conference “Compiler Internals: Exceptions and RTTI”. I modified the script gnu_rtti.py a little to display a classes diagram in a GraphViewer component in IDA (instead of a chooser) that, also, let’s you save the diagram in dot format. You can download my modified version of the script here.

After running this script (go grab a coffee if you do it yourself as it will take a while) the classes diagram will be displayed in the GraphViewer component and we can right click in the graph and select “Export to dot”. The following is the generated classes diagram of Skype rendered with GraphViz:

Skype classes diagram rendered with GraphViz

Skype classes diagram

That’s all! I hope you liked this blog post!

4 thoughts on “A simple PIN tool unpacker for the Linux version of Skype

  1. joxean Post author

    @Taussef Well, if the application protected with Themida doesn’t use a VM you can always dump and analyse the application, or debug it with a patched version of Wine, debug it under a VM, debug it with a userland debugger using anti-antidebugging tricks, etc… Or even write a PIN “generic” unpacker that dumps the process after some number of instructions or after some event happens.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>