I'm trying to find the kernel32 base address by directly assuming the kernel32.dll is always the 3rd module loaded.
ModLoad: 00400000 024077eb image00400000
ModLoad: 77ad0000 77c7f000 ntdll.dll
ModLoad: 75c80000 75d70000 C:\WINDOWS\SysWOW64\KERNEL32.DLL
ModLoad: 77160000 773d3000 C:\WINDOWS\SysWOW64\KERNELBASE.dll
...
I did have a for loop to run thru the modules but noticed kernel32.dll was always the 3rd entry.
Can I assume that's reliable?
The C program outputs
Kernel32.dll Base Address: 0x75C80000
#include <stdio.h>
#include <stdint.h>
#include <windows.h>
#include <winternl.h>
int main()
{
PPEB peb = (PPEB)__readfsdword(0x30);
uintptr_t kernel32Base = 0;
/* Skip first two entries as kernel32.dll is always the third entry */
PLIST_ENTRY ptr = peb->Ldr->InMemoryOrderModuleList.Flink->Flink->Flink;
PLDR_DATA_TABLE_ENTRY e = CONTAINING_RECORD(ptr, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks);
kernel32Base = (uintptr_t)e->DllBase;
printf("Kernel32.dll Base Address: 0x%p\n", (void*)kernel32Base);
return 0;
}
For example, windbg is loading DbgView.exe
Update
Based on Josh's answer, the suggestion was to use InLoadOrderModuleList
After hacking it for an hour, finally got it working
Kernel32.dll Base Address: 0x75C80000
The solution is to declare my own custom typedef structures for LDR_DATA_TABLE_ENTRY, PEB_LDR_DATA and PEB.
#include <stdio.h>
#include <stdint.h>
#include <windows.h>
typedef struct MY_PEB_LDR_DATA {
ULONG Length;
UCHAR Initialized;
VOID* SsHandle;
LIST_ENTRY InLoadOrderModuleList;
// ... other fields
} MY_PEB_LDR_DATA, *MY_PPEB_LDR_DATA;
typedef struct MY_LDR_DATA_TABLE_ENTRY {
LIST_ENTRY InLoadOrderLinks;
LIST_ENTRY InMemoryOrderLinks;
LIST_ENTRY InInitializationOrderLinks;
PVOID DllBase;
// ... other fields
} MY_LDR_DATA_TABLE_ENTRY, *MY_PLDR_DATA_TABLE_ENTRY;
typedef struct MY_PEB {
BYTE Reserved1[2];
BYTE BeingDebugged;
BYTE Reserved2[1];
PVOID Reserved3[2];
MY_PPEB_LDR_DATA Ldr;
// ... other fields
} MY_PEB, *MY_PPEB;
int main()
{
MY_PPEB peb = (MY_PPEB)__readfsdword(0x30);
uintptr_t kernel32Base = 0;
// Order of modules loaded into the process: [image][ntdll][kernel32]
// Skip first two entries as kernel32.dll is always the third entry.
PLIST_ENTRY ptr = peb->Ldr->InLoadOrderModuleList.Flink->Flink->Flink;
MY_PLDR_DATA_TABLE_ENTRY e = CONTAINING_RECORD(ptr, MY_LDR_DATA_TABLE_ENTRY, InLoadOrderLinks);
kernel32Base = (uintptr_t)e->DllBase;
printf("Kernel32.dll Base Address: 0x%p\n", (void*)kernel32Base);
return 0;
}
Here's a more compact version:
#include <intrin.h>
#include <stdint.h>
#include <stdio.h>
#ifdef _WIN64
#define PEB __readgsqword(0x60)
#define LDR_DATA_IN_PEB_OFFSET 24
#define IN_LOAD_ORDER_MODULE_LIST_OFFSET 16
#define DLL_BASE_OFFSET 48
#else
#define PEB __readfsdword(0x30)
#define LDR_DATA_IN_PEB_OFFSET 12
#define IN_LOAD_ORDER_MODULE_LIST_OFFSET 12
#define DLL_BASE_OFFSET 24
#endif
// The sequence of modules loaded into the process typically follows the order:
// [Executable Image], [ntdll.dll], [kernel32.dll].
#define KERNEL32_BASE_ADDRESS *(uintptr_t*)(*(uintptr_t*)(*(uintptr_t*)(*(uintptr_t*)(*(uintptr_t*)(PEB + LDR_DATA_IN_PEB_OFFSET) + IN_LOAD_ORDER_MODULE_LIST_OFFSET))) + DLL_BASE_OFFSET)
int main()
{
printf("Kernel32.dll Base Address: 0x%p\n", (void*)KERNEL32_BASE_ADDRESS);
}
Notes
InLoadOrderModuleList
This list contains the modules in the order they were loaded into the process. Typically, the executable itself is the first entry, followed by important system DLLs like ntdll.dll and kernel32.dll.
InMemoryOrderModuleList
This list contains the modules in the order they are laid out in memory. It is not necessarily the same as the load order, although it often is similar.
You wont get a definitive "yes" since you are diving into Windows internals, which are explicitly undocumented and can change between windows versions. So walking the PEB is safer than just assuming a specific order.
But... here are the details for Windows 10 according to Windows Internals 7th Edition (Pavel Yosifovich, Alex Ionescu, Mark E. Russinovich, and David A. Solomon).
The image exe will be mapped first.
Ntdll is loaded early because a lot of the loader code exists there:
Ntdll.dll is always loaded and that it is the first piece of code to run in user mode as part of a new process.
For Windows subsystem applications, it manually loads Kernel32.dll and Kernelbase.dll, regardless of actual imports of the process.
Given that kernel32 will show up early regardless, doing the name compare isn't going to slow you down very much.
Also, as mentioned in the comment, be sure to use InLoadOrderModuleList
.