This article focuses on how to locate the base address of the kernelbase.dll module. This is the first part of a blog series, focusing on how to create a custom Win32 reverse shellcode. The following blog post inspired me to write this article, which i strongly suggest for further reading

Kudos goes to Ionut Popescu for the very informative walkthroughs

In order to create a reverse tcp shellcode we need to know the addresses of the functions used in a windows tcp socket connection. For this reason, we will get the address of these functions using the GetProcAddress function. Additionally, in order to be able to search for such functions, we need to load the appropriate libraries. Moreover, a function that is crucial to use in order to load the wanted modules, is the LoadLibraryA, which is located in kernelbase.dll module. Traditionally the aforementioned functionality has been located in kernel32.dll library, but according to microsoft [ref .1], since 05/31/2018,

in order to improve internal engineering efficiencies and improve foundations for future work, we have relocated some functionality to new low-level binaries. This refactoring will make it possible for future installs of Windows to provide subsets of functionality to reduce surface area (disk and memory requirements, servicing, and attack surface). As an example of functionality that have been moved to low-level binaries, kernelbase.dll gets functionality from kernel32.dll and advapi32.dll.

For the development process the following dummy program used

#include <windows.h>

int main(int argc, char* argv[])
{
    LoadLibrary("user32.dll");
    _asm
    {
        // here we will write the instructions for the Win32 reverse tcp connection and shell interaction 
    
    }
    return 0;
}

Before we move further with the analysis, we will load the compiled program into WinDbg and then, we will check to see that the GetProcAddress function has been moved to kernelbase.dll module.

0:000> x kernel32!GetProcAddress
0:000> 
0:000> 
0:000> x kernelbase!GetProcAddress
762563a0 KERNELBASE!GetProcAddress (void)

Another way to justify that the GetProcAddress function has been moved to kernelbase.dll module, is to use dumpbin.exe tool as follows

C:\Users\Xenofon>dumpbin /exports c:\windows\system32\kernelbase.dll | findstr "GetProcAddress$"
        682  2A8 00040C60 GetProcAddress

At this point we are ready to start our analysis. First, we will exemine the Thread Environment Block (TEB) structure in order to find the exact location of the Process Environment Block (PEB) structure. Then we will navigate through PEB to search for the pointer to the PEB_LDR_DATA structure that will provide information about the loaded modules. Moreover, this Windows internal information will also help us to locate the kernelbase.dll base address. In WinDbg we can see the TEB structure using the command dt _teb as shown below

0:000> dt _teb
ntdll!_TEB
   +0x000 NtTib            : _NT_TIB
   +0x01c EnvironmentPointer : Ptr32 Void
   +0x020 ClientId         : _CLIENT_ID
   +0x028 ActiveRpcHandle  : Ptr32 Void
   +0x02c ThreadLocalStoragePointer : Ptr32 Void
   +0x030 ProcessEnvironmentBlock : Ptr32 _PEB
   +0x034 LastErrorValue   : Uint4B
   +0x038 CountOfOwnedCriticalSections : Uint4B
   +0x03c CsrClientThread  : Ptr32 Void
[...SNIP...]

As we see from the ouput above, we have the offset of the PEB structure ( offset 0x30 ). Windows uses the FS register to store the address of the TEB structure

0:000> dg fs 
                                  P Si Gr Pr Lo
Sel    Base     Limit     Type    l ze an es ng Flags
---- -------- -------- ---------- - -- -- -- -- --------
0053 00a15000 00000fff Data RW Ac 3 Bg By P  Nl 000004f3

So, as we see from the output above, the TEB structure is located at the address 0x00a15000. In WinDbg we can see the PEB structure using the command !peb as shown below

