When ELF.BillGates met Windows

If you are used to play with honeypots, you have inevitably met the ELF.BillGates malware. It is a known[1] botnet spread over Internet for 4 years.

In a nutshell, ELF.BillGates is a (Chinese) DDOS botnet with backdooring features. It is a binary file with many behaviors depending on the installation path[2]:

  • Gate 0: Infection Monitor (dropper + persistence)
  • Gate 1: Host (Contact C&C + DDOS features)
  • Gate 2: Backdooring
  • Gate 3: Utility spoofing

The “Elf.BillGates” version targets Linux operating system. We have followed the activities of this botnet for several months and during our investigations we found some versions of a Windows fork of the malware. This article attempts to detail this variant.

The primary infection vector is the exploit of the vulnerability CVE-2014-6332[3], which drops the binary file hosted on an HTTPd File Server (HFS)[4]. This vulnerability allows an attacker to escape the Internet Explorer sandbox with a VBScript script and execute an arbitrary binary file downloaded from the Internet.



Figure 1 – Example of compromised HFS server

First and foremost, we noticed that this malware seems to be currently in development. The author seems to make tests in the wild, and several samples are unstable.

In a few weeks, we collected about thirty samples, and we identified 2 different versions of the malware:

    • A version almost working on Windows XP but unstable on more recent operating systems.
    • A very unstable version based on Safeengine protector (a packer against reverse engineering)[5].

Both versions reference the same symbol path:

重构 can be translated by builder.

This article analyzes a sample of the first family named 36000.exe (sha1: 4b14d7aca890642c3e269b75953e65cb)

GatesInstall – Gate 0 – Infection monitor

PDB: F:\\Updates\\重构\\GatesInstall\\Release\\GatesInstall.pdb

This is the installation part of the malware, that will drop the different files in the system, and create persistence.

This sample in not obfuscated, but we have met some UPX packed samples.

This binary file embeds seven executable resources.


Figure 2 – PEStudio view of the binary


As we can infer from the PDB path, this binary file is the installer of Win32.BillGates malware.

On its first execution, it checks if the system is not already infected by trying to kill BillGates instance with the system tool taskkill.exe :
Taskkill /F /IM DbSecuritySpt.exe
Taskkill /F /IM Bil.exe
Taskkill /F /IM svch0st.exe
Taskkill /F /IM DNSClient.exe
Taskkill /F /IM DNSProtection.exe

/F is for killing process, /IM is the image name.

After this check, the malware checks the OS version with GetOsVersionExA and fills a global variable with the following value. It is supposed to support all versions of Windows:

Windows Server 2008 R2
Windows Server 2008
Windows 7
Windows Vista
Windows Server 2003
Windows XP
Windows 2000
Windows NT
Windows 32s
Windows Unknown

After that, it checks if it runs on a 32 or a 64-bit OS with the help of the GetSystemWow64DirectoryA API.

Happy to play with an old Windows installation, I tried to launch the installer on Windows 2000 but I was disappointed: GetSystemWow64DirectoryA is only available starting from Windows XP, so the process does not start due to this unresolved reference:


Figure 3- Error: Unable to find entry point of GetSystemWow64DirectoryA Proc on kernel32.dll

The detection of OS older than Windows XP is then pretty useless.

After that check, the malware installation depends on the version of the OS.

On Windows 2003 / XP, the following files are created:

C:\Program Files\DbSecuritySpt\DbSecuritySpt.exe (resource 107 or 108)
C:\Program Files\DbSecuritySpt\svch0st.exe (resource 104)
C:\Program Files\Windows Media Player\agony.exe ( resource 103)
C:\Program Files\Windows Media Player\agony.sys (resource 102)
C:\Program Files\Windows Media Player\DNSProtection.exe (resource 107 or 108)
C:\Program Files\Windows Media Player\DNSSupport.exe (resource 107 or 108)

On Windows 2008 Server, two additional files are created:

C:\Program Files\DbSecuritySpt\NPF.sys (resource 105)
C:\Program Files\DbSecuritySpt\packet.dll (resource 106)

DbSecuritySpt.exe, DNSSupport.exe and DNSProtection.exe have the same contents. On the 32-bit edition of the OS, resource 107 is used whereas resource 108 is used on the 64-bit variant of the OS.

