In-depth Formbook malware analysis – Obfuscation and process injection


Formbook is a form-grabber and stealer malware written in C and x86 assembly language. It’s a ready to sell malware, that can be used by cyber-criminals who don’t have any skill in malware development. The sample analyzed in this blog-post has been dropped by a word document, during a mail campaign used to distribute Formbook. We caught it thanks to Breach Fighter, our cloud-based sandboxing engine, used to analyze files received by our customers. We wrote a previous post about a simple form-grabber malware used to hook HTTP requests made by common browsers in order to steal passwords, where we described the inline hook mechanism. Note that several blogposts about Formbook have already been published. The first one, from Arbor Networks (September 2017), describes some of the obfuscation methods involved with this malware. The second one from FireEye (October 2017), gives a nice overview of the malware capabilites and also covers some of the obfuscation tricks. Based on these articles, we decided to write a new one with a focus on:

  • obfuscation methods used by formbook in order to complicate reverse-engineering
  • the anti-debugging / anti-sandbox tricks
  • the usage of IDA Python to automate the analysis
  • the way formbook uses process hollowing through explorer’s thread hijacking and APC injection

Anti-Analysis tricks

Strings encryption

Running the strings command on this malware doesn’t give a clue about its intent. Indeed, all the strings used by this malware are obfuscated or encrypted. Encrypted strings storage and encryption algorithm are explained in the section ‘Data encryption’.

Strings hashing

The malware tries to use as less (encrypted) strings as possible. For instance, to check for the presence of a string in memory (e.g a process’ name), rather than storing the encrypted expected string and decrypting it to perform the comparison, the malware applies a hash function on the input string and checks whether it matches the expected pre-computed hash.

The hash function used by formbook is BZip2 CRC32, applied on strings previously converted to lower-case:

>>> from crccheck.crc import Crc32Bzip2
>>> hex(Crc32Bzip2.calc(bytearray("NtCreateProcessEx".lower())))

In this article, each mention to a string hash refers to the BZip2 CRC32 hash of the associated string.

Data encryption

The malware stores encrypted buffers, “hidden” directly within the text section. The address of each encrypted buffer is retrieved thanks to a trick commonly used by malware. Since a call instruction pushes on the stack the address of the instruction to execute when returning from the callee, a call instruction (0xE8) with an operand of 0x00000000 leads to a jump at the address following the call. The following ‘pop eax’ instruction can then be used to retrieve the current instruction pointer value, and thus the start of the encrypted buffer, located 2 bytes after:

Schema from Arbor Network illustrating the ‘pop eip’ trick

Arbor Networks already described this mechanism and also released a python script which implements the decryption functions, decrypt_func1() and decrypt_func2(), used to decrypt this kind of encrypted buffers. The decrypt_func1() function takes as argument an input buffer that is bigger than the decrypted output buffer (some bytes are used as ‘opcodes’ describing which transformation should be applied). The decrypt_func2() function applies 2 simple transformations on the input buffer, then perform RC4 decryption using a 20 bytes key long (resulting from a SHA-1 message digest) and applies 2 more simple transformations on the input buffer.

Decrypting the encrypted hash array

Let’s take a look at how these 2 functions are used at the early stage of the malware, in order to decrypt an array containing many hashes, used later to perform dynamic import resolution and anti-debug / anti-sandbox tricks.

Two encrypted buffers, respectively encbuf1 and encbuf3, are given to the decrypt_func1() function, and their outputs are used to compute two SHA-1 hashes, used later as RC4 keys (rc4_key_one and rc4_key_two). A third encrypted buffer, encbuf2, is first given to the decrypt_func1() function, and then given to the decrypt_func2() function with the RC4 key ‘rc4_key_one’ derived previously. The resulting buffer is decrypted with the decrypt_func2() function and the RC4 key ‘rc4_key_two’, which gives encbuf2_s3. Finally, the SHA-1 of this buffer is computed to obtain the final RC4 key. The following dot graph illustrates this process.

We wrote an IDA python script, released on github, used to decrypt the array of hashes and to print the associated strings. To find hashes related to dynamically imported functions, we modified the shellcode_hashes plugin in order to add support for BZip2 CRC32. Since the encrypted array also contains hashes of strings used to perform anti-debug / anti-sandbox tricks, we wrote another python script used to request JSON files containing strings likely used by malware. Since this script can be usefull for analyzing other malware using strings’ hashes, we released it on github, and we’ll update it as we encounter new malwares. Feel free to open pull requests in order to improve the JSON base of knowledge.