0:000> !peb
PEB at 00a12000
    InheritedAddressSpace:    No
    ReadImageFileExecOptions: No
    BeingDebugged:            Yes
    ImageBaseAddress:         00cd0000
    NtGlobalFlag:             70
    NtGlobalFlag2:            0
    Ldr                       77464d80
    Ldr.Initialized:          Yes
    Ldr.InInitializationOrderModuleList: 010a3968 . 010ae480
    Ldr.InLoadOrderModuleList:           010a3a60 . 010ae470
    Ldr.InMemoryOrderModuleList:         010a3a68 . 010ae478
            Base TimeStamp                     Module
          cd0000 6122500c Aug 22 15:24:28 2021 C:\Users\Xenofon\source\repos\testasm\Debug\testasm.exe
        77340000 1bdbc4b8 Oct 23 15:52:56 1984 C:\Windows\SYSTEM32\ntdll.dll
        77000000 7e8f02e1 Apr 14 08:00:01 2037 C:\Windows\System32\KERNEL32.DLL
        76140000 C:\Windows\System32\KERNELBASE.dll
        6e530000 5d30ea7d Jul 18 23:54:05 2019 C:\Windows\SYSTEM32\VCRUNTIME140D.dll
        6e3b0000 731fe41b Mar 17 01:24:11 2031 C:\Windows\SYSTEM32\ucrtbased.dll
        76df0000 3f7ceca3 Oct 03 05:27:31 2003 C:\Windows\System32\user32.dll
        75560000 55cf9768 Aug 15 21:47:52 2015 C:\Windows\System32\win32u.dll
        77150000 C:\Windows\System32\GDI32.dll
   [...SNIP...]

After initiating the command above, we see that there is some valuable information available regarding the PEB stucture, which can help us significantly, giving us foothold on how to move further. According to this information we can now check the ldr pointer at address 0x77464d80 ,to clarify that indeed points to the _PEB_LDR_DATA structure. Furthermore, we can check this by using the command dt _peb @$peb in WinDbg as seen below

0:000> dt _peb @$peb
ntdll!_PEB
   +0x000 InheritedAddressSpace : 0 ''
   +0x001 ReadImageFileExecOptions : 0 ''
   +0x002 BeingDebugged    : 0x1 ''
   +0x003 BitField         : 0x4 ''
   +0x003 ImageUsesLargePages : 0y0
   +0x003 IsProtectedProcess : 0y0
   +0x003 IsImageDynamicallyRelocated : 0y1
   +0x003 SkipPatchingUser32Forwarders : 0y0
   +0x003 IsPackagedProcess : 0y0
   +0x003 IsAppContainer   : 0y0
   +0x003 IsProtectedProcessLight : 0y0
   +0x003 IsLongPathAwareProcess : 0y0
   +0x004 Mutant           : 0xffffffff Void
   +0x008 ImageBaseAddress : 0x00cd0000 Void
   +0x00c Ldr              : 0x77464d80 _PEB_LDR_DATA
   +0x010 ProcessParameters : 0x010a1ec0 _RTL_USER_PROCESS_PARAMETERS
   +0x014 SubSystemData    : (null) 
   +0x018 ProcessHeap      : 0x010a0000 Void
   +0x01c FastPebLock      : 0x77464b40 _RTL_CRITICAL_SECTION
 [...SNIP...]

There is indeed the _PEB_LDR_DATA structure located at address 0x77464d80, and the pointer of that structure is located at offset 0xc.

0:000> ? poi(@$peb+0xc)
Evaluate expression: 2001096064 = 77464d80

Moreover, we will use this address to find the exact offset of InMemoryOrderModuleList inside the _PEB_LDR_DATA structure as seen below

0:000> dt _PEB_LDR_DATA 0x77464d80
ucrtbased!_PEB_LDR_DATA
   +0x000 Length           : 0x30
   +0x004 Initialized      : 0x1 ''
   +0x008 SsHandle         : (null) 
   +0x00c InLoadOrderModuleList : _LIST_ENTRY [ 0x963b50 - 0x974bf8 ]
   +0x014 InMemoryOrderModuleList : _LIST_ENTRY [ 0x963b58 - 0x974c00 ]
   +0x01c InInitializationOrderModuleList : _LIST_ENTRY [ 0x964310 - 0x974e38 ]
   +0x024 EntryInProgress  : (null) 
   +0x028 ShutdownInProgress : 0x1 ''
   +0x02c ShutdownThreadId : 0x000007fc Void

Now lets get the InMemoryOrderModuleList adddress since we know the offset ( 0x14 )

0:000> ? poi(poi(@$peb+0xc)+0x14)
Evaluate expression: 9845592 = 00963b58

Following is the _PEB_LDR_DATA structure prototype

