The Sad State of Reverse Engineering

1 11 2009

When you look at software engineering as a research field, you can see some pretty serious progress there. There are amazing projects like PyPy and LLVM, massive optimizations in gcc and JIT compilers (HotSpot, Psyco, TraceMonkey). Compared to that, I have the impression that the reverse engineering community did not produce any significant results. What we have is disassemblers, that is to say parsers.

To make things even worse, the more advanced tools used in RE have been created for a totally different purpose (think Pin, VEX, QEMU, Bochs, virtualization…). Some nice works are being performed by folks like Sean Heelan, Silvio Cesare, the Sogeti R&D team (metasm, fuzzgrind) and the BitBlaze team (TEMU, Vine). But overall I can see no open, community-driven, formally sound approach. The tools are either not FOSS, limited in scope, or just not-that-reusable.

There is a number of potential factors to explain the situation:

  • reversers are not developers (this, I think, is a big factor)
  • reversers are solitary, basement programmers (not to mention cheese pops and japanese tentacle porn)
  • the complexity of x86 + Windows makes the entry cost too high for academics

We are therefore left with a research niche with virtually no academics, little to no developer community, that still pumps some big bucks. The only player left is the security industry, i.e. corporations which have absolutely no incentive to solve the problem.

Did I miss something, or is the picture really that grim?





Stop the Bullshit, People

29 10 2009

Here is the top 5 list of bad ideas that show up every time you discuss malware or desktop security. These ideas are so bad that they get you sucked into a depressingly bad exchange of stupid arguments. So please, stop using them. Or else I’ll kick you in the nuts.

 

“Yeah sure it works… if there’s no vulnerability in it lol”
That, sir, is a tautology. Besides, with this kind of argument, you can quickly infer that nothing actually works.

“Yeah your technique is nice and all, but there’s no way it’s going to be included in mainstream computers (i.e. Windows)”
This is such a bad idea, that I’m not even going to comment on it.

“Your anti-malware technique will not work in cases X and Y”
Of course it won’t. We only have informal definitions of malware, so basically every anti-malware scheme is based on heuristics (i.e. sometimes they work, sometimes not)

“You can’t ask the user to make informed decisions”
As stated above, we have no automatic way to decide if actions are malicious or not. So of course at some point we’ll have to ask the user. Just because the Vista UAC sucked does not mean all ask-the-user schemes suck.

“I don’t care about malware, I’m not running Windows”
Deep inside you, you know that there is no secret sauce in other OSes that make them magically immune to malware, don’t you?

 





Differential Reversing

2 10 2009

I love this [dion.t-rexin.org]: a known technique with a clean, elegant and almost free approach. The idea is to find interesting input-dependent spots in binaries:

  • first, instrument the binary and record a hit trace (basic block granularity is enough) for a base input and a trigger input
  • then, compute the difference between the hit traces
  • finally, highlight the differences in a disassembler, and plug a wetware to analyse the result

Dion does (1.) with a pintool, (2.) with a python script and (3.) with IDAPython. Sweeeet :)





A new visualization for packed and self-modifying programs

21 09 2009

I have been working with my PhD supervisor on a dynamic typing system to detect and visualize the temporal evolution of self-modifying programs (it’s not as complicated as it sounds). The typing system works as follows:

  • each memory address has a read, write and execution level (r, w, x)
  • initially, every memory address begin with type (0, 0, 0)
  • when an address with type (r, w, x) is executed, its type becomes (r, w, w+1)
  • when an instruction with type (r1, w1, x1) reads a memory address with type (r2, w2, x2), the target address type becomes (x1, w2, x2)
  • when an instruction with type (r1, w1, x1) writes to a memory address with type (r2, w2, x2), the target address type becomes (r2, x1, x2)

With that we can get a trace from a program (with DBI, an emulator, a debugger, whatever) and see what is executed (execution level >= 1). By construction, if we have code with an execution level of 2, it means that it has been written by the program itself before being executed, therefore it is self-modifying code.

Again by construction, if we see code with an execution level k+1, it means that it has been written by code at level k. Hence we can precisely distinguish between different layers of code (in our jargon, different code waves)

Now we can detect some interesting properties based on the type of memory addresses:

  • if an address has been read, written and then executed (RWX), we label it decrypted
  • if an address has only been written and executed (WX), we label it blind write
  • if an address has been executed and then read (XR), we assume there has been an integrity check
  • if an address has been executed and then written (XW), we assume the code has been scrambled (supposedly as an anti-memory-dump technique)

Therefore we have a way to trace different layers of code, and some relations between the layers (decryption, blind writes, integrity checking and code scrambling). This gives us the following visualization for some packers:

