Reversing Encrypted Callbacks and COM Interfaces
Reversing Encrypted Callbacks and COM Interfaces
Introduction
In this paper, I would like to discuss about viruses which make use of COM Interfaces to implement their
functionality and how we can effectively reverse these binaries.
As an example, I will take a virus, which was recently found in the wild and uses certain interesting
techniques.
For the purpose of clarity and context, I will walk through the code execution flow.
We will also be looking in depth at how the network communication is encrypted before sending it to the
callback server, how the response is decrypted and parsed to extract the malicious binaries.
This paper is targeted towards those who are familiar with malware analysis at the same time those who
have experience with malware analysis might find new techniques to effectively analyze viruses.
Purpose
One of the main reasons I wrote this paper was to explain in depth the different stages involved in viruses
that exchange data with the callback server using encrypted channels.
Most write ups of viruses online, do not discuss these stages. With an understanding of the techniques
used by viruses to secure the exchange of data over network, it will become easier to identify the type of
data exfiltrated from machines and the main purpose of the virus.
From PEiD:
From Section Headers:
If you want to check even further, you can reverse the binary and find the following code section where it
looks for the “Nullsoft Inst” marker:
Now that we know it is an SFX file, we can extract its contents using 7-zip. SFX file makes use of CRC32
and Zlib for compression, which is supported by, 7-zip.
1. rzkxixls.exe
2. setup.dat
3. rs.dat
The dropper will extract these files to the %temp% directory. Once it has extracted these files, it will
create a new process to execute rzkxixls.exe from the %temp% directory as shown below:
Stage 2 - Execution of Dropped Files
The dropped file, rzkxixls.exe is a virus compiled in VB.
From PEiD:
From the entry point in Debugger and also one of the loaded modules is MSVBVM60.dll
The reason we do this is because viruses written in VB will dynamically obtain the function pointers for
APIs imported from kernel32.dll, ntdll.dll and other modules by calling DllFunctionCall().
Before we analyze it further, let us quickly run a Call Trace on the virus. We must ensure that, this is done
inside a sandbox, since to obtain a Call Trace of the virus, we will be executing it.
I have written a pintool, which will obtain the sequence of CALL instructions along with the instruction
addresses. By looking at the output, we can clearly see that it performs code injection into another process
using the following sequence of APIs:
As you can see, we can quickly identify the method used for code injection by the binary using the Call
Trace pintool. This particular method for code injection is used by several viruses these days and has
become common.
Now that we have a brief overview and understanding of the virus, let us analyze it in the debugger.
Once we break at DllFunctionCall, follow the return address (at the top of the stack) into the code section.
Now, set a breakpoint at the instruction, jmp eax. The function pointer of the API will be returned in eax.
After running the binary we can see that the address of EnumWindows() function was returned in eax.
EnumWindows() function is used in this case only to introduce control flow obfuscation. Since this API
takes an application defined callback function as one of the parameters:
We will follow the first parameter passed to this API in the code section and set a breakpoint at it. In our
case, this address is: 0x0014d458.
Run the binary and break at above address. We have now reached the main code section of the binary.
This is a self modifying code stub. The subroutine at address: 0x0014db68 will be used to modify the
encrypted code present at the address: 0x0014d464.
At first, it loads a large value (0xDDDDFDDD) in the ECX register and then runs a LOOP to introduce delay
in execution.
This is followed by the decryption routine. It makes use of the MMX XOR instruction instead of the general
XOR instruction. The reason to do this is to bypass code emulation. Since code emulators have to
implement the instruction set of x86 processors, they do not implement the complete instruction set.
It is a known method for viruses to make use of undocumented FPU/MMX instructions to defeat the code
emulators.
Once the self modifying code has executed, we will return to the decrypted code section:
In this code section it first makes use of common anti debugging techniques by checking the fields
NtGlobalFlags and BeingDebugged in the Process Environment Block.
After this, it executes the CPUID instruction with eax set to 1 (CPUID_GETFEATURES) and checks the
value of the bit, CPUID_FEAT_EDX_MMX. This check is done to see if the CPU supports MMX instructions.
This is followed by another delay execution routine, which loads a large value into ECX register and runs a
loop.
It now starts resolving the function pointers and Calls the APIs. Below code section corresponds to the
subroutine used to resolve the function pointers:
Instead of getting the function pointers of wrapper APIs like VirtualAlloc(), it gets the address of low level
APIs like ZwAllocateVirtualMemory()
Below is a Call to ZwAllocateVirtualMemory() to allocate memory within its own process address space:
It then searches for the marker, 0x3a58583a within itself and copies the encrypted code to the above
allocated memory followed by the decryption routine.
We can again see the use of MMX instructions and MMX registers in the decryption routine.
Unmaps the image base of the newly created process using ZwUnmapViewOfSection().
Now, it proceeds to perform the code injection using the following method. I will be mentioning the steps
used for code injection without going in much detail since this is commonly used.
Since the remote process is in SUSPENDED_STATE before the call to ZwResumeThread, in order to debug
it, we will modify the entry point of primary thread in remote process by editing the code in our own
address space just before the call to ZwWriteVirtualMemory().
We replace the bytes at the entry point with EB FE which correspond to short relative jump so that the
execution pauses at the entry point in remote process.
The contents of setup.dat file will be decrypted using the decryption routine below:
1. The first byte of setup.dat file indicates the size of the cyclic key, in our case 0x08.
2. The next 0x8 bytes corresponding to the cyclic key will be copied to a local buffer.
3. An array of size 0x100 bytes consisting of bytes 0x00 to 0xFF will be generated.
4. This array of bytes will be permutated and modified using the bytes of the above 8 byte cyclic key.
Once the permutated table is generated, it goes through another phase of permutation as follows:
1. Read a byte from the front end of permutation table.
2. Read a byte from back end of permutation table.
3. Swap the above 2 bytes.
4. Add the above 2 bytes and store it as the result.
5. Use the result above as an offset into the permutation table and read a byte. This byte becomes the
1 byte XOR key that will be used to decrypt the contents of setup.dat file.
6. The loop continues till the entire setup.dat file is decrypted.
After decryption, we receive a mangled output. If we look at the memory dump, we can observe the MZ
DOS header, however it is mangled. So, another subroutine is called to demangle it.
It again allocates memory, copies the decrypted binary there and then resolves function pointers
imported from various modules to update the function pointer table.
It then parses the PE header of the binary, calculates the OEP and then executes the decrypted binary as
shown below:
We will run the binary and observe the network traffic. This will give us an overview of the network
callbacks.
The HTTP response is interesting as it is encrypted. We will look into the specific code section to
understand how it decrypts the response.
But first, let us see how the virus encrypts the data before sending it to the callback server.
Encryption Stage
It uses the Win32 Crypto APIs imported from advapi32.dll to perform the encryption along with custom
encryption routines.
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwQCDMHOqOBOGSrxtrAWaGj/OF
Gc6PqeJSgM0KTZnqBsSP71Mo3ZRqDFJHl/VxV/OyNzOYzE4NEXAmHADjG5YnhhnXAud1FG
/iuXJsj6v+I0wpKHhmwQdb8RfdM4/T3VAaLE11xBAUboJ+1TGzRbpBTnvddJ9EIqZlUf8eft7
DHN09SDE/kp3m3RKBRig0xhL1qzIkRgcmdBjfRowW/LM/JfuU/iYY7YU8OPG+YBQhT9YSeF
gbQORArtr3ivQcujIsD+nm/PEv6pcxznPg/KOTYfRs+xtn42AgwJpDmpv4t2+sOHQ1ZWNwds
4XOw8GS8M7WwwPYbVa12R/eXffcZPUQIDAQAB
This public key is stored in base64-encoded form. It is base 64 decoded to convert from ASCII to
binary.
5. Now, the above public key is used to encrypt the key generated in step 1 as shown below:
a) Acquires a handle to the CSP of type, PROV_RSA_FULL with the flags CRYPT_VERIFYCONTEXT
| CRYPT_MACHINE_KEYSET.
b) It then calls CryptDecodeObjectEx() to decode the above public key from binary to a
structure of type: X509_PUBLIC_KEY_INFO
c) Uses CryptImportPublicKeyInfo() to import the public key from the structure decoded
above.
The public key algorithm type in our case is: 1.2.840.113549.1.1.1, which means that RSA is used
to both encrypt and sign the message.
d) Now, CryptEncrypt() is used to encrypt the key generated in Step 1 using the Public Key
above. The size of the encrypted key is 0x100 bytes.
6. It concatenates 0x100 bytes of encrypted key with 0x61 bytes of encrypted data.
7. It then, Base64 Encodes the complete binary blob.
8. This is followed by URL encoding the result of above step.
The resulting encoded and encrypted data will be sent in the HTTP GET request as you can see in the
network communication screenshot before.
The attacker’s server will retrieve the encrypted key by reversing the steps mentioned above:
Most viruses will perform the network callback by executing the APIs imported from ws2_32.dll like
connect(), send() or APIs like HttpOpenRequestA(), HttpSendRequestA() from wininet.dll.
Those cases are easy to debug and identify while tracing the code. However, when a binary performs
network callbacks using the COM Interface, tracing the code is not so easy.
Let us now look at the code section, which is used for network callback.
At first it initializes the COM library for the current thread using CoInitialize(). The next function called is
CoCreateInstance().
To debug the code further, we must understand what type of object is being instantiated in this case. We
can do this by checking the 1st and 4th parameter of the API as shown below:
HRESULT CoCreateInstance(
_In_ REFCLSID rclsid,
_In_ LPUNKNOWN pUnkOuter,
_In_ DWORD dwClsContext,
_In_ REFIID riid,
_Out_ LPVOID *ppv
);
The first parameter corresponds to the CLSID (Class ID) and the forth parameter corresponds to the IID
(Interface ID).
In our case,
CLSID = {0002DF01-0000-0000-C000-000000000046}
IID = {D30C1661-CDAF-11D0-8A3E-00C04FC9E26E}
In order to find the meaning of the CLSID and IID, we need to look up the Windows Registry, specifically
these keys: HKEY_CLASSES_ROOT\CLSID\ and HKEY_CLASSES_ROOT\Interface\
After looking up the above CLSID and IID values we can see that in our case, the CLSID corresponds to
Internet Explorer (Ver 1.0) and IID corresponds to IWebBrowser2.
It is also important to understand the return value of CoCreateInstance. It will return a pointer to the COM
object.
This is the actual COM Object itself. If we follow it in memory dump again, we can see a table of function
pointers:
All the methods of IWebBrowser2 Interface are invoked by calling the function pointers from the above
table. However, these function pointers are not resolved by the debugger to any symbol name. This is the
reason, tracing the code of COM interfaces in debugger requires us to find the function names as well.
If we trace the code further, we see the following sequence of API calls:
UuidCreate(): This is used to create a 128-bit UUID which is later used as the class name of the Window.
It is important to note that UUID is generated randomly. In our case, the UUID is: {6F601261-8C73-4E4B-
8565-E3DA3E8242E0}
RegisterClassExW(): This is used to register a class with the Window Procedure at: 0x100051da. It is
always useful to set a breakpoint at the window procedure since it will have some important functionality
besides creating the Window.
In our case, we can see that the Window Procedure compares the Window Message code with 0x113,
which corresponds to WM_TIMER window message. If the window message code is not equal to 0x113
then the control is transferred to the default window procedure. So, we know the window message of
interest.
FindWindowA(): It then checks for the presence of any Windows in the system with the Class Name
equal to the UUID created previously. This is similar to the cases where a virus checks for a specific Mutex
Name to check if there is any other instance of the virus running on the machine.
GetSystemMetrics: It uses GetSystemMetrics() function to retrieve the values of the maximum possible
width and height of the screen as shown below:
0x3E corresponds to SM_CYMAXIMIZED and 0x3D corresponds to SM_CXMAXIMIZED.
CreateWindowExA: It creates a Window with the class name set to the UUID created before and
the dimensions of the window are set to the maximum possible width and height of the screen.
SetWindowLongW: It sets the user data (GWL_USERDATA) associated with the window created
above. The user data consists of the pointer to the COM object.
If we trace the code further, we can see the calls to IWebBrowser2 Interface. This is where we
need to find the function names. The calls look like shown below:
The debugger does not provide any information about the function name.
Let us try to understand how the methods exposed by the IWebBrowser2 interface are called.
In order to find the function names, we will look up the C/C++ header files provided along with
compilers like MSVC. In our case, we will check the header file, ExDisp.h.
MIDL_INTERFACE("D30C1661-CDAF-11d0-8A3E-00C04FC9E26E")
IWebBrowser2 : public IWebBrowserApp
{
The structure of interest to us is IWebBrowser2Vtbl. Also, notice the IID (Interface ID) passed to
MIDL_INTERFACE. It corresponds to the IID of IWebBrowser2 interface as we saw before.
Now, we need to locate the function name, which corresponds to the function at offset 0x94.
Since the size of each function pointer = 0x4 bytes, we can calculate the position of function in the
above structure as:
Position = Offset/4 + 1
It takes 2 parameters, the first is the pointer to the COM object and the second is the pointer to the
variable that receives the handle of the window.
This way, we can easily analyze all the methods exposed by the IWebBrowser2 interface.
We get the handle to the window corresponding to the CLSID of Microsoft Internet Explorer.
It calls SetWindowLongW() again to set the GWL_STYLE of the Internet Explorer window to
WS_CHILD as a result of which it will not have a menu bar.
SetParent: It then sets the parent window of the Internet Explorer as the window created above
(with the UUID).
IWebBrowser2.put_Visible: It calls the put_Visible method to set the visible property of the
Internet Explorer window to hidden.
SysAllocString: It allocates a string to store the URL to which the network callback will be made.
IWebBrowser2.Navigate2: It calls the Navigate2 method exposed by the IWebBrowser2
interface to navigate to the above URL.
Once we execute this function, it will send a GET request to the callback server.
As we observed previously that it receives an encrypted response. Let us see how this response is
decrypted.
Now, to trace the code further, we need to understand the IHTMLDocument2 interface and the
methods exposed by it. We look up the header file, Mshtmlc.h and find the interface defined here:
It is also important to note that we should check the Interface definition for C and not C++ since
the order of methods exposed by the interface differs between the two.
Once again, we look up the header file, Mshtmlc.h for the methods exposed by the IHTMLElement
Interface as shown below:
Here innerText refers to the content in the HTML response between the tags: <html><body> and
</body></html>, which in our case is the encrypted response.
The decryption routine will first generate a Permutation Table of size 0x100 bytes using the 0xF4
bytes decryption key.
This permutation table is then used again in XOR decryption of the binary response. This
decryption routine is similar to the one we saw previously.
You can see the decrypted response in the memory dump below:
The original length is stored as the second DWORD in the response, in our case: 0x03E5D4. This is
the total length – 0xC bytes because the first 0xC bytes store data for verification.
In the second stage of verification, it calculates the hash of the total decrypted response using a
single byte key, 0x7F as shown below:
The calculated hash is compared with the hash stored in the decrypted response as the first
DWORD, in our case, 0xAF7EF27A
It then compares the strings stored in the response with “core”. The strings stored in response
are: “clk”, “ppc” and “core”. This is done to locate the correct offset, which will be used to locate
the binary in the response.
Once it locates the string, “core”, it will copy 0x3E58A bytes to a new buffer.
Similarly it extracts the second binary embedded in the decrypted response by copying, 0x38800
bytes to a new buffer.
Once both the binaries are copied from the decrypted response to new buffers, it parses the
binaries.
Binary 1:
It copies the sections of the binary one by one to a new buffer. It then parses the PE header,
locates the AddressOfNames in Export Directory and reads the module name, MozSvcs64.dll.
In this way, we can see how the decrypted response is parsed to extract malicious binaries to
carry the attack forward.
Conclusion
After reading this paper, you will be able to reverse the encrypted network communication
performed by most viruses these days and gain a better understanding of the data being
exfiltrated, the data received in response from attacker’s server and code execution flow.
Also, as we can see, even the modern day viruses do not use complex encryption methods or
custom encoding techniques. There is a lot more scope in the encryption of data exchanged with
the callback servers.
References
http://msdn.microsoft.com/