typedef struct _PEB_LDR_DATA {
  BYTE       Reserved1[8];
  PVOID      Reserved2[3];
  LIST_ENTRY InMemoryOrderModuleList;
} PEB_LDR_DATA, *PPEB_LDR_DATA;

At this point we are interested mainly in the InMemoryOrderModuleList, which according to Microsoft Docs, is the head of a doubly-linked list that contains the loaded modules for the process. Each item in the list is a pointer to an LDR_DATA_TABLE_ENTRY structure. Lets check this out

0:000> dt _PEB_LDR_DATA
ucrtbased!_PEB_LDR_DATA
   +0x000 Length           : Uint4B
   +0x004 Initialized      : UChar
   +0x008 SsHandle         : Ptr32 Void
   +0x00c InLoadOrderModuleList : _LIST_ENTRY
   +0x014 InMemoryOrderModuleList : _LIST_ENTRY
   +0x01c InInitializationOrderModuleList : _LIST_ENTRY
   +0x024 EntryInProgress  : Ptr32 Void
   +0x028 ShutdownInProgress : UChar
   +0x02c ShutdownThreadId : Ptr32 Void

As seen above, we realize that the InMemoryOrderModuleList is located at offset 0x14.

At this point lets start constructing our instructions

XOR ECX, ECX                  ; zero out ECX
MOV EAX, FS:[ecx + 0x30]      ; EAX = PEB

At the first two lines above, the first instruction sets the ecx register to zero and the second instruction uses ecx to avoid null bytes. Lets explain this a bit.. If we use the mov eax,fs:[30] instruction, it will be assembled to the following opcode sequence, 64 A1 30 00 00 00, which apparently produces null bytes. In the contrary, if we use the instruction mov eax, fs:[ecx+0x30], it will be assembled to 64 8B 41 30, which does not contain null bytes.

Below we see this in practice using the msf-nasm tool from metasploit framework.

nasm > MOV EAX, FS:[ecx + 0x30]
00000000  648B4130          mov eax,[fs:ecx+0x30]
nasm > MOV EAX, FS:[0x30]
00000000  64A130000000      mov eax,[fs:0x30]

At this point we need to move further and find the address of the InMemoryOrderModuleList, and then the pointer to LDR_DATA_TABLE_ENTRY structure, which as said before will help us to find the exact offset of kernelbase.dll module and finaly load it into ebx register as we'll see later

MOV EAX, [EAX + 0xc]          ; EAX = PEB->Ldr
MOV ESI, [EAX + 0x14]         ; ESI = PEB->Ldr.InMemoryOrderModuleList

At the first line above, we have the ldr pointer loaded in the eax register. The mov instruction saves the address of the PEB_LDR_DATA structure in eax register. The PEB_LDR_DATA structure is located at the offset 0x0C at the PEB structure. Moreover, in case we follow that pointer in the PEB_LDR_DATA, then, at offset 0x14 we have the InMemoryOrderModuleList. Here, the first element is a forward link or Flink, which is a pointer to the next module in the doubled linked list. In addition to this, as we see above, the pointer placed inside the esi register.

0:000> dt _PEB_LDR_DATA poi(@$peb+0xc)
ucrtbased!_PEB_LDR_DATA
   +0x000 Length           : 0x30
   +0x004 Initialized      : 0x1 ''
   +0x008 SsHandle         : (null) 
   +0x00c InLoadOrderModuleList : _LIST_ENTRY [ 0x963b50 - 0x974bf8 ]
   +0x014 InMemoryOrderModuleList : _LIST_ENTRY [ 0x963b58 - 0x974c00 ]
   +0x01c InInitializationOrderModuleList : _LIST_ENTRY [ 0x964310 - 0x974e38 ]
   +0x024 EntryInProgress  : (null) 
   +0x028 ShutdownInProgress : 0x1 ''
   +0x02c ShutdownThreadId : 0x000007fc Void
0:000> dx -r1 (*((ucrtbased!_LIST_ENTRY *)0x77464d94))
(*((ucrtbased!_LIST_ENTRY *)0x77464d94))                 [Type: _LIST_ENTRY]
    [+0x000] Flink            : 0x963b58 [Type: _LIST_ENTRY *]
    [+0x004] Blink            : 0x974c00 [Type: _LIST_ENTRY *]