upx-hostnamemolebox-hostnamepec2-hostnameyp-1allaplepelock-hostnameacprotect-hostnametelock-hostname

Note 1: thanks to Silvio Cesare for providing the packed samples

Note 2: we are going to present all this stuff at Malware (Montréal) with Jean-Yves Marion and Wadie Guizani, and at Deepsec (Vienna)





A look at anti-virtualization in malware samples

21 09 2009

In previous posts, I described PuppetMaster, a way to dynamically detect and control CPU-based VMM detection methods in malware samples. We ran it on 2 sets of malware samples, and here are the results.

1. 60k samples from a Nepenthes honeypot

  • 62498 samples on the honeypot
  • 59554 of them being executable files
  • 48404 were analysed “correctly”
  • 13409 samples were terminated due to a 2 minutes timeout

The number of samples trying to detect virtualization is surprisingly low:

  • 71 (0.15%) binaries used at least one anti-virtualization technique
  • 65 (0.13%) binaries used the SIDT anti-virtualization technique
  • 0 (0.00%) binaries used the STR anti-virtualization technique
  • 0 (0.00%) binaries used the SLDT anti-virtualization technique
  • 0 (0.00%) binaries used the SGDT anti-virtualization technique
  • 14 (0.03%) binaries used the VMware channel anti-virtualization technique

2. 25k samples from uh… somewhere

These samples were shared by Paul Royal, so thanks Paul :)

  • 25118 samples
  • 23104 of them being executable files
  • 18670 were analysed “correctly”
  • 8298 samples were terminated due to a 2 minutes timeout

Again, the number of samples trying to detect virtualization is very low:

  • 117 (0.63%) binaries used at least one anti-virtualization technique
  • 56 (0.30%) binaries used the SIDT anti-virtualization technique
  • 0 (0.00%) binaries used the STR anti-virtualization technique
  • 2 (0.01%) binaries used the SLDT anti-virtualization technique
  • 6 (0.03%) binaries used the SGDT anti-virtualization technique
  • 58 (0.31%) binaries used the VMware channel anti-virtualization technique

Conclusion

There are a few potential reasons why the numbers are so low:

  1. the samples used other techniques that we do not support (such as detecting the VMware tools, or hardware version)
  2. or the samples we got are really not representative of malware samples in the wild. Indeed, our 60k samples contain mostly Allaple samples.
  3. or anti-virtualization techniques are not that common in actual malware samples…

It would be interesting to run the test on better malware repositories, unfortunately this is not something obvious to get our hands on. So if you have a big malware repo ready to be dissected, and you would like to share them with an academic lab for free, I’d be glad to hear from you: reynaudd at loria dot fr.





Do We Really Need Malware Analysis?