After executing the IDA python script, one can see that dynamic function import, anti-vm, anti-sandbox and anti-analysis trick are likely to be performed.

12 0xf653b199 NtCreateProcessEx
13 0xc8c42cc6 NtOpenProcess
79 0x3ebe9086 vmwareuser.exe
80 0x4c6fddb5 vmwareservice.exe
83 0x85cf9404 sandboxiedcomlaunch.exe
84 0xb2248784 sandboxierpcss.exe
85 0xcdc7e023 procmon.exe
86 0x11f5f50 filemon.exe
114 0x24a48dcf guard32.dll
115 0xe11da208 SbieDll.dll

The full script output for this instance of formbook is available on our github respository (the format is based on this file, issued from Arbor Network’s analysis).

Manually mapping a copy of ntdll

As you can see with CFF Explorer, the Import Directory Table (IDT) of formbook is empty. This is generally a suspicious hint that likely suggests dynamic function importing:

Even if no dll is referenced in the import table, the Windows loader always loads ntdll.dll in the process address space. Security solutions based on userland hooks frequently intercept functions in ntdll.dll to monitor process activity. In order to evade monitoring, formbook maps a copy of this dll and performs many of its interactions with the operating system through this dll. FireEye referenced this method in their article as “Lagos Island method”. In this section, we’ll describe how formbook perform this operation.

As you may known, the view of a DLL is not the same whether it is stored on the disk or mapped in memory. This is due to the PE file format specifications which require, for paging purposes, that each section, when mapped in memory, is guaranteed to start at a virtual address that’s a multiple of IMAGE_OPTIONAL_HEADER.SectionAlignment (0x1000 by default). On the other hand, in the PE file, the raw data that comprises each section is guaranteed to start at a multiple of IMAGE_OPTIONAL_HEADER.FileAlignement (0x200 by default). Also, the size of a section on the disk is always round up to the value IMAGE_OPTIONAL_HEADER.FileAlignement while the size of a section in memory theoretically doesn’t need to be round up to IMAGE_OPTIONAL_HEADER.SectionAlignment (it is in practice since the OS allocates page of 4KB). For more information, please read A Tour of the Win32 Portable Executable File Format.

Let’s see how formbook deals with these PE specifications in order to copy and map its own version of ntdll.dll:

1. Retreiving the original ntdll full path

To retrieve the base address of ntdll, loaded by the Windows image loader, formbook uses a function that we named ‘get_module_base_address_by_hash()’. This function iterates over each LDR_DATA_TABLE_ENTRY entry of the LIST_ENTRY ‘InMemoryOrderModuleList’ in order to compute the hash of the image name (LDR_DATA_TABLE_ENTRY.BaseDllName) and checks whether it matches the expected value, given as an argument.

If the expected checksum is found, the DLL base address is returned. Then, it uses another function that we named ‘get_module_data_table_entry_by_base_address()’ which works as similar but returns a pointer on the LDA_DATA_TABLE_ENTRY entry where an entry with the expected base address if found. From this entry, the full path (LDR_DATA_TABLE_ENTRY.FullDllName) of the original ntdll module can be extracted.

2. Perform a raw copy of ntdll from disk to memory

The next step is to perform a raw copy of ntdll from disk to memory, using the full path retrieved previously. This is performed with the following steps:

  1. Obtain an handle on the disk version of ntdll.dll using NtCreateFile() with the ‘FILE_OPEN’ CreateDisposition
  2. Retrieve ntdll size using NtQueryInformationFile() with the class ‘FileStandardInformation’
  3. Allocate a buffer of the appropriate size, used to store the file copy, with RtlAllocateHeap()
  4. Read the raw file from disk to memory with NtReadFile()

3. Mapping ntdll headers and section into memory from the raw copy

As stated earlier, the view of a PE file is not the same on the disk and in memory. Thus, in order to map into memory its own copy of ntdll, from the the disk copy, the following steps are performed:

  1. Check for the MZ header and PE header (0x4550) in the raw copy buffer
  2. Read the SizeOfImage field from the Optional Header and allocate a buffer (new mapped DLL’ buffer) of this size using NtAllocateVirtualMemory()
  3. Read the SizeOfHeaders field from the Optional Header and copy the PE headers
  4. Read the NumberOfSections from the COFF Header
  5. For each section within the section table, read the RawSize, RawAddress, VirtualSize and VirtualAddress fields
  6. Copy from ntdll.dll file’s buffer, each section of size ‘VirtualSize’, starting at offset ‘RawAddress’, to the new manually mapped DLL at offset ‘VirtualAddress’

From now on, the manually mapped version of ntdll is loaded in an almost similar way as the one mapped by the Windows loader, and thus can be used by formbook. One noteworthy difference is the fact that the manually mapped dll is contained in a single committed region, whose protection is PAGE_EXECUTE_READWRITE. The version loaded by the Windows loader is mapped in several committed regions with appropriates protections (e.g : PAGE_READONLY for the PE header, PAGE_EXECUTE_READ for .text sections, …)

Loading additional DLLs in the process address space

In addition of using undocumented functions from the native API, formbook also uses higher level functions exported by DLLs such as kernel32.dll or user32.dll. Theses DLLs are loaded in the process address space using the undocumented function LdrLoadDll(), from the manually mapped instance of ntdll. The address of LdrLoadDll() is resolved dynamically using the method described in the next section.

Dynamic function importing

Dynamic function importing is performed to resolve address of functions contained in both, the manually mapped version of ntdll.dll and additional DLLs loaded with LdrLoadDll(). A function that we named ‘import_func_by_hash’ is used to import functions using the address of their corresponding DLL in the process address space, and the expected function’s name hash. It works as following:

  1. Check for the MZ header and PE header (0x4550) at the corresponding DLL base address
  2. Read the fields SizeOfExportTable and ExportTable (RVA) from the Optional Header
  3. Read the fields AddressOfNames (RVA), NumberOfNames, AddressOfFunctions (RVA), NumberOfFunctions and AddressOfNameOrdinals (RVA) from the export table
  4. For each entry of the array of strings pointer AddressOfNames, compute the hash of the function’s name and check it against the expected hash
  5. If it matches, obtain the index of the function from the array of WORDs AddressOfNameOrdinals
  6. Read the address of the function (RVA) from the array of function addresses AddressOfFunctions, at the index read previously

The address of the function (RVA) is finally added to the DLL base address and cached by formbook in an array of imported function’s pointers. Thus, if formbook needs to call this function a second time, it won’t have to import it again.

Anti-debugging / Anti-Analysis tricks

Once formbook has mapped its copy of ntdll, it can start to perform its malicious stuff… In this section, we will describe several methods used by formbook in order to check for:

  • System call hooks set-up by security tools
  • Blacklisted running processes
  • Blacklisted injected DLLs
  • Debugged, virtualized or sandboxed environement

After performing each check, a corresponding resulting byte is set within a custom structure. Each field of this structure is then tested, in a function used to decide whether the process should stop its execution. This function returns 0 if at least one anti-debugging or anti-analysis check identified something unusual:

Unfortunatly, when debugging this malware, it’s not sufficient to just patch the return value of this function. Indeed, when a check fails, the malware also modifies some hashes from the encrypted hashes array so that dynamic functions import fails later.

Check for WOW32Reserved Hook

Since formbook is a 32-bit PE, to check whether it is running in wow64 compatibility mode (i.e 32-bit PE running on a 64-bit Windows OS), it checks whether the full path of ntdll.dll contains ‘wow64’.

When PE files are running in wow64 mode, a specific 32-bit version of ntdll is mapped with the particularity that each syscall routine ends with a call to [fs:0xc0] (It’s not the case anymore on Windows 10, but [fs:0xc0] is still usable for compatibilty reasons). This is actually a call to the address contained in TEB structure’s field ‘Wow32Reserved’, which result in a routine located in wow64cpu.dll. This routine is used to switch from 32-bit to 64-bit native code by performing a jump instruction with the segment selector 0x33.

A method for tracing system calls performed by PE running in wow64 mode consists in overriding the ‘Wow32Reserved field’ to setup a hook mechanism. It is for instance used by Stealth64 OllyDbg plugin. The idea is to replace the field ‘WoW32Reserved’ with the address of custom section of code used to intercept syscalls.

