example_string
variable to decode the string of interest.decodestring
function that is part of the Python base64 library.encrypt_Init
.We know that when we reach address 0x40122A, the encryption has been completed.
We know several of the variables and arguments that are used in the encryption function. These include the counter and two arguments: the buffer (lpBuffer
) to be encrypted or decrypted and the length (nNumberOfBytesToWrite
) of the buffer.
We have an encrypted file, the malware itself, and the knowledge of how its encryption function works. Our high-level goal is to instrument the malware so that it takes the encrypted file and runs it through the same routine it used for encryption. (We are assuming based on the use of XOR that the function is reversible.) This high-level goal can be broken down into a series of tasks:
Set up the malware in a debugger.
Prepare the encrypted file for reading and prepare an output file for writing.
Allocate memory inside the debugger so that the malware can reference the memory.
Load the encrypted file into the allocated memory region.
Set up the malware with appropriate variables and arguments for the encryption function.
Run the encryption function to perform the encryption.
Write the newly decrypted memory region to the output file.
In order to implement the instrumentation to perform these high-level tasks, we will use the Immunity Debugger (ImmDbg), which was introduced in . ImmDbg allows Python scripts to be used to program the debugger. The ImmDbg script in is a fairly generic sample that has been written to process the encrypted files that were found with the malware, thereby retrieving the plaintext.
immlib
is the Python library, and the immlib.Debugger
call provides programmatic access to the debugger. The open
calls open files for reading the encrypted files and writing the decrypted version. Note that the rb
option on the open
commands ensures that binary characters are interpreted correctly (without the b
flag, binary characters can be evaluated as end-of-file characters, terminating the reading prematurely).The imm.remoteVirtualAlloc
command allocates memory within the malware process space inside the debugger. This is memory that can be directly referenced by the malware. The cfile.read
command reads the encrypted file into a Python buffer, and then imm.writeMemory
is used to copy the memory from the Python buffer into the memory of the process being debugged. The imm.getRegs
function is used to get the current register values so that the EBP register can be used to locate the two key arguments: the memory buffer that is to be decrypted and its size. These arguments are set using the imm.writeLong
function.
The actual running of the code is done in two stages as follows, and is guided by the setting of breakpoints using the imm.setBreakpoint
calls, the setting of EIP using the imm.setReg("EIP",location)
calls, and the imm.Run
calls:
The initial portion of code run is the start of the function, which sets up the stack frame and sets the counter to zero. This first stage is from 0x004011A9 (where EIP is set) until 0x004011b7 (where a breakpoint stops execution).
The second part of the code to run is the actual encryption loop, for which the debugger moves the instruction pointer to the start of the cryptographic initialization function at 0x004011f5. This second stage is from 0x004011f5 (where EIP is set), through the loop one time for each byte decrypted, until the loop is exited and 0x0040122a is reached (where a breakpoint stops execution).
Finally, the same buffer is read out of the process memory into the Python memory (using imm.readMemory
) and then output to a file (using pfile.write
).
Actual use of this script requires a little preparation. The file to be decrypted must be in the expected location (C:\encrypted_file). In order to run the malware, you open it in ImmDbg. To run the script, you select the Run Python Script option from the ImmLib menu (or press ALT-F3) and select the file containing the Python script in . Once you run the file, the output file (decrypted_file) will show up in the ImmDbg base directory (which is C:\Program Files\Immunity Inc\Immunity Debugger), unless the path is specified explicitly.
In this example, the encryption function stood alone. It didn’t have any dependencies and was fairly straightforward. However, not all encoding functions are stand-alone. Some require initialization, possibly with a key. In some cases, this key may not even reside in the malware, but may be acquired from an outside source, such as over the network. In order to support decoding in these cases, it is necessary to first have the malware properly prepared.
Preparation may merely mean that the malware needs to start up in the normal fashion, if, for example, it uses an embedded password as a key. In other cases, it may be necessary to customize the external environment in order to get the decoding to work. For example, if the malware communicates using encryption seeded by a key the malware receives from the server, it may be necessary either to script the key-setup algorithm with the appropriate key material or to simulate the server sending the key.