Honeynet Scan of the Month 33 Analysis

"Evil Has No Boundaries !"

Author: Chris Eagle, cseagle at nps d0t edu

Answers to the challenge questions can be found here

Analyzing 0x90.exe, for Scan of the Month 33

The Honeynet Scan of the Month Challenge for November 2004 invited participants to analyze a program that had been protected by a variety of anti reverse-engineering techniques. Information given in the challenge statement was:

"This month's challenge is to analyze an unknown binary, in an effort to reinforce the value of reverse engineering, and improve (by learning from the security community) the methods, tools and procedures used to do it. This challenge is similar to SotM 32. However, this binary has mechanisms implemented to make the binary much harder to analyze, to protect against reverse engineering."


"All we are going to tell you about the binary is that it was 'found' on a WinXP system and has now be sent to you for analysis. You will have to analyse it in-depth and get as much information as possible about its inner working, and what is the goal of the binary."

Background: My preferred method of approaching a malware analysis is to do some preliminary investigation of the binary using simple tools such as file and strings to develop an idea of what to expect and then move into a purely passive analysis phase. My analysis platform of choice is a Windows workstation with IdaPro and cygwin utilities installed.  For obfuscated code, I make extensive use of the x86 emulator plugin for IdaPro.

Obtaining the binary: The file 0x90.exe was downloaded from the Honeynet site and its integrity verified using both the md5sum sum tool available with cygwin. The file utility verified that 0x90.exe was indeed a Windows exectable:
$ md5sum 0x90.exe
7daba3c46a14107fc59e865d654fefe9 *0x90.exe

$ file 0x90.exe
0x90.exe: MS-DOS executable (EXE), OS/2 or MS Windows
Preliminary analysis: Following confirmation that the file was a Windows executable, the strings utility was used to see if it could uncover anything interesting. With any Windows binary, it is always a good idea to scan for both 7-bit ASCII and 16 bit Unicode strings. The following ASCII strings were found (edited to show only initially interesting strings):
$ strings -a 0x90.exe
This program must be run under Win32
You really thought you would find strings eh? ;-)
Scan of the month coded by Nicolas Brulez / Digital River
No Unicode strings were discovered in the binary.

The most interesting fact learned from all of this is that the program author is probably aware that some reverse engineers hope to deduce capabilities of a binary simply by analyzing the output of the strings command.  In this case, the author seems to be taunting such analysts a bit.

A More Detailed Look: The next thing I always do with a malware specimen is load it up in IdaPro. I am generally interested in answering the following questions:
I am interested in these questions just in case I decide to actually run the program as part of testing it. Running the program is something I try to avoid entirely as an analysis technique. I generally only run malware sample once I know what to expect of them and when I can be sure that I can control them. In any case, if I do run them, they are always run on an isolated network for the purpose of confirming behaviors discovered during the static analysis.

Anti reverse-engineering measures: Loading the binary inito IdaPro yielded interesting results.  The file took and extremely long period of time for Ida to load and analize and the resulting .idb database file and resulted in the following error dialog:

Ida generates the following messages:
Detected file format: Portable executable for IBM PC (PE)
0. Creating a new segment (00DE1000-00DE2000) ... ... OK
1. Creating a new segment (00DE2000-00E27000) ... ... OK
2. Creating a new segment (00E27000-F0D21DFF) ... ... OK
3. Creating a new segment (00E28000-00E29000) ...
Additional segment (00E29000-F0D21DFF) ...
4. Creating a new segment (00E29000-F0D21DFF) ... ... OK
... OK
and the problem occurs during number 2 above.  The first thing that jumps out is the size of the segment which warrants further investigation.  Saving the Ida database again takes quite a bit of time and results in a database file that is about 523Mb in size.  Running dumpbin is no help as it simply chokes:
      C:\> dumpbin 0x90.exe
Microsoft (R) COFF Binary File Dumper Version 6.00.8447
Copyright (C) Microsoft Corp 1992-1998. All rights reserved.

