Using Existing Malware to Save You Time



Category: Unit 42

As a malware analyst and reverse engineer, I am often faced with reversing some type of cryptography algorithm or decompression routine that can take hours, days, months, or even years to fully understand.  I am often tasked with understanding:  What is the blob of data that is used by the malware?

Answering the “what” is always the challenging part and I usually don’t have a lot of time to fully reverse some crypto routine.  I simply need to answer the question:  This data is a configuration file that is used by the malware to do XYZ or I simply don’t know what this data is (I don’t like to give this answer, but it happens).

There are several different approaches one can take to decrypt/decompress data from malware.  You can run the malware and dump memory segments (dump strings on each sample afterwards), debug the malware in a debugger, place hooks on decryption/decompression routines and dump return vales, static analysis, etc.  While all these approaches are good and will provide you the desired answers they can be somewhat time consuming. What if you have several blobs of data you need decoded/decompressed?  Wouldn’t it be great if you could take the assembly code directly from the malware’s decompression/decoding routine, put it in a compiler such as Visual Studio, compile it to a dynamic link library (DLL), and then call into it using your favorite scripting language such as Python?  This blog will show a technique that can be used to achieve just this.  A link to the finished tool hosted on Unit 42’s public tools GitHub repository, which decompresses a data blob within Reaver used as a database lookup for API calls and strings can be found here.


The Scenario

For this example, I was tasked with trying to identify the compression algorithm used as part of our recent analysis into the Reaver malware family and determine if the strings within the malware could be decompressed from the binary without running it.  The keyword here being “without”.

During my analysis of the Reaver malware family, it appears to implement a modified Lempel-Ziv-Welch (LZW) compression algorithm.  The decompression algorithm from the Reaver malware for this example was found at address:  0x100010B2 and is approximately 200 lines of assembly.  Decompressed routine example is in Figure 1, below:

Figure 1 Reaver decompression routine

For brevity the entire code from the malware function is not shown.  The important parts to take aware from this are:

  • Calling convention is __thiscall (indicates C++)
  • Function takes five arguments
  • The function is called once from the malware (number of cross references identified in IDA Pro)

Here is what the function looks like when being called:

Figure 2 Calling Reaver decompression routine

Here’s an overview of calling the decompressed function:

  • Clears EAX register, so EAX is zero
  • Pointer to the object is stored in ECX (Thiscall)
  • The three pushes of EAX indicate that the last three parameters to the decompressed routine are always zero.
  • Parameter two is a pointer to a destination buffer.
  • Parameter one is a pointer to the compressed data.

The compressed data is:

Figure 3 Reaver compresed data

For brevity the entire contents of the compressed data are not shown.  The entire size is:  ~45,115 bytes.

Bytes 1-7 (08 00 A5 04 01 12 03) appear to be a magic header for the compression routine and was found in all Reaver malware variants.

Armed with this knowledge we can now turn our focus to the inner working of the decompression routine.

Note:  From here one could simply monitor the return from the call and dump the contents of the destination buffer, which would contain the decompressed data, but this would require running the code from a debugger.  Remember our goal is to not run the sample.

At this point we have enough general information that we can begin to create a DLL, so start up Visual Studio or any compiler that handles compiling assembly (NASM/MASM).  Create a new empty DLL project and add a new header file.  For example, I created a header with the following information:

Figure 4 C Header file

The above code creates a single export named “Decompress” and accepts two arguments.  Why two and not five?  Since the other three arguments will always be zero there is no need to define them.  The return type for our function is a Boolean.

For your source file (.cpp or .c), take the assembly from IDA Pro or your debugger and add it to your source file. Here is what my source file looks like (after I fixed it up):

Figure 5 Our decompression routine

Taking assembly from IDA Pro or a disassembler such as Immunity Debugger isn’t one to one as it does require some work on your part.  Unfortunately, you can’t take the assembly and expect things to just magically work.  One area that requires special attention are the function calls made within your code block.  Each assembly call needs a name (label) and all the code needs to be arranged in the proper calling order, otherwise you will receive unexpected results or crash.  Also, it’s important that you copy the assembly for each function call that is made.  In this sample, I used the word “check” to represent function names or jump locations, as I was quickly working my way through this.

Since LZW encodes data using an index into a dictionary the first thing the decompression routine does is allocate a buffer of memory 16,512 bytes (0x4080) to create the dictionary.  From the assembly, it uses the C++ API malloc to allocate the buffer and then sets the buffer to NULL (this is how malloc works).  A simpler and more efficient way is to use calloc function which reduces the number of instructions and allocates the buffer for you.

We start by coding this in C++ and then switch to Visual Studio inline assembly using the __asm keyword.  The code block within the __asm keyword is where you will place your assembly instructions and make the necessary adjustments; not only for the code to compile, but also to ensure that the stack is aligned properly.  In studying the decompression routine, the following instructions were necessary before we can begin to start executing the decompression routine.

  • Set EBX to zero.
  • Subtract 64 bytes (0x40) from the stack. Necessary to prevent us from overwriting any stack data
  • Save our stack pointer into ESI
  • EDI needs to point to our dictionary buffer created via calloc
  • EAX needs to point to our source data
  • EDX needs to point to our destination buffer

The following nine lines were manually added in-order to satisfy the requirements for the decompression algorithm.  The remaining code was copied directly from Immunity Debugger.

Figure 6 Setting up decompression routine requirements

At this point, all it takes is to update the assembly calls and jumps with meaningful names and arrange them in the correct order.  Now the code should compile and run, but when our routine is finished you must restore the stack back, so it returns to the proper caller in this case Python ctypes.  The following code was added:

Figure 7 Adjusting stack for return

Here we are restoring the stack pointer and base pointer and adding 0x120 or 0x58 to ESI depending if the DLL is a VS debug build or release build.

Now that we have a DLL we can begin to call into it and pass it data via Python and ctypes.  The following Python script uses our DLL to decompresses Reaver data.

The Python script was recently updated to support multiple Reaver variants.  The newer Reaver variants use Microsoft CAB compression as a first layer followed by LZW modified decompression.  The script does the following:

  • Loads our DLL LzwDecompress.dll
  • Attempts to locate the magic signature values for the modified LZW header or Microsoft CAB
  • For the LZW decompression routine creates two string buffers, which are pointers to a buffer. The source buffer is a pointer to the data that needs to be decompressed and the destination buffer is where we will store the decompressed data.
  • Call the export named Decompress and pass it our two parameters
  • Writes the data to a file

The following is an example of the script running:

Malware Reversing

Figure 8 Script decompressing data

The first example is of an older version of Reaver that uses the LZW decompression routine.  The decompressed data is written to a text file that contains the following:

The next example is of a newer Reaver sample that added a layer of compression using Microsoft CAB.

Malware reversing_2

Figure 9 Script expanding CAB file and decompressing data

Here the script found the magic values for Microsoft CAB, expanded the file, read in the expanded file, found the magic value in that file for the decompression routine and wrote the same decompressed data to a text file.


Conclusion

This blog has shown you that by taking the existing Reaver decompression routine straight from assembly, placing it into Visual Studio, compiling it into a DLL, then calling into it via Python saves us a considerable amount of time.  You no longer must reimplement the routine in C or Python as you simply call the routine and pass it the same data as the malware would.  The tradeoff is understanding assembly, stacks and knowing what registers the routine requires. Once armed with that knowledge it’s easy to implement and can be applied to any function within a binary.

Got something to say?

Get updates: Unit 42

Sign up to receive the latest news, cyber threat intelligence and research from Unit42


SUBSCRIBE TO RSS