15 09 2009
Recently I’ve been wondering, how is malware analysis different from traditional program analysis? The fundamental reason is that programs can generally self-modify themselves. There is a direct consequence: with malware we have to admit that we don’t have static access to the program listing (thus preventing standard program analyses). And since turning self-modifying code (SMC) into normal code is undecidable, we end up only with technical (i.e. partial) solutions. This is why virtually every paper on malware analysis will only be a report on how a given technology/implementation is better/faster/stronger than the others.
This has a corollary too: since we have only partial solutions, malware authors actively implement techniques to defeat our implementations. This opens a sub-research field: the production of techniques to defeat the analysis-defeating techniques. Yes, there is some irony in this, for instance this about packing -> emulation-based unpacking -> anti-emulation techniques -> other-wonderful-unpacking-techniques…
Now, you might wonder, how did we get into this quagmire? As Schneier (http://www.schneier.com/blog/archives/2007/05/do_we_really_ne.html) pointed it out before me, this is an accident – a historic by-product of the way the IT industry evolved. The x86 architecture allowed self-modifying code, and operating systems did nothing to prevent or regulate that. And bam, a research niche was born.

omgwtfRecently I’ve been wondering, how is malware analysis different from traditional program analysis? The fundamental reason is that programs can generally self-modify themselves. There is a direct consequence: with malware we have to admit that we don’t have static access to the program listing (thus preventing standard program analyses). And since turning self-modifying code into normal code is undecidable, we end up only with technical, partial solutions. This is why virtually every paper on malware analysis will only be a report on how a given technology/implementation is better/faster/stronger than the others.

This has a corollary too: since we have only partial solutions, in some cases they don’t work. And malware authors actively exploit that fact, by implementing techniques to defeat our implementations. This opened a sub-research field: the production of techniques to defeat the analysis-defeating techniques. Yes, there is some irony in this, for instance think about packing -> emulation-based unpacking -> anti-emulation techniques -> other-wonderful-unpacking-techniques…

Now, you might wonder, how did we get into this quagmire? As Schneier pointed it out before me, this is an accident – a historic by-product of the way the IT industry evolved. The x86 architecture allowed self-modifying code, and operating systems did nothing to prevent or regulate that. And bam, a research niche was born.





Automatic Exploit Generation

11 09 2009

One of the best MSc dissertations I’ve read:

“We present a novel algorithm that integrates data-flow analysis and a decision procedure with the aim of automatically building exploits. The exploits we generate are constructed to hijack the control flow of an application and redirect it to malicious code.

Our algorithm is designed to build exploits for three common classes of security vulnerability; stack-based buffer overflows that corrupt a stored instruction pointer, buffer overflows that corrupt a function pointer, and buffer overflows that corrupt the destination address used by instructions that write to memory. For these vulnerability classes we present a system capable of generating functional exploits in the presence of complex arithmetic modification of inputs and arbitrary constraints. Exploits are generated using dynamic data-flow analysis in combination with a decision procedure.”

(yes, I am now Schneier-blogging)





Merry Christmas everybody!

20 07 2009

books





Puppetmaster Strikes Back

13 07 2009

Vincent Mussot and I implemented new virtualization counter-countermeasures in puppetmaster. This time we can detect and thwart 6 tests out of 7 in ScoopyNG. In addition to the SIDT test, we counter the SLDT, SGDT and STR techniques in a similar way: instrument the binary until one of these instructions is found, intercept the memory address it writes to and patch the return value.

The 2 other tests use the VMware backdoor (the in eax instruction, with a magic value in eax and edx). We thwart it by detecting the backdoor trigger, and changing the magic values (this way an exception is raised, as if there were no backdoor). We then restore the magic values to add a little bit of stealth.

You can compile and run puppetmaster with the latest version (27887) of Pin on Windows and Linux. The makefile for the free version of Visual C++ is given below.

// puppetmaster.cpp
// Usage: pin -t <puppetmaster.dll> -- <binary> [arguments]\n
// Currently supported anti-virtualisation techniques: SIDT, SLDT, SGDT, STR, VMWare channel    
// works with pin-2.6-27887-msvc9

#include <string>
#include "pin.H"

int needRestore = 0;

VOID poisonSIDT(ADDRINT memIp) {
 char *data = (char *)memIp;
 unsigned int* m = (unsigned int *)(data+2);
 *m = 0xd00dbeef; // if ((idt_base >> 24) == 0xff) -> vmware detected
}

void poisonSLDT(ADDRINT memIp) {
 char *data = (char *)memIp;
 unsigned int* m = (unsigned int *)(data);
 *m = 0xdead0000; // if (ldt_base != 0xdead0000) -> vmware detected
}

void poisonSGDT(ADDRINT memIp) {
 char *data = (char *)memIp;
 unsigned int* m = (unsigned int *)(data+2);
 *m = 0xdeadbabe; // if ((gdt_base >> 24) == 0xff) -> vmware detected
}

void poisonSTR(ADDRINT memIp) {
 char *data = (char *)memIp;
 unsigned int* m = (unsigned int *)(data);
 *m = 0xbebaadde; // if ((mem[0] == 0x00) && (mem[1] == 0x40)) -> vmware detected
}

void poisonVMWareChannel() {
 unsigned int EAX_save;
 unsigned short int DX_save;
 __asm {
 mov EAX_save, eax
 mov DX_save, dx
 }
 if ((EAX_save == 0x564D5868) && (DX_save == 0x5658)){
 __asm {
 mov dx, 0x0004
 }
 needRestore = 1;
 }
 else needRestore = 0;
}

void restoreVMWareChannel() {
 if (needRestore == 1) {
 __asm {
 mov eax, 0x564D5868
 mov dx, 0x5658
 }
 needRestore = 0;
 }
}

VOID Instruction(INS ins, VOID *v) {
 string buffer = INS_Disassemble(ins);
 if (buffer.substr(0,4) == "sidt")
 INS_InsertCall(ins, IPOINT_AFTER, (AFUNPTR)poisonSIDT, IARG_MEMORYWRITE_EA, IARG_END);
 else if (buffer.substr(0,4) == "sldt")
 INS_InsertCall(ins, IPOINT_AFTER, (AFUNPTR)poisonSLDT, IARG_MEMORYWRITE_EA, IARG_END);
 else if (buffer.substr(0,4) == "sgdt")
 INS_InsertCall(ins, IPOINT_AFTER, (AFUNPTR)poisonSGDT, IARG_MEMORYWRITE_EA, IARG_END);
 else if (buffer.substr(0,3) == "str")
 INS_InsertCall(ins, IPOINT_AFTER, (AFUNPTR)poisonSTR, IARG_MEMORYWRITE_EA, IARG_END);
 else if (buffer.substr(0,6) == "in eax") {
 INS_InsertCall(ins, IPOINT_BEFORE, (AFUNPTR)poisonVMWareChannel, IARG_END);
 INS_InsertCall(ins, IPOINT_AFTER, (AFUNPTR)restoreVMWareChannel, IARG_END);
 }
}

int main(int argc, char * argv[]) {
 PIN_Init(argc, argv);
 INS_AddInstrumentFunction(Instruction, 0);
 PIN_StartProgram();    
 return 0;
}

Here is the Nmakefile for the Nmake utility:

# Nmakefile
######################################################################################
# This is the NMAKE file for building and testing PIN toos contained in one of the
# subdirectories of the PinTool project or PIN kit.
#
# For description of targets and options, see Nmakefile in the root directory.  
######################################################################################

!if "$(PIN_HOME)"==""
PIN_HOME=..
!endif

# Define tools to be buit and tested
######################################################################################
COMMON_TOOLS=puppetmaster.dll

# Include building and testing rules from the root Nmakefile.
######################################################################################
INCLUDE_SUB_RULES=1
!INCLUDE $(PIN_HOME)\Nmakefile

Build instructions:

  • download and install Pin and Visual C++
  • under $PIN_HOME\source\tools, create a puppetmaster directory
  • put puppetmaster.cpp and the Nmakefile in that directory
  • from that directory, run ..\nmake.bat puppetmaster.dll

Now you can run it and use your newly acquired ninja skills on ScoopyNG:

C:\pin-2.6-27887-msvc9-ia32_intel64-windows\source\tools\puppetmaster>pin -t obj-ia32\puppetmaster.dll -- ScoopyNG.exe
####################################################
::       ScoopyNG - The VMware Detection Tool     ::
::              Windows version v1.0              ::

[+] Test 1: IDT
IDT base: 0xd00dbeef
Result  : Native OS

[+] Test 2: LDT
LDT base: 0xdead0000
Result  : Native OS

[+] Test 3: GDT
GDT base: 0xdeadbabe
Result  : Native OS

[+] Test 4: STR
STR base: 0xdeadbabe
Result  : Native OS

[+] Test 5: VMware "get version" command
Result  : Native OS

[+] Test 6: VMware "get memory size" command
Result  : Native OS

::                   tk,  2008                    ::
::               [ www.trapkit.de ]               ::
####################################################

Note: we do not support the last test in ScoopyNG because Pin does not currently support far rets in different code segments. But as far as I can tell the bug that this last test uses has been patched, I was not able to trigger it. It should probably be considered deprecated.





Command your shiny Android in Python

20 06 2009

I am the happy owner of an Android phone. I love it. But what I love even more, is rolling some sweet Python on my precious.

In case you have a vanilla phone like I do, you will need to follow these steps:

  • download and install the Android SDK for your platform
  • add the SDK tools to your system path, for instance on Windows run this in a shell:
> set PATH=%PATH%;path_to_android_sdk\tools
  • activate USB debugging on your phone (Parameters > Applications > Development > USB Debugging)
  • setup your system to detect your device as explained here (on Windows, it means installing the usb driver from the SDK)
  • check that your phone is detected correctly by running adb get-state (if everything is ok, the result should be ‘device’)
  • check out the Android Scripting Environment svn repository on your computer (see this page)

Your system is almost configured, let’s install the necessary applications on the phone:

  • install the Android Scripting Environment on your phone
  • (optionally) install the Text-to-Speech library (by Charles Chen) on your phone if you want to use it in your scripts, via the Android Market
  • run ASE on your phone, type menu and start a Python shell, you will notice a line like ‘export AP_PORT = “49508″‘.
  • setup port forwarding on your system with the value you just noted:
> set AP_PORT=49508
> adb forward tcp:%AP_PORT% tcp:%AP_PORT%
  • go to the ASE directory on your computer containing the android.py module
> cd path_to_ASE\python\ase
  • run your favorite Python 2.6 interpreter and enjoy the magic:
> python
Python 2.6 (r26:66721, Oct  2 2008, 11:35:03) [MSC v.1500 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import android
>>> droid = android.Android()
# now your phone is ready to acknowledge your awesomeness
>>> droid.speak('hello, my master')
{u'result': None, u'id': 2, u'error': None}
# or alternatively, if you don't have the TTS library installed:
>>> droid.makeToast('hello, my master')
{u'result': None, u'id': 1, u'error': None}

Now you have all the information you need to start exploring the API and roll  more interesting examples.

droid.makeToast