To check if a hook is setup, formbook checks whether the field ‘Wow32Reserved’ points to a section of code belonging to a PE64 DLL (i.e wow64cpu.dll), by performing the following step:

  1. Call NtQueryVirtualMemory() with the class ‘MemoryBasicInformation’ in order to retrieved the ‘AllocationBase’ of WOW32Reserved (i.e wow64cpu.dll base address)
  2. Parse the PE DOS Header and extract the COFF Magic defining the type of PE File
  3. Check that the COFF Magic equals 0x020B meaning the file is a PE64

Checking for blacklisted loaded DLLs

Formbook checks if the module SbieDll.dll is loaded within its address space. To do so, it uses a function that we named ‘get_dll_base_address_by_hash()’, described earlier. If the expected checksum is found (0xe11da208 for SbieDll.dll), the function returns the base address of this module and a flag related to this check is set.

Checking for blacklisted running processes

In order to check for blacklisted running processes, formbook calls NtQuerySystemInformation() with the class ‘SystemProcessInformation’. It then iterates over each SYSTEM_PROCESS_INFORMATION entry in order to compute the hash of the field ‘ImageName’. It then checks within the array of encrypted hash (from offset 79 to 98) if the computed hash is blacklisted. From the list of decrypted hashes, we can view that it consists of the following image name:

79 0x3ebe9086 vmwareuser.exe
80 0x4c6fddb5 vmwareservice.exe
81 0x276db13e vboxservice.exe
82 0xe00f0a8e vboxtray.exe
83 0x85cf9404 sandboxiedcomlaunch.exe
84 0xb2248784 sandboxierpcss.exe
85 0xcdc7e023 procmon.exe
86 0x11f5f50 filemon.exe
87 0x1dd4bc1c wireshark.exe
88 0x8235fce2 netmon.exe
89 0x21b17672 prl_tools_service.exe
90 0xbba64d93 prl_tools.exe
91 0x2f0ee0d8 prl_cc.exe
92 0x9cb95240 SharedIntApp.exe
93 0x28c21e3f vmtoolsd.exe
94 0x9347ac57 vmsrvc.exe
95 0x9d9522dc vmusrvc.exe
96 0x911bc70e python.exe
97 0x74443db9 perl.exe
98 0xf04c1aa9 regmon.exe

Checking for the presence of a debugger

Formbook checks whether it is currently beeing debugged, by calling NtQuerySystemInformation() with 2 different classes:

  • ‘SystemKernelDebuggerInformation’, used to know if a ring 0 debugger is attached
  • ‘ProcessDebugPort’, used to know if a ring 3 debugger is attached

The ‘BeingDebugged’ field from the PEB structure is also used later by formbook, to break from an infinite loop as soon as a userland debugger is attached.

Checking for blacklisted process image name

Formbook checks whether its own image name (BaseDllName) ends with a string whose hash equals 0x7c81c71d. This check is probably targeting a specific sandbox environment that change PE files’ image name before execution.

Checking for blacklisted loaded modules path

Formbook checks whether one of its loaded module is located within one of 7 blacklisted directories. To do so, it iterates over the LIST_ENTRY ‘InMemoryOrderModuleList’, extracts the full path of each module (FullDllName) and computes the hash of each directory that is part of the full path. Each hash is then checked within the array of encrypted hashes (from offset 99 to 105). We didn’t found any directory name matching a blacklisted directory hash. We’re interested to update the list of decrypted hashes if you found any of them.

Checking for blacklisted username

Formbook retrieves the username from its environement using RtlQueryEnvironmentVariable_U() with the variable name ‘USERNAME’. It then checks, within the array of encrypted hash (from offset 106 to 112), if the username contains as a substring a blacklisted usernames. From the list of decrypted hashes , we can view that it consists of the following:

106 0xed297eae cuckoo
107 0xa88492a6 sandbox-
108 0xb21b057c nmsdbox-
109 0x70f35767 xxxx-ox-
110 0xb6f4d5a8 cwsx-
111 0x67cea859 wilbert-sc
112 0xc1626bff xpamast-sc

Process Injection