0:000> dx -r1 ((ucrtbased!_LIST_ENTRY *)0x963b58)
((ucrtbased!_LIST_ENTRY *)0x963b58)                 : 0x963b58 [Type: _LIST_ENTRY *]
    [+0x000] Flink            : 0x963a50 [Type: _LIST_ENTRY *]
    [+0x004] Blink            : 0x77464d94 [Type: _LIST_ENTRY *]

Furthermore, we can also move streight to the address that we are interested in using the WinDbg command dt _PEB_LDR_DATA poi(poi(@$peb+0xc)+0x14)

0:000> dt _PEB_LDR_DATA poi(poi(@$peb+0xc)+0x14)
ucrtbased!_PEB_LDR_DATA
   +0x000 Length           : 0x963a50
   +0x004 Initialized      : 0x94 ''
   +0x008 SsHandle         : (null) 
   +0x00c InLoadOrderModuleList : _LIST_ENTRY [ 0x0 - 0x5c0000 ]
   +0x014 InMemoryOrderModuleList : _LIST_ENTRY [ 0x5d1320 - 0x1f000 ]
   +0x01c InInitializationOrderModuleList : _LIST_ENTRY [ 0x4a0048 - 0x962438 ]
   +0x024 EntryInProgress  : 0x00180016 Void
   +0x028 ShutdownInProgress : 0x6a 'j'
   +0x02c ShutdownThreadId : 0x00002acc Void

Now lets verify that the LDR Data Table is indeed holding the offset and the base address of the ntdll.dll module.

0:000>  dt _LDR_DATA_TABLE_ENTRY 0x963a50
ntdll!_LDR_DATA_TABLE_ENTRY
   +0x000 InLoadOrderLinks : _LIST_ENTRY [ 0x963f38 - 0x963b58 ]
   +0x008 InMemoryOrderLinks : _LIST_ENTRY [ 0x96db88 - 0x964170 ]
   +0x010 InInitializationOrderLinks : _LIST_ENTRY [ 0x77340000 - 0x0 ]
   +0x018 DllBase          : 0x001a2000 Void
   +0x01c EntryPoint       : 0x003c003a Void
   +0x020 SizeOfImage      : 0x963928
   +0x024 FullDllName      : _UNICODE_STRING "ntdll.dll"
   +0x02c BaseDllName      : _UNICODE_STRING "--- memory read error at address 0x0000ffff ---"
   [...SNIP...]

Well, there is an error above as you can see "--- memory read error at address 0x0000ffff ---". Thats because the InMemoryOrderLinks list entry is located at offset 0x8 ( highlighted in red above ). In order to fix this issue we subtract 0x8 from 0x963a50

0:000> dt _LDR_DATA_TABLE_ENTRY 0x963a50-8
ntdll!_LDR_DATA_TABLE_ENTRY
   +0x000 InLoadOrderLinks : _LIST_ENTRY [ 0x963f30 - 0x963b50 ]
   +0x008 InMemoryOrderLinks : _LIST_ENTRY [ 0x963f38 - 0x963b58 ]
   +0x010 InInitializationOrderLinks : _LIST_ENTRY [ 0x96db88 - 0x964170 ]
   +0x018 DllBase          : 0x77340000 Void
   +0x01c EntryPoint       : (null) 
   +0x020 SizeOfImage      : 0x1a2000
   +0x024 FullDllName      : _UNICODE_STRING "C:\Windows\SYSTEM32\ntdll.dll"
   +0x02c BaseDllName      : _UNICODE_STRING "ntdll.dll"
[...SNIP...]

We can verify that the address of the ntdll.dll is 0x77340000 as seen at offset 0x18 at DllBase above. The lodsd instruction below will follow the pointer specified by the esi register and the results will be placed inside the eax register. In such case the memory address of the second list entry structure will be loaded in eax register.

LODSD  ; memory address of the second list entry structure

Now that we know the address of the ntdll.dll module, we can proceed further in the linked list to find the third list entry structure which will give us the offset of the kernel32.dll module. In order to do this we will follow the linked list and see where it points next.

At this point, we will search for the kernel32.dll base address using WinDbg