Dump of file 0x90.exe


0x90.exe : fatal error LNK1106: invalid file or disk full: cannot seek to 0x79520898
The next step I took was to load the binary up in a PE editor.  For this I used PE Tools which was able to list the sections contained in the binary as shown below:

Here again we note the unusual size of the NicolasB section and the fact that it overlaps the .idata section.  The fact that both NicolasB and idata begin at file offset leads to the conclusion that the NicolasB section probably should occupy zero bytes in the file and the PE editor is used to make this change as seen below:

With this change made, the modified file loads cleanly in IdaPro.  One challenge faced by users of IdaPro when dealing with self modifying code is that without running the program, it is difficult to observe the changes the program would make to itself at runtime.  One means to overcome this limitation is to reverse engineer enough of the program to understand how the self-modifying portion of the code behaves and then create a script that will modify the Ida database in the same way as if the program was actually running.  This requires time for both understanding the code and authoring/debugging an IDC script.  I prefer to make use of an alternate method which makes use of an x86 instruction emulator plugin available for IdaPro.  Using the plugin, a user can step through instructions in the database.  Any instructions that modify memory locations represented by the database cause the emulator to interact with and modify the database directly.  By using this technique, the program itself is used to modify the Ida database eliminating the need to fully comprehend the program's actions and author a script to carry out those actions. 

Using the plugin and beginning at the start function,  the first anti-reverse engineering measure encountered is the use of Windows exceptions to perform some interesting register manipulations.  The code listing to the right shows the calling of a subroutine named sub_DE22D7.  At the call, the return address 0xDE228F is placed in the stack and becomes part of the exception handler configured in the first 3 lines of sub_DE22D7.  The subroutine utilizes the rdtsc instruction to read the CPU's time stamp counter just prior to generating an exception with a null pointer dereference at 0xDE22E5.  This causes a memory access violation exception to be generated and control transfers to the exception handler at 0xDE228F. By accessing the passed CONTEXT data structure the handler zeroizes all of the CPU's debug registers (0xDE229C-0xDE22A8) which has the effect of zeroing out any breakpoints specified in those registers.  At location 0xDE22B2, the handler retrieves the saved time stamp counter before invoking rdtsc a second time to get the current time stamp.  The comparison at location 0xDE22C3 is a check to see whether the time stamp has incremented by an unusually large amount which will be the case if the program has been suspended by a debugger.  In such a case, action is taken to modify the saved eip before returning from the exception (this results in the program crashing eventually).

The program runs through a number of similar exception cycles before entering the first of 178 xor decoding loops which wrap the program being program being protected like the layers of an onion.  The problem with running code like this in a debugger is when and where to stop.  It is difficult to choose a location to set a breakpoint because with self modifying coded you can never be sure that a particular location will be reached.  Additionally, placing an int 3 breakpoint is next to impossible because the value (0xCC) may be modified by a decoding loop before it is hit.  After unwrapping several of these layers using the emulator plugin, a pattern began to emerge.  Each layer that was decoded contained yet another decoder to decode yet another inner layer, and the same algorithm was used by each decoding layer.  The major question was "how deep do these layers go?"

In order to fully automate the process of getting past these onion layers I developed an IDC script named unroll_all.idc to handle all of the unrolling. The first version of the script was designed to decode successive layers until a failure occurred, then report the number of layers successfully decoded.  That number was used as a loop termination condition in the final version of the script.  The layers were so deep and the amount of data being xored so much (about 256k bytes per pass) that the script took 15 minutes to execute.  The script actually handled 172 of the layers at which point something changed that caused it to fail.  The emulator was used to get past the final 3 layers.  When an obfuscated binary has been completely decoded, it is often posible to view strings that failed to appear initially.  For this reason I ran strings ono the binary once again (using the IdaPro strings facility) but unfortunately, noted no new strings.

