Finding kernel32.dll

In order to locate and access essential functions within Windows, we must first locate the kernel32.dll module, as it is an entry point for resolving function addresses and loading other modules.

When dealing with custom shellcode, it is essential to resolve the addresses of Win32 functions provided by kernel32.dll and others. By obtaining the base address of kernel32.dll and resolving the addresses of specific functions, the shellcode gains the ability to directly invoke these functions.

The LoadLibraryA function is part of the kernel32.dll module. This function is used to load in other modules as we require them.

This section describes a method for finding the base address of kernel32.dll.

When a process is loaded into memory in the Windows operating system, the system ensures that essential system libraries, such as ntdll.dll and kernel32.dll, are also loaded into the virtual memory space of the process. These libraries contain a variety of important functions and services that the process may rely on for its execution and interaction with the operating system. By loading these libraries into the process's memory, the process gains access to their functionality, allowing it to make use of Win32 APIs, system calls, and other system services.

Leveraging the consistent loading order of these modules, we can exploit this behavior to determine the base address of kernel32.dll. This module is always loaded third following the application PE and ntdll.dll.

The gs register holds a reference to the current thread's Thread Environment Block (TEB). We can follow the offset in the TEB to get a reference to the Process Environment Block (PEB) and the offset in the PEB to get a reference to the Loader (LDR).

The TEB is a data structure in Windows that is associated with each thread of execution in a process. Its purpose is to store thread-specific information and provide a mechanism for accessing that information. The TEB contains various fields and structures that hold data related to thread context, thread-local storage (TLS), exception handling, and other thread-specific information. It serves as a communication channel between the operating system and the thread, allowing the system to manage and track the state of individual threads within a process.

The TEB structure can be examined in Windbg using dt nt!_TEB. Use this to find offset 0x60:

0:000> dt nt!_TEB
ntdll!_TEB
   +0x000 NtTib            : _NT_TIB
   +0x038 EnvironmentPointer : Ptr64 Void
   +0x040 ClientId         : _CLIENT_ID
   +0x050 ActiveRpcHandle  : Ptr64 Void
   +0x058 ThreadLocalStoragePointer : Ptr64 Void
   +0x060 ProcessEnvironmentBlock : Ptr64 _PEB
   ...

The PEB is primarily used by the operating system for managing and controlling the execution of the process, such as loading and unloading modules, handling exceptions, managing process environment settings, and providing access to system resources. The process can also access certain fields in the PEB to retrieve information about itself or perform specific operations.

The PEB structure can be examined in Windbg using dt nt!_PEB. Use this to find offset 0x18:

0:000> dt nt!_PEB
ntdll!_PEB
   +0x000 InheritedAddressSpace : UChar
   +0x001 ReadImageFileExecOptions : UChar
   +0x002 BeingDebugged    : UChar
   +0x003 BitField         : UChar
   +0x003 ImageUsesLargePages : Pos 0, 1 Bit
   +0x003 IsProtectedProcess : Pos 1, 1 Bit
   +0x003 IsImageDynamicallyRelocated : Pos 2, 1 Bit
   +0x003 SkipPatchingUser32Forwarders : Pos 3, 1 Bit
   +0x003 IsPackagedProcess : Pos 4, 1 Bit
   +0x003 IsAppContainer   : Pos 5, 1 Bit
   +0x003 IsProtectedProcessLight : Pos 6, 1 Bit
   +0x003 IsLongPathAwareProcess : Pos 7, 1 Bit
   +0x004 Padding0         : [4] UChar
   +0x008 Mutant           : Ptr64 Void
   +0x010 ImageBaseAddress : Ptr64 Void
   +0x018 Ldr              : Ptr64 _PEB_LDR_DATA
   ...

The LDR is a component of the PEB in Windows. It is a data structure that maintains information about the loaded modules in a process.

The LDR keeps track of the loaded modules and their associated information, such as the module's base address, entry point, module name, dependencies, and other attributes. It forms a linked list of entries, where each entry represents a loaded module in the process.

By observing the consistent order of module loading, with the third loaded module always being kernel32.dll, we can enumerate the InMemoryOrderModuleList to locate the base address of kernel32.dll.

The commented assembly is shown below:

Locate kernel32.dll
find_kernel32:
    xor  rcx, rcx                ; RCX = 0
    mov  rax, [gs:rcx + 0x60]    ; RAX = _PEB
    mov  rax, [rax + 0x18]       ; RAX = _PEB->_PEB_LDR_DATA
    mov  rsi, [rax + 0x20].      ; RSI = 
                                 ; _PEB->_PEB_LDR_DATA->InMemoryOrderModuleList
    lodsq                        ; RAX = Second Module (NTDLL)
    xchg rax, rsi                ; RAX = RSI, RSI = RAX
    lodsq                        ; RAX = Third Module (KERNEL32)
    mov rbx, [rax + 0x20]        ; RBX = Base address of KERNEL32

Now that we have examined the process of enumerating the relevant structures starting from the PEB, the shellcode should become self-explanatory. One instruction that you may encounter for the first time is lodsq, which loads the QWORD at the address pointed by rsi into rax and increments rsi by 8 bytes (QWORD).

We have a reference to the base address of kernel32.dll from which we can now resolve function addresses.

Exercises

  1. Familiarise yourself with the PEB, TEB, and LDR structures in Windbg Preview.

  2. Ensure you understand what is required to find kernel32.dll using these structures.

Last updated