In this section, we’ll describe how formbook performs its process injection to migrate in a process whose image base belongs to Microsoft. For simplification issues, in this
section, we’ll consider that formbook runs on a 32-bit Windows version. Formbook can migrate from a 32-bit process running in wow64 mode to a native 64-bit process, but this implies a lot of patches on the .text section. The previous form-grabber malware that we analyzed used a well known method to inject targeted processes using WriteProcessMemory() and CreateRemoteThread(). Formbook uses another method, less common, which can be summarize in 3 stages:

  1. explorer’s main thread hijacking and APC injection
  2. suspended process creation by explorer’s hijacked thread or injected APC
  3. migration in the newly created suspended process (process hollowing)

The main advantages of this method are that no new thread is created within explorer’s process and the parent process of the suspended created process is explorer.

1. Hijacking explorer main thread

Adjusting process privileges

Formbook will perform several operations on explorer’s process, such as mapping sections of code within this process address space or suspending / resuming its main thread. Since explorer’s process runs with the same user account as the currently logged-on user, formbook process doesn’t need additional privileges to perform debugging like operations. Indeed, for debugging non-system processes, it is not necessary to grant or enable SeDebugPrivilege. However, the malware author doesn’t seem to be aware of this and formbook updates its privileges in order to enable ‘SeDebugPrivilege’. To do so, it uses NtOpenProcessToken() to get an handle on its own process, and then uses ConvertSidToSidW() and NtAdjustTokenPrivileges(). The malware doesn’t check if its privileges have sucessfully been updated, thus the execution continues in any case.

Mapping a patched formbook image base within formbook’s address space

Formbook maps a copy of its running process, within its own address space, using the following steps:

  1. Create a new section, using NtCreateSection(), whose size is formbook SizeOfCode + SizeOfHeaders
  2. Map this section within its address space using NtMapViewOfSection()
  3. Copy the header from formbook base address to the new mapped section (SizeOfHeaders bytes)
  4. Copy the code from formbook base address + SizeOfHeaders to the new mapped section (SizeOfCode bytes)

As an anti-forensic technique, the header of the new mapped section is overriden with the SHA-1 of a random 32-bit integer. Thus, formbook’s PE header won’t appear in clear within explorer’s neither in the hollowed process’ address space, when it will be mapped in these processes. However, this in an issue for a function that we called ‘get_base_address_from_text()’, which retrieves the base address of formbook, by trying to find the string ‘This program cannot’ in memory, followed by the MZ header.

Thus, the random value is saved by patching an instruction from the ‘get_base_address_from_text()’ function, so that it takes another path, used to find the PE base address, by searching for the expected SHA-1 in memory. In order to apply this patch to the ‘get_base_address_from_text()’ function, a loop is performed on the whole mapped section using the ‘egg hunting’ technique to find the sequence of bytes “40 41 49 48 B8 88”:

We can find these instructions from the function ‘get_base_address_from_text()’, before beeing patched:

.text:00417D91 40                    inc     eax
.text:00417D92 41                    inc     ecx
.text:00417D93 49                    dec     ecx
.text:00417D94 48                    dec     eax
.text:00417D95 B8 88 88 88 88        mov     eax, 888888888 ; patched immediate value
.text:00417DA1 89 45 FC              mov     [ebp+patched_imm], eax
;; if eax equals 0x88888888 (not patched) search for 'This program cannot'
;; otherwise use eax to computes its SHA-1 and search for this value
.text:00417DA9 81 7D FC 88 88+       cmp     [ebp+patched_imm], 88888888h;
.text:00417DB2 74 4B                 jz      short loc_417DFF

Since Hex-Rays decompiler cannot guess that the bytecode will be patched, its evaluates the cmp instruction as always true and thus doesn’t evaluate the false branch:

Finding explore.exe’s pid and main thread id

After applying its patches, formbook tries to find explore.exe’s pid and main thread id, using the same method as for checking for blacklisted running processes. It uses NtQuerySystemInformation() with the class ‘SystemProcessInformation’ and iterates over each SYSTEM_PROCESS_INFORMATION entry in order to compute the hash of the process image name, up to finding the one related to ‘explorer.exe’.

Hijacking explorer.exe’s main thread and APC injection

After retreiving explore.exe’s pid, formbook retrieves an handle on this process, using NtOpenProcess() with the following desired access:


Then it is able to map its patched section of code, within explorer.exe address space. This new mapped section will be used to execute formbook code within explorer’s main thread, as well as to provide a shared buffer between formbook and explorer processes.

An handle on explorer’s main thread is retrieved using NtOpenThread() with the following desired access:


Thus, formbook is now able to suspend explorer.exe’s main thread using NtSuspendThread(), and to retrieve its instruction pointer within the CONTEXT structure using NtGetContextThread().
Then, another patch is applied on formbook’s section of code that will be executed by explorer. The unpatched instructions look as following:

.text:00041C17 68 88 88 88 88        push    88888888h            ; patched with CONTEXT.Eip
.text:00041C1C 60                    pusha
.text:00041C1D E8 1E 03 00 00        call    formbook_main_explorer32_hijacked
.text:00041C22 61                    popa
.text:00041C23 C3                    retn    ; end of the shellcode execution, return to CONTEXT.Eip

The patch consists in replacing the 0x88888888 immediate value by the Eip field extracted from the CONTEXT structure previously. This is used to save explorer.exe instruction pointer in order to restore it at the end of formbook code execution. As you can see, the call to ‘formbook_main_explorer32_hijacked()’, is surrounded by a pusha and popa instructions so that explorer’s process retrieves its registers values as if its thread had never been hijacked. When the return instruction is executed, explorer’s main thread will continue its execution as if it had never been hijacked.

By attaching to explorer.exe and searching for this section of code within its process address space we can see the patch:

After applying the patch, a call to NtSetContextThread() is used to update explorer’s main thread instruction pointer which is now hijhacked, and points to formbook’s code within explorer. At this point, the thread is still suspended. Then an APC is injected by calling NtQueueApcThread(), with an entry point to the user APC routine pointing on the pusha instruction, just before the call to formbook_main_explorer32_hijacked(). This is quite elegant since the same section of code is used by both context calls, one from the hijacked thread and another from the injected APC routine.

Finally, a call to NtResumeThread() is performed.

Before resuming hijacked explorer’s main thread, the kernel first pops and executes each APCs from the thread’s APC queue. Finally, the hijacked explorer’s main thread is executed and a second call to formbook_main_explorer32_hijacked() is performed.

2. Suspended process creation

Suspended process creation by explorer’s hijacked thread or injected APC

Unlike within formbook process address space, the hijacked thread of explorer doesn’t map a copy of ntdll and doesn’t perform any anti-debug trick. The full path of ntdll.dll is retrieved using the method described previously (c.f: Retreiving the original ntdll.dll full path), and is used as the ‘System32’ or ‘SysWOW64’ source directory to open a randomly choosen windows executable, from a list of 39 entries.

The windows executable list is stored in an encrypted buffer whose RC4 key is derived from the SHA-1 of the following 16 bytes buffer: “00 00 01 01 00 00 01 00 01 00 01 00 00 00 00 00”:

As explained by FireEye, this buffer is actually the same as the result of the anti-analysis tests placed into a 16-bytes array used to decide whether formbook’s process should continue its execution.

After decryption of an encrypted buffer, one can see the list of 39 windows executable (from offset 3 to 41) that can be used by formbook to migrate itself:

3 svchost.exe
4 msiexec.exe
40 wuapp.exe
41 cmd.exe

The full list of windows executable used to perform process hollowing is available here.

The choosen executable image base is then used to create a suspended process with a call to the undocumented function CreateProcessInternalW(), exported by kernel32.dll. It is interesting to notice that, for this function especially, the associated decrypted hash (0xad0121e0) is modified with a simple substraction before trying to perform the dynamic import:

>> hex(Crc32Bzip2.calc(bytearray("CreateProcessInternalW".lower())))
>>> 0xad0121e0-0xad0121ab

The following information related to the newly created process are copied in a memory area shared between explorer and formbook process:

  • The process’ image base full path
  • The process’ image base address
  • The process’ PROCESS_INFORMATION structure
  • The process’ STARTUPINFO structure

This shared memory area is contained in the same section as formbook image mapped into explorer. Thus, in formbook process address space, they have the same view base address resulting from NtMapViewOfSection(), and thus have the same page protection (PAGE_EXECUTE_READWRITE).

