Skip to content

Instantly share code, notes, and snippets.

@botder
Created July 26, 2025 22:13
Show Gist options
  • Save botder/2259a4f79e1b1b364258ec0dc29c4fe7 to your computer and use it in GitHub Desktop.
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.
#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