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 fromkernel32.dll
andadvapi32.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.
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
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
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
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 !