Now, either the injected APC or the hijacked thread have finished their work. Note that if the APC is sucessfully executing within explorer, a flag is set within the shared buffer that will be tested by the hijacked thread, to avoid creating a second suspended process. Thus, we can conclude that hijacking explorer’s main thread is useless if the APC is always successfully executed.

3. Migration into the new suspend process

Just after its call to NtResumeThread(), formbook needs to wait for the suspended process to be created. Thus, it sleeps using NtDelayExecution() and then tries to read from the shared memory area the information filled by explorer’s injected APC or explorer’s hijacked thread.

If the newly created process’ pid, main thread id, image base full path and base address are successfully read and have non-null values, formbook will be able to perform its process hollowing.

An handle on the targeted process and targeted thread are retrieved using NtOpenProcess() and NtOpenThread(). Based on the retrieved image base full path, formbook maps a raw copy of the PE file from the disk image base. Thus, it is able to parse the PE header and to extract the address of entry point of the suspended process.

Now, formbook will map itself within this process, such as it did previously to map itself within explorer’s address space. Then, it will override the first instructions of the suspended process entry point, in order to perform a call to a function that calls ‘formbook_main_migrated()’. The patch is built from stack-based operation, and looks as following:

8b ff          mov edi, edi
55             push ebp
8b ec          mov ebp, esp
e8 00 00 00 00 call 0x00000000 ; immediate value to be patched

The operand of the call is then fixed with the relative offset to a formbook’s instruction which calls ‘formbook_main_migrated()’. Let’s take a look at a real example, where the randomly choosen process is ‘wuauclt.exe’:

With WinDbg and CFF explorer we can see that:

  • wuauclt.exe image base address is 0x00060000
  • wuauclt.exe relative address of entry point is 0x5891
  • formbook has been mapped within wuauclt.exe at 0x00160000
  • formbook’s instruction used to call ‘formbook_main_migrated()’ is located at the 0x17b73d
  • formbook’s function ‘formbook_main_migrated()’ is located at 0x177eb0

By adding the address following the call instruction from wuauclt’s patched entry point (0x60000+0x5891+10), to the operand of the call instruction (0x00115ea2) we can see that the destination of the call is formbook’s instruction used to call ‘formbook_main_migrated()’:

>>> hex((0x6589b+0x115ea2)&0xffffff)

Finally, after patching the suspended process entry point, a call to NtResumeThread() is performed, and formbook starts the execution of ‘formbook_main_migrated()’ within the migrated process. The original formbook process can thus stop its execution by calling ExitProcess().

Injecting targeted application

Once formbook has migrated within the newly created windows process, it can finally target applications containing sensitive information. Within an infinite loop (while a debugger isn’t attached), it tries to find process name’ hashes matching in the array of encrypted hashes (from offset 120 to 211). From the list of decrypted hashes, we can view that it consists of different kind of applications such as web-browsers, mail programs, instant messaging applications, FTP clients or even Skype:

120 0x9e01fc32 iexplore.exe
121 0x216500c2 firefox.exe
122 0x48e207c9 chrome.exe
123 0x2decf13e microsoftedgecp.exe
173 0x84f8d766 foxmail.exe
174 0xb6494f65 incmail.exe
175 0x13a75318 thunderbird.exe
178 0x6b8a0df3 yahoomessenger.exe
179 0x9c02f250 icq.exe
180 0xe52a2a2e pidgin.exe
196 0xea653007 filezilla.exe
211 0xcb591d7f skype.exe

We haven’t yet analyzed in-depth this part of the malware, but the code injection techniques looks similar to what we explained in this post. Formbook maps itself within the targeted processes and uses thread hijacking and APC injection to execute different kind of routines, according to the targeted process.

  • kao
    Posted at 20:28h, 03 April Reply

    Your 3 unknown blacklisted processes are:
    89 0x21b17672 prl_tools_service.exe
    90 0xbba64d93 prl_tools.exe
    92 0x9cb95240 SharedIntApp.exe

    • mm
      Rémi Jullian
      Posted at 11:14h, 04 April Reply

      Thanks for your comment kao! I updated the list of hashes as well as our git repository used to find this kind of processes’ hashes. For the fun story, you would have won a beer if you were working in our team since it was the reward for missing hashes 😉

Post A Comment