Win32/64:Blackbeard & Pigeon: Stealthiness techniques in 64-bit Windows, Part 2
Last week we promised to explain in detail how the "Blackbeard" Trojan infiltrates and hide itself in a victim’s system, especially on its 64-bit variant. Everything described in this blogpost happens just before Pigeon (clickbot payload) gets downloaded and executed. The most interesting aspects are the way it bypasses the Windows' User Access Control (UAC) security feature and switches the run of 32-bit code of the Downloader to 64-bit code of the Payload. And finally, how the persistence is achieved.
As almost all other malware, this downloader is encapsulated with a cryptor. After removing the first layer cryptor, we can see that the downloader is written in a robust way. The same code can be run under either a 32-bit or 64-bit environment, which the code itself decides on the fly based on the entrypoint of the unpacked layer. Authors can therefore encapsulate their downloader in either a 32-bit or 64-bit cryptor and it will get executed well in both environments.
At first, we notice a sequence of push, pop, rol, test, jnz instructions. These instructions, when run in different environments, produce different results. Depending on the result, a conditional jump is either taken or not taken. These initial instructions run on both 32-bit and 64-bit environment. After the first jump (JNZ) there are two branches, one runs in 32-bit and WoW64 environment, the other runs in 64-bit environment.
Under the 64-bit environment, value 0x40000000 is pushed into the RCX register. Even after rotation by 2 positions to the left, ECX still remains zero (ZF is set), so JNZ jump is NOT taken. The 64-bit code is therefore executed. Downloader simply gets its image base, adds the relative virtual address of 64-bit payload function and executes it.
Under the 32-bit environment value, 0x40000000 is pushed into the ECX register, which is then rotated by 2 positions to the left, causing ECX to hold value 1. Instruction TEST ECX, ECX does not set zero flag (ZF), so JNZ jump is taken and the 32-bit branch of code is executed.
32-bit applications can be executed on both 32-bit or 64-bit operating systems. To decide what the processor architecture is, the downloader calls GetNativeSystemInfo function. The expected result is either 0x00, which stands for PROCESSOR_ARCHITECTURE_INTEL (x86) or 0x09, which stands for PROCESSOR_ARCHITECTURE_AMD64 (x64).
The code below resolves the address of GetNativeSystemInfo function and calls it. It then compares the wProcessorArchitecture information (0x4010e0). If x86 architecture is detected, JNZ at address 0x4010ee is not taken and function inWin32 is executed. If x64 architecture is detected, JNZ at address 0x4010ee is taken and code starting from address inWoW64 is executed.
When executing the 32-bit application on a 64-bit operating system, the 32-bit application runs in emulation of a 32-bit operating system which is called Windows on Windows64 (shortened to WoW64). WoW64 intercepts system calls made by the 32-bit application, converts 32-bit data structures into 64-bit data structures and invokes 64-bit system calls. After the 64-bit system call is finished, it translates any output data structures from 64-bit back to 32-bit data structures. WoW64 subsystem is implemented using three dynamic-link libraries: Wow64.dll, Wow64win.dll, and Wow64.cpu.dll. Wow64.dll takes care of translations from 32-bit to 64-bit, Wow64win.dll provides entry points for 32-bit applications and Wow64cpu.dll provides switching the processor from 32-bit mode to 64-bit mode. The interesting part of downloader starts at address 0x40112c, where it calls the function at 0x401000.
Notice the function's second parameter 0xad70. It will be used later in this analysis.
The called function then resolves base addresses of the three above mentioned fundamental WoW64 dll libraries. In addition to them, it also retrieves the virtual address of an important structure from Wow64win.dll - sdwhwin32JumpTable.
The memory span of all fundamental WoW64 modules is displayed using WinDBG's lm command.
At 0x40105f, it resolves the address of GDI32!BRUSHOBJ_hGetColorTransform, which is a function exported from gdi32.dll library. The resolved address is stored in EBX register. Later, at 0x401066, the system call ordinal is extracted from the first instruction of the call.
If we want to get the address of function in sdwhwin32JumpTable, first we subtract 0x1000 from its ordinal number, then multiply it by 8 (in 64-bit system, each pointer has 8 bytes) and add it to the beginning of sdwhwin32JumpTable table. In our case, 0x78bbfae0 + 0x129 * 8 = 0x78bc0428.
The Figure below shows the address of BRUSHOBJ_hGetColorTransform stored in sdwhwin32JumpTable.
However, in the analyzed downloader, something more stealthy is going to happen. At 0x401083, the address in sdwhwin32JumpTable corresponding to BRUSHOBJ_hGetColorTransform is overwritten by a user-specified address (0x40ad70). See the following two figures, which represents the described situation before and after overwriting is done.
Then we execute CALL EBX (0x40108a), which is supposed to execute BRUSHOBJ_hGetColorTransform. The instruction flow then continues into theGDI32.dll library (BRUSHOBJ_hGetColorTransform), where we can see a call to fs:[0xc0], which is wow64cpu!X86SwitchTo64BitMode.
Dword at address fs:[0x0c0] contains an address with a jump causing switch to 64-bit environment (segment 0033h determines 64-bit environment).
Before calling Wow64SystemServiceEx, several interesting parameters are passed: 0x7559fae0 is the beginning of sdwhwin32JumpTable in wow64win.dll; 0x129 is ordinal of BRUSHOBJ_hGetColorTransform in sdwhwin32JumpTable; and 0x766e5c55 is the beginning of BRUSHOBJ_hGetColorTransform in GDI32.dll.
Inside of Wow64SystemServiceEx, address of BRUSHOBJ_hGetColorTransform in sdwhwin32JumpTable is computed and called at 0x755bcf84(call r12). Instead of the original address, code from the patched address is executed.
At this moment, a 64-bit payload within the loader gets executed. The 64-bit payload begins at address 0x40ad70.
You might wonder why such a complicated transition from 32-bit to 64-bit environment is done. Malware could of course run only in 32-bit environment. 64-bit version would not be necessary. However the described malware is just a downloader and loader which downloads another payload. That payload want essentially access even 64-bit running processes (for example web browsers) and inject some payloads to them. Although there have been some hacks which describe how to access memory of 64-bit process from 32-bit process, it is easier to use straightforward 64-bit -> 64-bit access.
Before executing the payload itself, it is important to make sure that it runs with elevated privileges. Without that, privilege for taking ownership (SeTakeOwnershipPrivilege) cannot be acquired, security permissions to system files cannot be changed, and persistence cannot be established. An important part of the code occurs around at address 0x409a2e. Depending on the function parameters, getDelta can resolve in four different addresses and later at 0x409a90 calling four different functions. If one function fails, another one is called.
The first function (0x409bcc) does nothing special, it just tries to acquire SeTakeOwnershipPrivilege. If malware is not run with elevated privileges, this function fails.
The second function (0x409efc) is more interesting. In the figure below, you can see resolving ShellExecuteW with parameter "runas," which tries to run a file with elevated privileges. The code uses sysprep.exe, which is a tool that "prepares an installation of Windows for duplication, auditing, and customer delivery." Sysprep.exe is an application which needs administrative rights every time it is executed, and it is also a whitelisted UAC application. Whitelisting allows users with lower right than administrative to run applications with full administrative rights and UAC settings is still set to the highest security.
There is a proof-of-concept for Windows 7 UAC whitelisting, which uses the above mentioned feature. At first, a random dll library is copied from windows\system32 folder to %APPDATA%\Roaming folder under a randomly generated name. The newly copied file is patched and then, with the help of IFileOperation, it is copied into the sysprep directory under the name cryptbase.dll. When sysprep.exe gets executed, it loads the cryptbase.dll library from the system32 directory. If we put a fake cryptbase.dll library into the sysprep directory, it will load the fake library instead of the real one. Sysprep is an elevated process, so everything which it loads is also elevated. To bypass UAC on Windows 7, it is necessary to be an administrator. A second function also checks SIDs to make sure an administrator account is present. If it is run under only a standard user account, the second method fails.
The third function (0x4086c0) exploits CVE-2013-3660. If this function succeeds, a standard user can run programs under system privileges.
The last function (0x409c40) tries to run rundll32.exe <random dll>, System1. A random dll is created with the same method as mentioned in the second function; it is also stored in the same location (%APPDATA%\Roaming).
A few times, we mentioned that a system DLL is copied into %APPDATA%\Roaming and patched. Let's look how this DLL library is patched. The main function of the DLL library is overwritten. Instead of its original function, it opens a previously created section object with the dowloaded payload and calls its entrypoint function, which is 0x40ad70. It uses just four imported functions - NtOpenSection, NtMapViewOfSection, NtOpenEvent, NtSetEvent. To resolve addresses of these imports, references to these libraries are overwritten in DLL's import table.
It may lead to situations where one import is present twice.
Finally, the overwritten dll main function: The orginal DLL is on the left, the patched one is on the right. Notice the name of the section object (unicode string in the screenshot below starting with \Session\1\...).
If an attempt to bypass UAC via the above mentioned methods are not successful, users may encounter (depending on UAC settings) one or more dialogs as displayed in figures below. The user is presented with a prompt where important system programs ask for higher privileges (in the following order - File Operation, System Preparation Tool, Windows host process(Rundll32)). If the user clicks no, no infection happens. However, if the UAC bypass is successful, the user gets infected and no UAC is displayed (no matter what UAC settings are).
In this section, we use screenshots from a 32-bit version, but the 64-bit variant works on the same principle.
The initial stage of the downloaded payload establishes persistence on the infected system. Unlike many other pieces of malware which modify registry keys or copy itself into the Startup folder, we encountered a much stealthier and more complicated form of persistence. Instead of modifying the above mentioned registry keys, an important system DLL library is patched so that the payload is executed every time the operating system starts. Rpcss.dll is the chosen library to be patched. RPCSS stands for Remote Procedure Call System Service, which is a core service of RPC (Remote Procedure Call). This is an important technology for creating distributed client/server programs, running on all Windows machines. It is an important system file, so malware needs to perform a few steps before being able to overwrite it.
First, it attempts to acquire SeTakeOwnershipPrivilege. This privilege allows it to take ownership of any file. The default owner of rpcss.dll is a user called "TrustedInstaller," who is the only one with full access to this system file (read, write, execute.) All other users, including SYSTEM, have only read and execute privileges by default. However, with SeTakeOwnershipPrivilege enabled, the owner of the rpcss.dll can be changed to the current user. Malware then creates a new access control list (ACL) with two access control entries (ACE) - current user and SYSTEM. This access list is then assigned (using SetNamedSecurityInfoW) to the rpcss.dll file. The result is that there are only two users with read/write/execute access - the current user and SYSTEM. Now it is possible to patch the dll library.
When patching the existing library, the best practice is to locate a block full of zeroes and replace it with executable code. However, payload related to Blackbeard/Pigeon has more than 100 KB, so it is not possible to find such a big block of zeroes within the rpcss.dll. Rpcss.dll contains only a small stub, which reads, decrypts, and executes previously encrypted payload from a randomly named file in Windows\system32 directory. The payload is encrypted with a single byte XOR operation. You can see the repeating character 'O' in the figure below, which can give a hunch that the whole file could be decrypted by XORing with character 'O'.
If a regular user notices a suspiciously named file in the Windows\system32 directory and if he tries to open the file and read it, access to the file will be revoked, because only the SYSTEM user has the right to do so. Rpcss.dll is executed by SYSTEM, so there is no problem in locating and reading the payload. Under Windows XP machines, rpcss.dll is located once in Windows\system32 and once in Windows\system32\dllcache. Both instances must be patched. The loader also disables the Windows File Protection (WFP) mechanism by calling undocumented API with ordinal 5 from sfc_os.dll library. SfcFileException, as described in this article, should disable WFP on a specific file for 1 minute.
Rpcss.dll is a library. It is not patched at its main function (Dllmain) entrypoint. Malware localizes gaServiceEntryTable structure and offset where pointer to KernelServiceMain is stored. The pointer to this function is patched so that it points to the newly inserted block of data.
Patched KernelServiceMain starts with getDelta assembly sequence (call $+5, pop), which returns the current address. Then it keeps subtracting 0x1000, until it finds signature (MZ) which is the base address of the currently loaded library. Decrypt_string is a simple XOR loop, which decrypts the block of memory with the name of the file with the encrypted payload.
It also allocates a new memory block (VirtualAlloc) where it stores the decrypted payload. The same decrypt_string function is used for payload decryption. The pPayload is then executed by CALL EDX (0x76abed59). After thepayload is executed, it jumps to the original KernelServiceMain (JMP EAX).
The left figure shows gaServiceEntryTable structure with original address of KernelServiceMain, the right figure shows the patched one.
At this moment, malware is persistent on the compromised system. There are no traces of infection in the registry. The standard user and even administrator can notice only randomly named files in windows\system32 directory, but he/she has no access to these files (only SYSTEM can access them.)
Now it is time to spawn a internet communication thread and download another payload and install it on compromised system.
Win32:Blackbeard-F [Trj] (drive-by-download)
Win64:Patched-A (patched 32-bit rpcss.dll)
Win32:Patched-AOD (patched 64-bit rpcss.dll)
Win64:Blackbeard-A (patched randomly chosen and copied 64-bit dll library)
The authors would like to thank to David Fiser for consultations related to this analysis and Veronika Begánová for a great piece of pop art used as promotional picture.
Far from sci-fi depictions, artificial intelligence – through machine learning algorithms and big data – is key to defusing today's evolving cyberthreats.
Defeating today’s – and tomorrow's – cybercriminals requires man and machine to collaborate, intelligently. This is the heart of next-gen cybersecurity.