After several tests, Win32.Billgates is only able to start on Windows XP. On newer versions of Windows, the installer simply crashes. This crash seems to be related to ASLR. In fact, when the code attempts to retrieve the security cookie in functions handling buffers, it references a hard-coded address as if the binary file was loaded at a fixed address. This generates an access violation.

afterviolation1 afterviolation2

The rest of this article details the analysis of the malware on Windows XP.

Once the binary files are written to disk, GateInstall launches DbSecuritySpt.exe and DNSSupport.exe as services. Creating services requires administrator privileges. In most cases, attackers gain administrator privileges by brute forcing administrator RDP account on Windows Server 2003 computers.

That’s all for the installer.


GateInstall writes the same binary file in 3 locations:

C:\Program Files\DbSecuritySpt\DbSecuritySpt.exe (resources 107 or 108)
C:\Program Files\Windows Media Player\DNSProtection.exe (resources 107 or 108)
C:\Program Files\Windows Media Player\DNSSupport.exe (resources 107 or 108)

PDB: F:\\Updates\\重构\\GatesInstall\\Release\\Gates.pdb

Gates starts by an identification routine:

      • Decryption of its configuration
      • Check of the file path and if it is launched as a service.

The configuration is encrypted with a hard-coded RSA 1024 key:


Once decrypted, the configuration data is organized in the same way as the ELF version[6]:


In the Windows version, Prime C, D and modulus N offset are hard-coded, meaningless and not used.

In this sample we noticed an empty campaign name, but other analyzed samples were linked? to a named campaign:

The Windows binary file also contains some clear strings that allow us to say it is a variant of the ELF version:


DbSecuritySpt – Gate 1 – Host

Launched as a service, DbSecuritySpt is the main persistent binary file that is run. To get into DbSecuritySpt behavior, the binary file must be launched as a service from C:\Program Files\DbSecuritySpt\DbSecuritySpt.exe.

DbSecuritySpt launches several threads in charge of fingerprinting the computer, communicating with the C&C infrastructure and performing DDos actions.

The following data is sent to the C&C:

DbSecuritySpt – Gate 1 – Host


This service is also in charge of taking part of DDoS campaigns.

DbSecuritySpt is supposed to support several DDoS types: ICMP, SYN UPD and DNS amplification.

The binary file contains a list of 230 hardcoded IP addresses that correspond to DNS servers used for DNS amplification attacks[7].


We tested these DNS servers. Only 58 IP addresses seem to be still vulnerable. The other servers were either patched or unreachable.

svch0st – Gate 2 – Backdoor

PDB: E:\SVN\trunk\2014\小陈\重构\IECtrl\Release\IECtrl.pdb

小陈 can be translated as Chen and重构 as builder.

At last, GateInstall drops the binary file C:\Program Files\DbSecuritySpt\svch0st.exe.