0:000> dx -r1 (*((ntdll!_LIST_ENTRY *)0x963a50))
(*((ntdll!_LIST_ENTRY *)0x963a50))                 [Type: _LIST_ENTRY]
    [+0x000] Flink            : 0x963f38 [Type: _LIST_ENTRY *]
    [+0x004] Blink            : 0x963b58 [Type: _LIST_ENTRY *]
0:000> dt _LDR_DATA_TABLE_ENTRY 0x963f38-8
ntdll!_LDR_DATA_TABLE_ENTRY
   +0x000 InLoadOrderLinks : _LIST_ENTRY [ 0x964300 - 0x963a48 ]
   +0x008 InMemoryOrderLinks : _LIST_ENTRY [ 0x964308 - 0x963a50 ]
   +0x010 InInitializationOrderLinks : _LIST_ENTRY [ 0x964170 - 0x964310 ]
   +0x018 DllBase          : 0x77000000 Void
   +0x01c EntryPoint       : 0x7701f5a0 Void
   +0x020 SizeOfImage      : 0xf0000
   +0x024 FullDllName      : _UNICODE_STRING "C:\Windows\System32\KERNEL32.DLL"
   +0x02c BaseDllName      : _UNICODE_STRING "KERNEL32.DLL"

In terms of x86 assembly language, the lodsd instruction will follow the pointer specified by the esi register and the results will be placed inside the eax register. As we know, after the lodsd instruction assigns the value pointed to the address loaded in esi register into the eax register, it increments esi by 4 (pointing to the next dword) pointing to the next list entry structure. For that reason, before using the lodsd instruction for the second time, we should first use the xchg instruction in order to assign the next pointer to esi register. This means that after executing the lodsd instruction, the address of the third list entry structure, will be placed inside the eax register. Furthermore, inside this list entry structure we can find the offset ( 0x18 ) which holds the base address of the kernel32.dll module.

XCHG EAX, ESI ; EAX = ESI , ESI = EAX 
LODSD  ; memory address of the third list entry structure

The two instructions above will be repeated once again in order to follow the Flink from the previous list to move further to the fourth linked list to find the kernelbase.dll address

XCHG EAX, ESI ; EAX = ESI , ESI = EAX 
LODSD  ; memory address of the fourth list entry structure

At this point, we will search for the kernelbase.dll base address using WinDbg

0:000> dx -r1 (*((ntdll!_LIST_ENTRY *)0x963f38))
(*((ntdll!_LIST_ENTRY *)0x963f38))                 [Type: _LIST_ENTRY]
    [+0x000] Flink            : 0x964308 [Type: _LIST_ENTRY *]
    [+0x004] Blink            : 0x963a50 [Type: _LIST_ENTRY *]
0:000> dt _LDR_DATA_TABLE_ENTRY 0x964308-8
ntdll!_LDR_DATA_TABLE_ENTRY
   +0x000 InLoadOrderLinks : _LIST_ENTRY [ 0x964160 - 0x963f30 ]
   +0x008 InMemoryOrderLinks : _LIST_ENTRY [ 0x964168 - 0x963f38 ]
   +0x010 InInitializationOrderLinks : _LIST_ENTRY [ 0x963f40 - 0x77464d9c ]
   +0x018 DllBase          : 0x76140000 Void
   +0x01c EntryPoint       : 0x76254030 Void
   +0x020 SizeOfImage      : 0x213000
   +0x024 FullDllName      : _UNICODE_STRING "C:\Windows\System32\KERNELBASE.dll"
   +0x02c BaseDllName      : _UNICODE_STRING "KERNELBASE.dll"
   +0x034 FlagGroup        : [4]  "???"
[...SNIP...]

As we see above, the BaseDllName holds the kernelbase.dll name at offset 0x02c and the DllBase holds its address 0x76140000 at offset 0x18. So in order to gain the kernelbase.dll base address, we need to use the following command in WinDbg, as well as to do the appropriate calculations.