Following the last decoding loop, the program transfers to location 0x00DE8653 (Which I dubbed VM_Setup, see Note 1) where two exception/exception handling blocks are executed in quick succession before control jumps across a block of six dword variables to location 0x00DE874E (which I dubbed VM_Entry). 

From this point on, the code takes on a bit of a different style.  The six dword memory locations starting at 0x00DE8736 are initialized with successive four byte chunks of the following string:
"Evil Has No Boundaries !"
The instructions that perform each initialization are each sandwiched between a popa/pusha sequence to restore and save all registers on the stack.  In between the end of one action (pusha) and the beginning of the next action (popa), an average of 170 decoy instructions are inserted to attempt to distract an analyst.  The key to analyzing the program at this point is to ignore virtually everything that falls between a pusha and a succeeding popa and concentrate only on what falls between a popa and a subsequent pusha.  The only interesting code that falls outside of popa/pushapairs is the code that causes an exception to be generated.  At this point in the program, exception sequences are all initiated using a call instruction to call a subroutine 52 bytes ahead of the call statement itself.  This can be seen in the image below.The use of exceptions is continued but from this point forward the exception handlers take on a slightly different form.  Checks of the time stamp counter are discontinued but debug registers continue to be zeroized and eip is modified based on values contained in the saved eax and edi registers.  The effect is to cause the exception handler to return to a location other than that immediately following the location at which the exception was generated.  The code sequence for a typical exception generation and handler is also shown below:

In order to follow execution of the program following each exception, it is necessary to understand where eax and edi take on their values prior to each exception.  The following code sequence is representative of the actions that take place prior to the generation of each exception (consider this sequence to be the fetch phase of a CPU's basic fetch-decode-execute loop.  The CPU in this case is the NVM, see Notes):
DATA:00E14C64                 popa
DATA:00E14C66 add esi, 7
DATA:00E14C69 movzx eax, byte ptr [esi]
DATA:00E14C6C mov edi, ds:off_E1B991[eax*4]
DATA:00E14C73 movzx eax, byte ptr [esi+1]
DATA:00E14C77 pusha
DATA:00E14C78 call sub_E14CB1
In the sequence above, we see esi modified, then edi loaded by indexing into a table at 0x00E1B991 and ultimately eax is loaded with the byte value at [esi+1].  The call statement indicates that an exception is about to be generated which will result in a transfer to a program location dictated by the values in eax and edi as mentioned previously.  Using the emulator plugin to step through the program it was discovered that yet another xor decoding loop had been entered to decode the 525 bytes in the range 0x00E1BA65-0xE1BC72.  Following enough emulation to determine the pattern (see Note 2) followed by the decoder and, because the emulator plugin currently requires manual intervention to deal with exceptions I opted to write another IDC script to mimic this decoding loop.  Hoping that this might be the final decoding loop, I again ran strings (within IdaPro) and noted the appearance of the new string:
"Please Authenticate!\r\n"
Perhaps implying that some form of authentication would be requred to get the program to do anything.  The repeated occurrence of the code sequence above revealed the the actions of the program are dictated by the contents of esi and the values contained in the table located at 0x00E1B991.  Because  esi appeared to be operating as an instruction pointer it finally dawned on me that the program was actually a software CPU (i.e. a virtual machine) with an instruction pointer implemeneted by esi and a table of instructions based at 0x00E1B991. I dubbed this virtual machine the NVM and the language that it interprets I called Nico.  At this point I elected to completely analyze the table at 0x00E1B991 to see what operations the NVM was capable of performing and attempt to understand the structure of Nico byte code.  Based on this analysis, the NVM appears to have 5 general purpose registers and a status register.  The NVM is capable of performing basic arithmetic and logical operations, stack operations, comparisons, branching, and function calls.  Nico byte code is detailed here.  The 525 bytes decoded in locations 0x00E1BA65-0xE1BC72 turned out to be Nico Byte Code to be interpreted by the NVM. Here is the disassembly of this Nico program along with a Nico byte code disassembler I wrote.

Understanding that a Nico program was being interpreted and resuming analysis with the emulator plugin, the Nico program was observed to do the following:
  1. Obtain a pointer to the command line with the Windows function GetCommandLineA
  2. Locate the first space character in the command line
  3. Perform some authentication by manipulating the characters of argv[1]
    1. The first 4 bytes of argv[1] are treated as a 32 bit int and after undergoing various addition and subtraction operations is compared against an internally generated key value.  By following the changes, it was discovered that the first four characters of argv[1] must be "13DN" or "13Dn" in order to match the internally generated key value.
    2. The last 4 bytes of argv[1] must conform to the following restrictions
      1. argv[1][4] + argv[1][6] == 0xA8
      2. argv[1][5] + argv[1][7] == 0x8F
      3. argv[1][4-5] gets treated as a 16 bit short and xor'ed into location 0x00E1BB54 which initially contains "GB".
  4. If authentication is successful, the program enters a loop to decode an additional 73 characters which yield the following message:
  5. "Welcome...\n"
    "Exploit for it doesn't matter 1.x Courtesy of Nicolas Brulez"
  6. Once the message has been decoded, the Nico program transfers control to location 0x00E1BB54.  The next two program bytes at this location were changed during the authentication phase (see 3.b.iii above).  These two bytes are used during the fetch phase of the NVM as described previously.  For this reason, it is important for the result of the xor operation in 3.b.iii yield valid index values into the NVM instruction table at 0x00E1B991.  Based on the structure of this table, the first byte needs to be in the range 0-5 and the second byte needs to be in the range 0-0xB depending on the value of the first byte.  Based on analysis of the NVM instructions that follow, it appears that the instruction at 0x00E1BB54 requires a one byte operand.  This limits the number of possible values for the first two bytes as they must form an index to an instruction that takes only one operand byte.  I elected to try a NOP operation which is represented by index values 2 and 9.  We can finally derive the last four characters of the authentication key.  In this case the result of operation 3.b.iii must be 2 and 9.  'G' ^ 2 == 'E' and 'B' ^ 9 == 'K', 0xA8 - 'E' == 'c' and 0x8F - 'K' == 'D'.  In this case, the final authentication key is 13DNEKcD or 13DnEKcD. See Note 3 for additional password possibilities.
  7. And finally, the Nico program invokes the printf function to print the newly decoded message to the console before exiting via the ExitProcess function.
Behavioral Observation: At this point sufficient static analysis had been performed to develop a good idea of the expected behavior of the program. The program was finally executed in a VMWare environment to verify that it behaved as expected.  When run with no command line arguments or with an invalid password, the program spit out the message:
"Please Authenticate!"
and then quit..  When run with a valid password, the program spit out the message:
"Exploit for it doesn't matter 1.x Courtesy of Nicolas Brulez"
before quiting.  There was no indication that the program contained any other functionality.  In particular, there is no indication that the binary takes any steps to permanently install itself and thus no steps for removal of the binary need to be developed.

Virtual Machine Notes:
  1. Control arrives at 0x00DE8653 following the decoding of the last major obfuscating.  At this point, the code that has finally been decoded actually represents a virtual machine interpreter and instruction 0x00DE8653 is the entry point to the virtual machines initialization code.  I dub this virtual machine the NVM and the programming language that it interprets Nico
  2. This decoding loop was actually being performed by a small piece of Nico byte code running on the newly extracted NVM virtual machine.
  3. The computation of a valid password described above causes a Nop instruction to be written into the Nico byte code at location 0x00E1BB54.  There are other 3 byte instructions that could be used instead which each lead to alternative password options.  Other passwords which result in successful authentication include:
    1. "1D3NG@aO"  or "1D3nG@aO"
    2. "1D3NGAnN"  or "1D3nGAaN"
    3. "1D3NEAcN"  or "1D3nEAcN"
    4. "1D3NEFcI"  or "1D3nEFcI"
    5. "1D3NEGcH"  or "1D3nEGcH"