The original name of this file is IECtrl.exe. IECtrl is an independent tool also used by other malwares (such as Win32:Wapomi-B https://www.virustotal.com/fr/file/4d7d9a80973b61f5fecdfdcd2e050ed9bc9541ad82ff68c864d851632ca16a77/analysis/ )

It implements the backdoor functionalities of Win32.BillGates. This tool is identified by Microsoft as « Trojan:Win32/WebToos.B ».

DbSecuritySpt.exe passes a list of C&C server URLs as a parameter to IECtrl. IECtrl contains the logic to download, extract and execute payload from these URLs.

DNSSupport – Gate 3 – Spoofing utility

DNSSupport must be run as a service from the location C:\Program Files\Windows Media Player\DNSSupport.exe. Its behavior is simple: it is in charge of launching DNSProtection.exe and leaves the process in an infinite loop preventing the service from being stopped.

Spoofing utility


DNSProtection is a “spoofing utility” Gate. It is not functional in the analyzed sample. However, static analysis of the binary file allows drawing some conclusions about its internal behavior.

DNSProtection is used for hiding infection traces. It uses the rootkit Agony. Agony is composed of an executable (agony.exe) that loads and runs a driver (agony.sys). This rootkit was released in the wild some years ago. It is used for hiding files, services and network connections. This malware uses DNSProtection for hiding all dropped files (DNSSupport.exe, DNSProtection.exe, DbSecuritySpt.exe, agony.sys, agony.exe and svch0st.exe) and the connections to the C&C servers.


Agony.sys cannot be loaded on a 64-bit version of the operating system as it is not signed.


Win32.BillGates developers seem not to be used to develop malwares for the Windows operating system. They use poor techniques that can easily be detected by anti-virus software, and the limitations in terms of operating system compatibility could be easily avoided. This Windows port should not be a big threat as the ELF version is.

ELF structure compared with Windows version:

      • GateInstall : Gate 0
      • DbSecuritySpt : Gate 1
      • Svch0st : Gate 2
      • DNSSupport / DNSProtection : Gate 3


During our analysis, we noticed some samples with strange behaviors (hooking, binary file infection, IRC connections …). After further analysis it appears that some samples were infected by Win32.Virut and Win32.parite viruses. Virut and Parite are viruses that infect ‘.exe’ and ‘.scr’ Windows binary files on disk. It is possible that the crooks using BillGates malware are working on infected systems.

This may also explain why a lot of Win32.Parite cleaning tools were discovered on several malicious working BillGates C&C servers we visited. J

Here is a screenshot of such a tool:


About 30% of analyzed samples were infected by Win32.Parite and 20% by win32.Virut.



Some Win32.BillGates hashes:


Win32.BillGates infected by Win32.Virut:


Win32.BillGates infected by Win32.Parite:



      • Created files :
        • C:\Program Files\DbSecuritySpt\DbSecuritySpt.exe
        • C:\Program Files\DbSecuritySpt\svch0st.exe
        • C:\Program Files\Windows Media Player\agony.exe
        • C:\Program Files\Windows Media Player\agony.sys
        • C:\Program Files\Windows Media Player\DNSProtection.exe
        • C:\Program Files\Windows Media Player\DNSSupport.exe
        • C:\Program Files\DbSecuritySpt\NPF.sys
        • C:\Program Files\DbSecuritySpt\packet.dll
      • Created services:
        • DbSecuritySpt
        • DNSSupport
      • Running processes:
        • DbSecuritySpt
        • DNSSupport
        • DNSProtection
        • exe

[1] https://www.botconf.eu/wp-content/uploads/2014/12/2014-2.10-Chinese-Chicken-Multiplatform-DDoS-Botnets.pdf

[2] http://www.novetta.com/wp-content/uploads/2015/06/NTRG_ElasticBotnetReport_06102015.pdf

[3] https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2014-6332

[4] http://blog.malwaremustdie.org/2014/11/china-elf-botnet-malware-infection.html

[5] http://www.safengine.com/en-us 

[6] http://www.novetta.com/wp-content/uploads/2015/06/NTRG_ElasticBotnetReport_06102015.pdf

[7] https://blog.cloudflare.com/deep-inside-a-dns-amplification-ddos-attack/

Related works:

MalwareMustDie : http://blog.malwaremustdie.org/2014/11/china-elf-botnet-malware-infection.html

Avast: https://www.botconf.eu/wp-content/uploads/2014/12/2014-2.10-Chinese-Chicken-Multiplatform-DDoS-Botnets.pdf

Novetta: http://www.novetta.com/wp-content/uploads/2015/06/NTRG_ElasticBotnetReport_06102015.pdf

habrahabr.ru: http://habrahabr.ru/post/213973/

How to run userland code from the kernel on Windows


Before Windows NT 4.0, the graphical part of the Windows subsystem was implemented completely in userland. Starting from NT 4.0 Microsoft decided to move a large part of the Window Manager and the Graphics Device Interface to kernel-mode in the Win32k.sys component. However, part of the implementation is still present in userland and the kernel component needs to call back user-mode code. To do so, Microsoft implemented a ‘reverse’ system call, allowing the kernel to call userland code. The whole process has already been discussed and explained in previous articles so we will not detail it again. Please refer to Tarjei Mandt white paper that contains a comprehensive description of the mechanism. In this post, we detail how Windows (from Windows XP to Windows 8) uses this mechanism to load modules in running processes. Understanding the mechanism may allow you to use it for your own purposes, in particular as a way to inject custom DLLs in processes while running code in the kernel portion of the Windows operating system. A recent article published by @zer0mem uses the ‘reverse’ system call mechanism to execute code in user-mode from kernel code. This post offers an alternative approach if you are free to drop a Windows binary file on the file system.

User-mode callbacks walkthrough

General mechanism

The function that enables to call user code from kernel is located inside the Windows kernel and is an exported function named KeUserModeCallback. The prototype of KeUserModeCallback is

The initialization takes place in the function user32!UserClientDllInitialize (the entry point of the user32 DLL) and basically makes the KernelCallbackTable field point to the non-exported user32!apfnDispatch symbol.

NTSTATUS KeUserModeCallback (
IN ULONG ApiNumber,
IN PVOID InputBuffer,
IN ULONG InputLength,
OUT PVOID *OutputBuffer,
OUT PULONG OutputLength

Since this function is not present in any WDK header, you have to retrieve it dynamically with the help of an MmGetSystemRoutineAddress call.

What this function basically does is copying these parameters onto the userland stack and returning back to userland code (in ntdll!KiUserCallbackDispatcher function).

Callable functions are identified by the ApiNumber parameter. This is a zero-based index in an array accessible through the KernelCallbackTable field of the Process Environment Block.

This field is initialized when the user32 module is loaded in the process (before initialization, the field is NULL). The initialization takes place in the function user32!UserClientDllInitialize (the entry point of the user32 DLL) and basically makes the KernelCallbackTable field point to the non-exported user32!apfnDispatch symbol.

kd> dt nt!_PEB @$peb
+0x000 InheritedAddressSpace : 0 ''
+0x001 ReadImageFileExecOptions : 0 ''
+0x002 BeingDebugged : 0 ''
+0x003 SpareBool : 0 ''
+0x004 Mutant : 0xffffffff Void
+0x008 ImageBaseAddress : 0x00400000 Void
+0x00c Ldr : 0x00251e90 _PEB_LDR_DATA
+0x010 ProcessParameters : 0x00020000 _RTL_USER_PROCESS_PARAMETERS
+0x014 SubSystemData : (null)
+0x018 ProcessHeap : 0x00150000 Void
+0x01c FastPebLock : 0x7c990620 _RTL_CRITICAL_SECTION
+0x020 FastPebLockRoutine : 0x7c911000 Void
+0x024 FastPebUnlockRoutine : 0x7c9110e0 Void
+0x028 EnvironmentUpdateCount : 1
+0x02c KernelCallbackTable : 0x7e392970 Void
+0x030 SystemReserved : [1] 0
+0x034 AtlThunkSListPtr32 : 0

This table contains function pointers to various userland callable functions, all of them located in the user32 module. The contents (thus the index of the functions) and the length of the table depend on the operating system version.

Here is an example (truncated) displaying a function table in the Windows XP SP3 32 bits process:

kd> dps 0x7e392970 L0n98
7e392970 7e3a7f3c USER32!__fnCOPYDATA
7e392974 7e3d87b3 USER32!__fnCOPYGLOBALDATA
7e392a38 7e3d8eb9 USER32!__ClientCopyDDEIn1
7e392a3c 7e3d8efb USER32!__ClientCopyDDEIn2
7e392a40 7e3d8f5e USER32!__ClientCopyDDEOut1
7e392a44 7e3d8f2d USER32!__ClientCopyDDEOut2
7e392a48 7e3aeb09 USER32!__ClientCopyImage
7e392a4c 7e3d8f92 USER32!__ClientEventCallback
7e392a50 7e3b19f6 USER32!__ClientFindMnemChar
7e392a54 7e3a28f3 USER32!__ClientFontSweep
7e392a58 7e3d8e4c USER32!__ClientFreeDDEHandle
7e392a5c 7e3a82ff USER32!__ClientFreeLibrary
7e392a60 7e39f4b2 USER32!__ClientGetCharsetInfo
7e392a64 7e3d8e83 USER32!__ClientGetDDEFlags
7e392a68 7e3d8fdc USER32!__ClientGetDDEHookData
7e392a6c 7e3cf9f5 USER32!__ClientGetListboxString
7e392a70 7e39ec46 USER32!__ClientGetMessageMPH
7e392a74 7e3a16eb USER32!__ClientLoadImage
7e392a78 7e3a8023 USER32!__ClientLoadLibrary
7e392a7c 7e3aec03 USER32!__ClientLoadMenu
7e392a80 7e39ee0d USER32!__ClientLoadLocalT1Fonts
7e392a84 7e3a09e4 USER32!__ClientLoadRemoteT1Fonts
7e392a88 7e3d907b USER32!__ClientPSMTextOut
7e392a8c 7e3d90d1 USER32!__ClientLpkDrawTextEx
7e392a90 7e3d9135 USER32!__ClientExtTextOutW
7e392a94 7e3d919a USER32!__ClientGetTextExtentPointW
7e392a98 7e3d9019 USER32!__ClientCharToWchar
7e392a9c 7e39ed14 USER32!__ClientAddFontResourceW
7e392aa0 7e39a13e USER32!__ClientThreadSetup
7e392aa4 7e3d9253 USER32!__ClientDeliverUserApc
7e392aa8 7e3d91f1 USER32!__ClientNoMemoryPopup
7e392aac 7e3aa740 USER32!__ClientMonitorEnumProc
7e392ab0 7e3d944a USER32!__ClientCallWinEventProc
7e392ab4 7e3d8e15 USER32!__ClientWaitMessageExMPH
7e392ab8 7e3acf8e USER32!__ClientWOWGetProcModule
7e392abc 7e3d948d USER32!__ClientWOWTask16SchedNotify
7e392ac0 7e3d9266 USER32!__ClientImmLoadLayout
7e392ac4 7e3d92c2 USER32!__ClientImmProcessKey
7e392af4 7e3d950c USER32!__fnOUTLPSCROLLBARINFO

Conditions for calling KeUserModeCallback

Before calling KeUserModeCallback, you must first check the KernelCallbackTable field of the Process Environment Block is not NULL (KeUserModeCallback will not do this for you). This field is at offset 0x2c on a 32-bit system and 0x58 on a 64-bit system (from Windows XP to Windows 8). Omitting to do so will eventually lead to a BSOD.

On Windows XP, the operating system does not place any condition on the state of the current thread for calling the KeUserModeCallback function, so it is safe calling the function whenever you want.

Starting from Windows Vista, things are different. Indeed, the KeUserModeCallback function checks for the presence of the CallOutActive flag in the Flags field of the current _KTHREAD structure (this field is set at least by the nt!KeExpandKernelStackAndCalloutEx function). If present, the operating system issues a bugcheck with a 0x107 undocumented code.

On Windows 8, the Microsoft developers added even more constraints to allow the call to succeed.

The first check performed by Windows 8 is ensuring the current thread runs at PASSIVE_LEVEL. If not, the operating system issues a bugcheck with code 0x4A (IRQL_GT_ZERO_AT_SYSTEM_SERVICE).

Then, the operating system checks if APCs are enabled. If not, the operating system issues a bugcheck with code 1 (APC_INDEX_MISMATCH).

Finally, the operating system checks the CallbackNestingLevel field of the current thread. If this value reaches 32, the function fails with a code equals to 0xC00000FD (STATUS_STACK_OVERFLOW). This field is set by KeUserModeCallback to record the number of nested calls to user-mode callbacks.

 User-mode callback for loading a library

Among the interesting functions, we can notice the user32!__ClientLoadLibrary function pointer.

This functionality is natively used by win32k.sys to inject the uxtheme.dll in running processes, allowing the operating system to apply visual styles to applications.

This operation is twofold. First, it effectively loads the module in the process memory, as if it were loaded by userland code. Then, a function called ThemeInitApiHook is invoked giving uxtheme.dll a chance to provide alternate implementations for various functions used by user32. We will not dive into the details of how this initialization function is called and what the patched functions are used for. We will just try to describe the parameters needed to load a module without calling any specific initialization function.

Function index

The first parameter requested is the ApiNumber. The value for the ‘load library’ feature depends on the operating system version.

Capture d’écran 2014-04-08 à 16.51.22
From now on, the index does not depend on the operating system flavor (32 bits or 64 bits).

Input buffer

The second and third parameters of the functions are the input buffer and its associated length, in bytes.

The input buffer for the ‘load library’ feature is described by the following structure:

typedef struct _USERHOOK
DWORD      dwBufferSize;
DWORD      dwAdditionalData;
DWORD      dwFixupsCount;
LPVOID     pbFree
DWORD      offCbkPtrs;
DWORD      bFixed;
DWORD      lpfnNotify
UNICODE_STRING lpInitFunctionName;
DWORD      offCbk[2];

This structure is a specialization of a more general mechanism that exists in the win32k.sys driver for user-mode callbacks: it is composed of a fixed header (from dwBufferSize to bFixed) and a variable-length data (starting from lpDLLPath).

dwBufferSize contains the length of the whole buffer, including the variable-length data.
dwAdditionalData contains the length of the variable-length data.
pbFree is a pointer to the end of the variable-length data.

We will not go into the implementation details of how this dynamic buffer is allocated and the previous fields are used by the Windows routines. We just have to mimic the way the buffer is filled in in order to call KeUserModeCallback.

Note: you can have a look at the win32k!AllocCallbackMessage and win32k!CaptureCallbackData functions called by win32k!ClientLoadLibrary if you want to understand how this structure is allocated and updated.

Relocatable buffer

The buffer supplied by the caller of KeUserModeCallback resides in kernel memory. The buffer must eventually resides in the user memory of the process (it is copied on the userland stack) in order to be handled by the userland functions.

In order to make the buffer location-independent, the Windows developers implemented a simple mechanism consisting of ‘fix-ups’. If the bFixed is FALSE, every pointer does not contain an address but an offset relative to the beginning of the structure.

Let’s take for example the buffer passed to the user32!__ClientLoadLibrary on a Windows XP 32 bits:

kd> db ef5a78e0 L68
ef5a78e0 68 00 00 00 40 00 00 00-01 00 00 00 48 79 5a ef h...@.......HyZ.
ef5a78f0 24 00 00 00 00 00 00 00-3e 00 40 00 28 00 00 00 $.......>.@.(...
ef5a7900 40 9e 00 00 1c 00 00 00-43 00 3a 00 5c 00 57 00 @.......C.:.\.W.
ef5a7910 49 00 4e 00 44 00 4f 00-57 00 53 00 5c 00 73 00 I.N.D.O.W.S.\.s.
ef5a7920 79 00 73 00 74 00 65 00-6d 00 33 00 32 00 5c 00 y.s.t.e.m.3.2.\.
ef5a7930 75 00 78 00 74 00 68 00-65 00 6d 00 65 00 2e 00 u.x.t.h.e.m.e...
ef5a7940 64 00 6c 00 6c 00 00 00 d.l.l...
dwBufferSize: 0x68
dwAdditionalData: 0x40
dwFixupCounts: 1
pbFree: 0xef5a7948
offCbkPtrs: 0x24 -> 0xef5a7904
bFixed : FALSE
lpDLLPath : (Length: 0x3e, MaximumLength: 0x40, Buffer: 0x28)

The buffer contains 1 fix-up (dwFixupsCount = 1). The array containing this fix-up is at offset 0x24 from the beginning of the structure (thus residing at address 0xef5a7904). The first and only element of this array is the offset of the value to fix: it is the UNICODE_STRING buffer (value 0x28 at offset 0x1c). After being fixed, the buffer points to the real memory address (0xef5a78e0+ 0x28 = 0xef5a7908).

This resolution is performed by the FixupCallbackPointers function of user32, after the buffer has been copied to the user land stack.

The code for this function looks like:

void FixupCallbackPointers(_USERHOOK_s *pData)
LPWORD offsetPointers;
DWORD fixup;
offsetPointers = (LPBYTE)pData + pData->offCbkPtrs;
for(fixup=0;fixup < pData->dwFixupsCount;fixup++)
pData[*offsetPointers] += (LPVOID)pData;

Load library-specific parameters

The first parameter in the dynamic part of the input buffer given to KeUserModeCallback is the name of the module to load; it is specified in the lpDLLPath field of the structure. The module is eventually loaded by the call to the kernel32!LoadLibraryExW function.

The second parameter passed in the buffer describes a function to call once the library is loaded and depends on the operating system version.

On Windows XP, the field (lpfnNotify) is an offset relative to the loaded module of the function to call. Starting from Windows Vista, the field (lpInitFunctionName) is the name of the function to call; this function must be exported because it is retrieved with the help of GetProcAddress.

To skip the initialization function call, simply specify a 0 value for lpfnNotify on Windows XP or specify no relocation for the function name (dwFixupsCount = 1 and offCbk[1] = 0) starting from Windows Vista.

Output buffer

On output, the KeUserModeCallback fills the OutputBuffer and OutputLength parameters with the results of the call if it succeeds.

For the load library case, the contents of the whole output buffer has not been investigated. However, the beginning of the output buffer matches the structure:

typedef struct _LOAD_OUTPUT
LPVOID lpBaseAddress;


The lpBaseAddress field contains the base address of the loaded module.

What about Wow64?

What we described so far is relevant for 32-bit processes on 32-bit operating systems and 64-bit processes on 64-bit systems. But what about 32-bit processes on 64-bit systems?

The good news is that it works equally from the kernel point-of-view, so what we explained is still relevant.

In a Wow64 process, the first change is that the KernelCallbackTable field of the Process Environment Block now points to wow64win module functions:

kd> dps 0x00000000`73e51510 L0n105
00000000`73e51510 00000000`73e82894 wow64win!whcbfnCOPYDATA
00000000`73e51518 00000000`73e82a28 wow64win!whcbfnCOPYGLOBALDATA
00000000`73e516a0 00000000`73e87dc8 wow64win!whcbClientCopyDDEIn1
00000000`73e516a8 00000000`73e87f78 wow64win!whcbClientCopyDDEIn2
00000000`73e516b0 00000000`73e880b8 wow64win!whcbClientCopyDDEOut1
00000000`73e516b8 00000000`73e88280 wow64win!whcbClientCopyDDEOut2
00000000`73e516c0 00000000`73e883c0 wow64win!whcbClientCopyImage
00000000`73e516c8 00000000`73e884e8 wow64win!whcbClientEventCallback
00000000`73e516d0 00000000`73e8862c wow64win!whcbClientFindMnemChar
00000000`73e516d8 00000000`73e8878c wow64win!whcbClientFreeDDEHandle
00000000`73e516e0 00000000`73e888a4 wow64win!whcbClientFreeLibrary
00000000`73e516e8 00000000`73e889b4 wow64win!whcbClientGetCharsetInfo
00000000`73e516f0 00000000`73e88aec wow64win!whcbClientGetDDEFlags
00000000`73e516f8 00000000`73e88c04 wow64win!whcbClientGetDDEHookData
00000000`73e51700 00000000`73e88d6c wow64win!whcbClientGetListboxString
00000000`73e51708 00000000`73e88f14 wow64win!whcbClientGetMessageMPH
00000000`73e51710 00000000`73e89088 wow64win!whcbClientLoadImage
00000000`73e51718 00000000`73e8920c wow64win!whcbClientLoadLibrary
00000000`73e51720 00000000`73e89370 wow64win!whcbClientLoadMenu
00000000`73e51728 00000000`73e894c4 wow64win!whcbClientLoadLocalT1Fonts
00000000`73e51730 00000000`73e895ac wow64win!whcbClientPSMTextOut
00000000`73e51738 00000000`73e89718 wow64win!whcbClientLpkDrawTextEx
00000000`73e51850 00000000`73e8c6a8 wow64win!whcbfnINPGESTURENOTIFYSTRUCT

What these functions do is performing an extra-marshalling between 64 and 32-bit structures.

Regarding the load library functionality, the wow64win!whcbClientLoadLibrary function first calls wow64win!FixupCaptureBuf64 which resolves the relative offsets.

The original buffer contains the raw data as received by the kernel:

0:000> db 00000000006fdde8 L00000000000000c8
00000000`006fdde8 c8 00 00 00 70 00 00 00-02 00 00 00 00 00 00 00 ....p...........
00000000`006fddf8 f0 d0 e1 02 80 f8 ff ff-48 00 00 00 00 00 00 00 ........H.......
00000000`006fde08 00 00 00 00 00 00 00 00-3e 00 40 00 00 00 00 00 ........>.@.....
00000000`006fde18 58 00 00 00 00 00 00 00-20 00 22 00 00 00 00 00 X....... .".....
00000000`006fde28 98 00 00 00 00 00 00 00-30 00 00 00 40 00 00 00 ........0...@...
00000000`006fde38 00 00 00 00 00 00 00 00-43 00 3a 00 5c 00 57 00 ........C.:.\.W.
00000000`006fde48 69 00 6e 00 64 00 6f 00-77 00 73 00 5c 00 73 00 i.n.d.o.w.s.\.s.
00000000`006fde58 79 00 73 00 74 00 65 00-6d 00 33 00 32 00 5c 00 y.s.t.e.m.3.2.\.
00000000`006fde68 75 00 78 00 74 00 68 00-65 00 6d 00 65 00 2e 00 u.x.t.h.e.m.e...
00000000`006fde78 64 00 6c 00 6c 00 00 00-54 00 68 00 65 00 6d 00 d.l.l...T.h.e.m.
00000000`006fde88 65 00 49 00 6e 00 69 00-74 00 41 00 70 00 69 00 e.I.n.i.t.A.p.i.
00000000`006fde98 48 00 6f 00 6f 00 6b 00-00 00 00 00 00 00 00 00 H.o.o.k.........
00000000`006fdea8 00 00 00 00 00 00 00 00 ........

In the original buffer, 2 fix-ups are declared. The pseudo-pointers are 64-bit long (in yellow and green in the previous image).

wow64win!FixupCaptureBuf64 replaces the relative offsets with absolute addresses. Since all relocations are performed, it sets the number of fix-ups to ‘0’.

0:000> db 00000000006fdde8 Lc8
00000000`006fdde8 c8 00 00 00 70 00 00 00-00 00 00 00 00 00 00 00 ....p...........
00000000`006fddf8 f0 d0 e1 02 80 f8 ff ff-48 00 00 00 00 00 00 00 ........H.......
00000000`006fde08 00 00 00 00 00 00 00 00-3e 00 40 00 00 00 00 00 ........>.@.....
00000000`006fde18 40 de 6f 00 00 00 00 00-20 00 22 00 00 00 00 00 @.o..... .".....
00000000`006fde28 80 de 6f 00 00 00 00 00-30 00 00 00 40 00 00 00 ..o.....0...@...
00000000`006fde38 00 00 00 00 00 00 00 00-43 00 3a 00 5c 00 57 00 ........C.:.\.W.
00000000`006fde48 69 00 6e 00 64 00 6f 00-77 00 73 00 5c 00 73 00 i.n.d.o.w.s.\.s.
00000000`006fde58 79 00 73 00 74 00 65 00-6d 00 33 00 32 00 5c 00 y.s.t.e.m.3.2.\.
00000000`006fde68 75 00 78 00 74 00 68 00-65 00 6d 00 65 00 2e 00 u.x.t.h.e.m.e...
00000000`006fde78 64 00 6c 00 6c 00 00 00-54 00 68 00 65 00 6d 00 d.l.l...T.h.e.m.
00000000`006fde88 65 00 49 00 6e 00 69 00-74 00 41 00 70 00 69 00 e.I.n.i.t.A.p.i.
00000000`006fde98 48 00 6f 00 6f 00 6b 00-00 00 00 00 00 00 00 00 H.o.o.k.........
00000000`006fdea8 00 00 00 00 00 00 00 00 ........

The second step builds another buffer containing only the static part of the structure with a layout matching the 32-bit code expectations.

0:000> db 6fdd20 L28
00000000`006fdd20 c8 00 00 00 70 00 00 00-00 00 00 00 f0 d0 e1 02 ....p...........
00000000`006fdd30 48 00 00 00 00 00 00 00-3e 00 40 00 40 de 6f 00 H.......>.@.@.o.
00000000`006fdd40 20 00 22 00 80 de 6f 00 ."...o.

The control is then passed to the user32!__ClientLoadLibrary function that performs the operations as if it were running on a 32-bit operating system.

Since the loading of the specified module is performed as if it were called by the userland process, the standard restrictions and behaviors are applicable. In particular, DLL redirection is in effect and loading of a DLL in c:\Windows\System32 will be automatically redirected to c:\Windows\SysWOW64.


It is possible to use the KeUserModeCallback function to load a custom library in processes, provided they use the user32 module. In practice, nearly all end-user applications use this module so it should not be a strong constraint. Since this function and the associated parameters are not documented, this functionality is subject to change in the future versions (even if it did not change so much during the 15 past years).

If you are interested in investigating other methods of executing user-mode code from the kernel, you can also have a look at the 6-part articles published by Nynaeve.