0:000> ? 0x964308-8+18
Evaluate expression: 9847576 = 00964318
0:000> db 00964318
00964318  00 00 14 76 30 40 25 76-00 30 21 00 44 00 46 00  ...v0@%v.0!.D.F.
00964328  08 44 96 00 1c 00 1e 00-30 44 96 00 cc aa 08 00  .D......0D......
00964338  ff ff 00 00 a0 4b 46 77-a0 4b 46 77 72 8b 90 ae  .....KFw.KFwr...
00964348  00 00 00 00 00 00 00 00-c0 43 96 00 c0 43 96 00  .........C...C..
00964358  c0 43 96 00 00 00 00 00-00 00 00 77 a4 11 34 77  .C.........w..4w
00964368  c8 41 96 00 98 3f 96 00-00 00 00 00 6c 4c 97 00  .A...?......lL..
00964378  c4 9c 97 00 2c 48 97 00-00 00 14 76 00 00 00 00  ....,H.....v....
00964388  26 84 8c c5 61 97 d7 01-c4 be 35 02 00 00 00 00  &...a.....5

As we see from the output above, the kernelbase.dll base address is located at offset 0x18 - 0x8 = 0x10 . At this point the following instruction will take place

MOV EBX, [EAX + 0x10]   ; EBX = Base address

As we saw earlier, using the lodsd instruction, the eax register holds a pointer to the second list entry of the InMemoryOrderLinks stucture. Furthermore, if we add 0x10 bytes to eax register, we will have the DllBase pointer, which points to the memory address of the kernelbase.dll module.

Finally, we will use the following C program in order to test our assembly instructions and check the addresses in the specified registers. In order to test our assembly instructions we will use Visual Studio

#include <windows.h>
int main(int argc, char* argv[])
{
   LoadLibrary("user32.dll");
   _asm
   {
      XOR ECX, ECX              // zero out ECX
      MOV EAX, FS:[ecx + 0x30]  // EAX = PEB
      MOV EAX, [EAX + 0x0c]     // EAX = PEB->Ldr
      MOV ESI, [EAX + 0x14]     // ESI = PEB->Ldr.InMemoryOrderModuleList
      LODSD                     // memory address of the second list entry structure
      XCHG EAX, ESI             // EAX = ESI , ESI = EAX 
      LODSD                     // memory address of the third list entry structure
      XCHG EAX, ESI             // EAX = ESI , ESI = EAX 
      LODSD                     // memory address of the fourth list entry structure
      MOV EBX, [EAX + 0x10]     // EBX = Base address
   }
   return 0;
}

Furthermore, as we see at the screenshot below, the ebx register holds the kernelbase.dll base address, so our instructions are working as expected.

Visual Studio Debugging

Afterwards, we open WinDbg and then by pressing Ctrl+E, we should be able to load the executable. At the current Windows machine, the executable file is located at the following path C:\Users\Xenofon\source\repos\testasm\Debug

Now, its time to load the debug symbols, so we first run the .symfix command and then the .sympath+ C:\Users\Xenofon\source\repos\testasm\testasm\Debug in order to load the symbols from the corresponding .pdb file. Then we run the command .reload in order to reload the symbols in WinDbg. In order to see the symbols we run x testasm!*

Afterwards, we run bu testasm!main in order to put a breakpoint in the main function of the executable.

0:000> bu testasm!main
0:000> bl
     0 e Disable Clear  00f916f0     0001 (0001)  0:**** testasm!main

Then, by running g command, the execution will stop at the first breakpoint ( which is at the main function ). Also, we should be able to see the source code as seen at the screenshot below

WinDbg debugging and Source Code window

Furthermore, we are in position to put a breakpoint at the last instruction MOV EBX, [EAX + 0x10]. Moreover, we can proceed further and press Alt+7 or press view->disassembly in order to open a new dissasembly window that shows the instructions and the corresponding addresses of the provided source code. At this point we can examine the memory addresses in order to find the specific address to put our breakpoint. As we see in the disassembly window at the image below, we can put the breakpoint at the address 0x00c21733 using the WinDbg command bp 0x00c21733

WinDbg debugging and Source Code window

The following screenshot also shows the debugging process and provides results with the base address of the kernelbase.dll module to be assigned to EBX register

WinDbg debugging

Thats it for now. The second part of the custom win32 reverse tcp shellcode development series will be focusing on how to find the export table of kernelbase.dll.

I hope you enjoyed this first part !