Created
July 26, 2025 22:13
-
-
Save botder/2259a4f79e1b1b364258ec0dc29c4fe7 to your computer and use it in GitHub Desktop.
This piece of source code shows you how to override BaseThreadInitThunk, which gets called by ntdll.dll before a thread starts its execution. This technique is also used by browsers to combat malicious threads/actors.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <cstdio> | |
#define UNICODE | |
#define NOMINMAX | |
#define WIN32_NO_STATUS | |
#define WIN32_LEAN_AND_MEAN | |
#define _WIN32_WINNT 0x0A00 | |
#include <windows.h> | |
#include <windowsx.h> | |
#undef WIN32_NO_STATUS | |
#include <ntstatus.h> | |
#include <winnt.h> | |
#include <winternl.h> | |
HANDLE (__fastcall*pBaseThreadInitThunk)(DWORD reserved, LPTHREAD_START_ROUTINE routine, LPVOID parameter) = nullptr; | |
DWORD WINAPI NopThread(LPVOID) | |
{ | |
return 0; | |
} | |
HANDLE __fastcall CustomBaseThreadInitThunk(DWORD reserved, LPTHREAD_START_ROUTINE routine, LPVOID parameter) | |
{ | |
auto danger = ""; | |
if (routine == (LPTHREAD_START_ROUTINE)LoadLibraryA) | |
danger = "LoadLibraryA"; | |
else if (routine == (LPTHREAD_START_ROUTINE)LoadLibraryW) | |
danger = "LoadLibraryW"; | |
if (!danger[0]) | |
{ | |
MEMORY_BASIC_INFORMATION info{}; | |
if (VirtualQuery(routine, &info, sizeof(info))) | |
{ | |
if (info.State != MEM_COMMIT) | |
danger = "MEM_COMMIT"; | |
else if (info.Protect == PAGE_EXECUTE_READWRITE) | |
danger = "PAGE_EXECUTE_READWRITE"; | |
} | |
else | |
{ | |
danger = "VirtualQuery"; | |
} | |
} | |
if (danger[0]) | |
routine = NopThread; | |
else | |
danger = "none"; | |
std::printf("[BaseThreadInitThunk] reserved:%lu routine:%p parameter:%p danger:%s\n", reserved, routine, parameter, danger); | |
return pBaseThreadInitThunk(reserved, routine, parameter); | |
} | |
int main() | |
{ | |
HMODULE const kernel32 = GetModuleHandleW(L"kernel32.dll"); | |
if (kernel32 == nullptr) | |
{ | |
std::fputs("Could not get a handle to the kernel32.dll module", stderr); | |
return 1; | |
} | |
pBaseThreadInitThunk = reinterpret_cast<decltype(pBaseThreadInitThunk)>(GetProcAddress(kernel32, "BaseThreadInitThunk")); | |
if (pBaseThreadInitThunk == nullptr) | |
{ | |
std::fputs("Could not find the BaseThreadInitThunk routine in the kernel32.dll module", stderr); | |
return 2; | |
} | |
std::puts("[+] kernel32.dll is OK"); | |
HMODULE const ntdll = GetModuleHandleW(L"ntdll.dll"); | |
if (ntdll == nullptr) | |
{ | |
std::fputs("Could not get a handle to the ntdll.dll module", stderr); | |
return 3; | |
} | |
auto base = reinterpret_cast<unsigned char*>(ntdll); | |
auto dos = reinterpret_cast<IMAGE_DOS_HEADER const*>(ntdll); | |
auto nt = reinterpret_cast<IMAGE_NT_HEADERS32 const*>(base + dos->e_lfanew); | |
auto section = IMAGE_FIRST_SECTION(nt); | |
bool hasDataSection = false; | |
for (UINT i = 0; i != nt->FileHeader.NumberOfSections; ++i, ++section) | |
{ | |
if (!section->Misc.VirtualSize) | |
continue; | |
if (!strncmp(reinterpret_cast<char const*>(section->Name), ".data", IMAGE_SIZEOF_SHORT_NAME)) | |
{ | |
hasDataSection = true; | |
break; | |
} | |
} | |
if (!hasDataSection) | |
{ | |
std::fputs("Could not find the .data section in the ntdll.dll module", stderr); | |
return 4; | |
} | |
std::puts("[+] ntdll.dll is OK"); | |
const auto data = reinterpret_cast<FARPROC*>(base + section->VirtualAddress); | |
const auto numPointers = section->Misc.VirtualSize / sizeof(FARPROC); | |
FARPROC* thunkPtr = nullptr; | |
for (DWORD i = 0; i < numPointers; ++i) | |
{ | |
if (data[i] == reinterpret_cast<FARPROC>(pBaseThreadInitThunk)) | |
{ | |
thunkPtr = &data[i]; | |
break; | |
} | |
} | |
if (thunkPtr == nullptr) | |
{ | |
std::fputs("Could not find the pointer to BaseThreadInitThunk in the .data section of the ntdll.dll module", stderr); | |
return 5; | |
} | |
PVOID previous = InterlockedCompareExchangePointer((void**)thunkPtr, (void*)CustomBaseThreadInitThunk, (void*)pBaseThreadInitThunk); | |
if (previous != (void*)pBaseThreadInitThunk) | |
{ | |
std::fputs("Failed to overwrite the BaseThreadInitThunk pointer in the ntdll.dll module", stderr); | |
return 6; | |
} | |
std::puts("[+] CustomBaseThreadInitThunk is active now"); | |
HANDLE thread = CreateThread(nullptr, 0, NopThread, nullptr, 0, nullptr); | |
WaitForSingleObject(thread, INFINITE); | |
thread = CreateThread(nullptr, 0, (LPTHREAD_START_ROUTINE)LoadLibraryA, (LPVOID)"user32.dll", 0, nullptr); | |
WaitForSingleObject(thread, INFINITE); | |
thread = CreateThread(nullptr, 0, (LPTHREAD_START_ROUTINE)LoadLibraryW, (LPVOID)L"user32.dll", 0, nullptr); | |
WaitForSingleObject(thread, INFINITE); | |
thread = CreateRemoteThread(GetCurrentProcess(), nullptr, 0, (LPTHREAD_START_ROUTINE)LoadLibraryA, nullptr, 0, nullptr); | |
WaitForSingleObject(thread, INFINITE); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment