As a new member of the Stormshield Security Intelligence team, my initiation ritual was to analyze a form-grabber malware used to steal passwords thanks to web-browser injection method. In this article I'll try to present a detailed analysis of this malware, with emphasis on the web-browser injection part. The malware is pretty old, its compilation time-stamp points out that it may have been used during November, 2012. However, as we will see throughout this blog-post, it is still effective against latest browsers (running in 32-bit mode). Xylitol, a security researcher, has shared a sample of this malware on Virus Total at the end of 2012, but no public analysis seems to be available on the Internet. Due to the lack of information about this malware, the propagation method of this threat is unknown.



The sample analyzed is packed with 2 layers. The first one is based on the well-known packer UPX and can be easily defeated. The second one is quite simple as well, it implements a small anti-debug trick which reads the 'BeingDebugged' flag within the PEB. It also involves a small de-obfuscation routine which performs XOR operations with a one byte key (0x0F), used to output decrypted PE sections, within buffers allocated with VirtualAlloc(). Following few calls to VirtualAlloc(), the end of the second unpacking stage can by identified with the following instructions:

push 0x666 ; magic value checked after unpacking
push ebx   ; base address of the unpacked PE
call eax   ; leads to the unpacked PE original entry point

Thus, it can be drawn that the first function executed by the malware takes 2 input parameters:

  • The unpacked PE base address
  • A dword used as a magic number

If the magic value isn't the expected one (0x666), then the malware stops its execution.


RC4 Encryption

In order to delay reverse-engineering and probably to defeat static analysis detection-based methods, the malware implements the RC4 algorithm to encrypt strings. Most of them are DLLs or functions' names and parameters related to web-browser injection, and used to dynamically resolve imports using LoadLibraryA() and GetProcAddress(). At the address .data+0x30, an array of structs with one entry per encrypted string is identified. The struct contains the following fields:

struct rc4_encrypted_string {
const char *string; // pointer to the encrypted string stored in .rdata
unsigned int length; // length of the string

Thus, in order to decrypt a string, the malware uses a function that pushes the number of the encrypted string within the array of structs, from which its address and length can be deduced. With an IDA python script, one can easily find cross-references to this function, identify the string's number as the last pushed argument, and thus perform decryption.

Executing this script reveals interesting strings such as DLL or function names:

The RC4 key used for encryption is 128-bits long and its address is stored at .data+0x14. In this sample the key is the following: 27F56A32B728364FBA109F983D148023.


Main execution loop

The main thread of the malware is used to perform an infinite loop which enumerates running process on the target operating system, in order to find a browser on which thread injection could be done. The process enumeration is done with the following functions from the Windows API:

  1. CreateToolhelp32Snapshot()
  2. Process32FirstW()
  3. Process32NextW()

The malware tries to inject a thread into any process whose name matches one of the following strings:

  • chrome.exe
  • firefox.exe
  • opera.exe
  • iexplorer.exe
  • WebKit2WebProcess.exe (Safari)


Form-grabber thread injection

Once a process' name matches an expected one, a call to OpenProcess() is used to get an handle on the targeted process. Then, a call to VirtualAlloc() allows the malware to allocate an executable memory-area within the targeted process address-space. This memory-area is used to store a copy of the PE file currently running, by using its address base (pushed at the end of the 2nd packing stage) as a source address. This is performed using WriteProcessMemory(). Now, the entire PE is mapped within the targeted process address-space, and a last call to CreateRemoteThread() allows the injected thread to start running.

Since the malware doesn't pass any variable to the thread function, the remote thread doesn't know in which process it is running. To find out, it accesses the InMemoryOrderModuleList LIST_ENTRY within the PEB, and checks the FullDllName associated with the first entry. According to its value ('chrome.exe', 'iexplorer.exe', ...) it calls a per-browser function used to setup inline hooking for the injected process.


Inline Hooking

Inline hooking is a well-known method used to intercept function calls, and execute a detour function that can access function parameters as the original function would have done. This method is referenced within the book Practical Malware Analysis (Direct Injection, p.257). In this sample, it is used to intercept functions called by browsers for sending HTTP requests, and thus access payload that may contain sensitive data such as credentials or credit card numbers.

In order to setup inline hooking, this malware uses one global struct per hooked function, stored within the .data section. The struct contains the following fields:

 // Function pointer definition for calling HTTPSendRequestA()
typedef BOOL (*http_send_request_prototype_t)(
_In_ HINTERNET hRequest,
_In_ LPCTSTR lpszHeaders,
_In_ DWORD dwHeadersLength,
_In_ LPVOID lpOptional,
_In_ DWORD dwOptionalLength

struct direct_injection_hook {
http_send_request_prototype_t hooked_function; // address of Wininet.dll!HttpSendRequestA
http_send_request_prototype_t detour_function; // address of the function written by the author
unsigned int count_saved_bytes; // number of bytes overriden at Wininet.dll!HttpSendRequestA
void (*return_to_dll)(void); // address of user-allocated page (RWX) used to execute the original
// hooked function after execution of the detour function

This structure is filled when the injected thread starts, by executing these steps:

    1. A call to GetModuleHandleA() and GetProcessAddress() allows to get the address of the targeted function, which aimed to be hooked. Thus, for iexplorer.exe, the field 'hooked_function' contains the address of Wininet.dll!HttpSendRequestA.


    1. In order to setup the jump to the detour function, the malware must override the 5 first bytes of Wininet.dll!HttpSendRequestA with one opcode byte, 0xE9 (relative unconditional jump), and 4 address bytes (relative distance up to the detour function). However, as x86 architecture has variable length instructions, it can't override 5 bytes systematically since it could erase the middle of an instruction. Thus, a function is used to disassemble the first bytes of a procedure, in order to compute the number of bytes to erase, without overriding an instruction in the middle. This number is saved in the structure within the field 'count_saved_bytes'.


    1. The field 'detour_function' is then written with the address of a function implemented by the malware, that will access HTTP payload. This function is explained later in this post.


    1. A memory-area of size 'count_saved_bytes' + 5 is allocated using VirtualAlloc() and a call to VirtualProtect() sets RWX permissions to the associated page. The address of this buffer is stored in the pointer 'return_to_dll'. It is then filled with the first N bytes (N being 'count_saved_bytes') of the targeted function. For instance, for Wininet.dll!HttpSendRequestA it is 8B FF 55 8B EC:
      wininet.dll:77B02EBC 8B FF          mov     edi, edi
      wininet.dll:77B02EBE 55             push    ebp
      wininet.dll:77B02EBF 8B EC          mov     ebp, esp

      Finally, a jump to Wininet.dll!HttpSendRequest+N is written and the memory-area pointed by 'return_to_dll' contains the following instruction:

      8B FF          mov     edi, edi                        ; copy of Wininet.dll!HttpSendRequestA+0,1
      55             push    ebp                             ; copy of Wininet.dll!HttpSendRequestA+2
      8B EC          mov     ebp, esp                        ; copy of Wininet.dll!HttpSendRequestA+3,4
      E9 XX XX XX XX jmp     Wininet.dll!HttpSendRequestA+5  ; jump to Wininet.dll!HttpSendRequestA+5

      It is now clear that a call to 'return_to_dll' would be equivalent to calling an unhooked version of Wininet.dll!HttpSendRequestA. Thus, instructions at 'return_to_dll' are going to be executed at the end of the detour function, to complete the browser's API call request. This mechanism is commonly known as a trampoline function.


  1. Once the trampoline function is written, the last step is to override the N first instructions of Wininet.dll!HttpSendRequest, to setup the jump (opcode 0xE9) to the detour function. To do so, 2 calls to VirtualProtect() are performed: a first one to set the page writable and a second to restore the original access rights.

Here is a decompiled version of this function, used to setup a inline hook, located at .text+0x40:

Execution flow after hooking

Once the hook is setup, if iexplorer.exe calls Wininet.dll!HttpSendRequestA, the execution flow will be:

  1. Execution of the first instructions of Wininet.dll!HttpSendRequestA which jump to the detour function
  2. The detour function accesses the hooked function's arguments such as the HTTP payload and can thus extract sensitive information
  3. The detour function calls the trampoline function (return_to_dll), the saved instructions of Wininet.dll!HttpSendRequestA are executed and a jump to Wininet.dll!HttpSendRequestA+5 is performed
  4. The remaining instructions of Wininet.dll!HttpSendRequestA are executed until the function returns and the HTTP request is performed transparently
  5. The detour function's epilog is executed, and returns to the source function, that made the call to Wininet.dll!HttpSendRequestA

The following graph, inspired from F-Secure Special Course, illustrates this process:

Hooked functions by browser

The following table summarizes DLLs and targeted functions according to the browser choice for the injection:

Browser DLL Function Payload access
chrome.exe ws2_32.dll WSASend Post-encryption
firefox.exe nspr4.dll PR_Write Pre-encryption
iexplorer.exe Wininet.dll HttpSendRequestW Pre-encryption
iexplorer.exe Wininet.dll HttpSendRequestA Pre-encryption
opera.exe ws2_32.dll WSASend Post-encryption
WebKit2WebProcess.exe (Safari) CoreFoundation.dll CFWriteStreamWrite Pre-encryption

Some functions operate at the HTTP layer, which means that browsing an HTTPS website will first result in crafting HTTP requests caught by the hook, before being passed to the TLS layer for encryption. Others operate at the TCP layer, which means that browsing an HTTPS website will result in hooking TLS records payload, ready to be sent on the socket and thus useless for this malware.


Analyzing the detour function

As explained previously, there is one detour function implemented for each hooked function. The detour function has the same prototype as the targeted one and thus can access parameters on the stack, that were pushed by the browser's previous stack frame. In this paragraph, we'll study the detour function that hooks Wininet.dll!HttpSendRequestA. According to the msdn, the prototype of this function is:

BOOL HttpSendRequest(
_In_ HINTERNET hRequest,
_In_ LPCTSTR lpszHeaders,
_In_ DWORD dwHeadersLength,
_In_ LPVOID lpOptional,
_In_ DWORD dwOptionalLength

The detour function checks the lpOptional parameter used to specify HTTP body payload, when PUT or POST requests are performed. If it's NULL, the current request is ignored (it's probably a GET or a HEAD request). Otherwise, the function retrieves the host and the URI requested, by calling InternetQueryOptionA() and providing an handle to the HTTP request (hRequest) and the flag INTERNET_OPTION_URL.
Then, it uses the format string "POST %s\r\nHost: %s\r\n%s\r\n\r\n", to build a copy of the HTTP request to be sent.
Finally, a generic function is called to obfuscate and exfiltrate the copy of the request, to an HTTP server owned by the attacker. The exfiltration is done with POST HTTP requests containing the URI "/developement/panel.php" and the following headers:

Content-Type: application/x-www-urlencoded
Accept: text/*
User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.46 Safari/536.5

The parameters of the POST request are generated based on the format string "c=%s&v=1.0&h=%s&t=%u", with arguments:

  • "c=%s" : The string is an hexadecimal dump of the intercepted HTTP request copy. Before being hexlify, the request copy is xored with a one byte key (0x07).
  • "v=1.0" : Probably the version identifier of this sample.
  • "h=%s" : Username and Hostname of the targeted computer, generated with a format string like "%s / %s". Arguments are retreived using GetUserNameA() and GetComputerNameA().
  • "t=%u" : Integer between 0 and 5 that defines the process in which injection is currently active

The following regular expression (PCRE) can be used to match the POST request's body:


In this sample, the HTTP server IP address is, meaning that this sample is either a debug version, or has been patched before being uploaded, or is deployed with another executable that serves as a proxy. A python script has been implemented to decode HTTP requests sent by this malware, it implements a class based on BaseHTTPServer to catch POST method:

import BaseHTTPServer
import urlparse

browser_mapping = {
0 : 'chrome.exe',
1 : 'chrome.exe',
2 : 'firefox.exe',
3 : 'opera.exe',
4 : 'WebKit2WebProcess.exe', # Safari
5 : 'iexplorer.exe'


class FormGrabberHTTPDecoder(BaseHTTPServer.BaseHTTPRequestHandler):

def do_POST(self):
request_line = self.requestline
content_length = int(self.headers['Content-Length'])
post_data =
decoded_data = urlparse.parse_qs(post_data)
malware_version = decoded_data['v'][0]
injected_browser = browser_mapping[int(decoded_data['t'][0])]
username_hostname = decoded_data['h'][0]
hexdumped_post_data = decoded_data['c'][0]
xored_post_data = hexdumped_post_data.decode('hex')
unxored_post_data = [chr(ord(c) ^ XOR_KEY) for c in xored_post_data]
unxored_post_data = ''.join(unxored_post_data)
d1 = ('-' * 79)
d2 = ('*' * 79)
print('Received HTTP request line: {}'.format(request_line))
print('Received HTTP body:\n{}\n{}\n{}'.format(d1, post_data, d1))
print('Malware version: {}'.format(malware_version))
print('Injected browser: {}'.format(injected_browser))
print('Username and Hostname: {}'.format(username_hostname))
print('Intercepted HTTP request:\n{}\n{}\n{}'.format(
d1, unxored_post_data, d1))

if __name__ == '__main__':
s = BaseHTTPServer.HTTPServer((HOST, PORT), FormGrabberHTTPDecoder)

Here is an example of the script output, when trying to authenticate on, using https with Internet Explorer:


Test with recent browsers

Since this form-grabber malware is almost 5 years old at the time of analysis, we can wonder whether it is still effective nowadays. Indeed, browsers' architecture has evolved during these few years, and some functions hooked by the malware may not be used anymore. The table below summarizes tests done on a Windows 7 OS (SP1, 64-bit), with the latest version of browsers (running in 32-bit mode) targeted by the malware:

Browser Version Vulnerable
chrome.exe 61.0 (32 bits) Yes
firefox.exe 55.0 (32 bits) No
iexplorer.exe 11.0 (32 bits) Yes
opera.exe 47.0 (32 bits) Yes
WebKit2WebProcess.exe (Safari) 5.1.7 (32 bits) Yes

Surprisingly, except for Firefox, all the hooks are still effective on latest 32-bits targeted browsers.



This is a quite small form-grabber malware that uses simple anti-debug tricks and few obfuscated calls. The RC4 algorithm encrypts functions and DLLs names used to perform dynamic imports. While inline hooking is a well-known method for intercepting function calls, it's still effective against latest browsers. The function implemented to disassemble the start of the hooked functions' seems to be well implemented. It allows the malware to be insensitive to targeted DLLs' updates. However, the malware's author didn't take the time to hook functions operating at the HTTP layer for all targeted browsers, and thus, may miss requests performed over https.


Additional information

  • MD5: cb066c5625aa85957d6b8d4caef4e497
  • SHA1: bd183265938f81990260d88d3bb6652f5b435be7
  • SHA256: 9cdb1a336d111fd9fc2451f0bdd883f99756da12156f7e59cca9d63c1c1742ce
  • Analysis on Virus Total

Share on

[juiz_sps buttons="facebook, twitter, linkedin, mail"]