Guide Undocumented Windows Functions & Structures

Hexui Undetected CSGO Cheats Sinkicheat PUBG Cheat

Rake

Cesspool Admin
Administrator
Jan 21, 2014
12,372
78,998
2,414
How long you been coding/hacking?
5 years
Undocumented Windows API Functions & Structures
You will need to access and use many undocumented Windows API functions and structures in the course of your hacking career. In the Windows SDK there is a file called winternl.h which has many structures which are important to us, but many of them have "Reserved" member variables that Microsoft doesn't want us to know about and doesn't want us to use but everyone uses them regardless, especially antiviruses and other low level software. Using the winternl.h versions will not allow you complete access to everything you may need.

People have reversed these structured and defined them for us.

Undocumented NT Internals Website
In this part of your learning the best resource for browsing and getting to know them is undocumented.ntinternals.net because they are nicely organized and displayed.

x64dbg ntdll header file
When it comes to including these structures and function prototypes in your project I recommend using the x64dbg ntdll.h header file as it is the most recently updated and x64dbg is actively developed by a large group of people.

ReactOS
ReactOS can be regarded as a Windows clone, it can load PE files just like Windows so there source code is super helpful for learning about the internal workings of Windows. While we're talking about the Windows Loader, this function specifically is a great resource: CreateProcessInternal

Another cool site:
Vergilius Project

winternl.h
winternl.h contains many of these items but they have member variables labeled "reserved" which is not helpful. For some things the wintern.h header is fine but as you start to do more advanced stuff you will probably want to avoid using it. Mixing and matching winternl.h and the x64dbg header for instance will cause you a headache. My solution to this problem is I prefixed all the x64dbg structures with _RFW_ so they don't interfere with any Windows definitions, you may want to do the same.

Most Important undocumented items for game hacking
The most important "undocumented" items for game hacking which you will want to learn about are:
The reason they are important is because one of the first things you will want to do is to parse the linked list of loaded modules in the Peb->Ldr to either hide your module or to find hidden modules. The TIB contains a pointer to the TEB, the TEB contains a pointer to the PEB and the PEB contains a pointer to the PEB_LDR_DATA linked list.

To introduce you to the topic of undocumented nt internals we will show you how to play with the Ldr

_THREAD_BASIC_INFORMATION
Important because it contains a pointer to the TEB
C++:
typedef struct _THREAD_BASIC_INFORMATION
{
    NTSTATUS ExitStatus;
    PVOID TebBaseAddress;
    CLIENT_ID ClientId;
    ULONG_PTR AffinityMask;
    KPRIORITY Priority;
    LONG BasePriority;
} THREAD_BASIC_INFORMATION, *PTHREAD_BASIC_INFORMATION;
Thread Environment Block
Lots of juicy stuff here, most importantly it contains a pointer to the PEB
C++:
typedef struct _TEB
{
    NT_TIB NtTib;

    PVOID EnvironmentPointer;
    CLIENT_ID ClientId;
    PVOID ActiveRpcHandle;
    PVOID ThreadLocalStoragePointer;
    PPEB ProcessEnvironmentBlock;

    ULONG LastErrorValue;
    ULONG CountOfOwnedCriticalSections;
    PVOID CsrClientThread;
    PVOID Win32ThreadInfo;
    ULONG User32Reserved[26];
    ULONG UserReserved[5];
    PVOID WOW32Reserved;
    LCID CurrentLocale;
    ULONG FpSoftwareStatusRegister;
    PVOID ReservedForDebuggerInstrumentation[16];
#ifdef _WIN64
    PVOID SystemReserved1[30];
#else
    PVOID SystemReserved1[26];
#endif
    CHAR PlaceholderCompatibilityMode;
    CHAR PlaceholderReserved[11];
    ULONG ProxiedProcessId;
    ACTIVATION_CONTEXT_STACK ActivationStack;

    UCHAR WorkingOnBehalfTicket[8];
    NTSTATUS ExceptionCode;

    PACTIVATION_CONTEXT_STACK ActivationContextStackPointer;
    ULONG_PTR InstrumentationCallbackSp;
    ULONG_PTR InstrumentationCallbackPreviousPc;
    ULONG_PTR InstrumentationCallbackPreviousSp;
#ifdef _WIN64
    ULONG TxFsContext;
#endif
    BOOLEAN InstrumentationCallbackDisabled;
#ifndef _WIN64
    UCHAR SpareBytes[23];
    ULONG TxFsContext;
#endif
    GDI_TEB_BATCH GdiTebBatch;
    CLIENT_ID RealClientId;
    HANDLE GdiCachedProcessHandle;
    ULONG GdiClientPID;
    ULONG GdiClientTID;
    PVOID GdiThreadLocalInfo;
    ULONG_PTR Win32ClientInfo[62];
    PVOID glDispatchTable[233];
    ULONG_PTR glReserved1[29];
    PVOID glReserved2;
    PVOID glSectionInfo;
    PVOID glSection;
    PVOID glTable;
    PVOID glCurrentRC;
    PVOID glContext;

    NTSTATUS LastStatusValue;
    UNICODE_STRING StaticUnicodeString;
    WCHAR StaticUnicodeBuffer[261];

    PVOID DeallocationStack;
    PVOID TlsSlots[64];
    LIST_ENTRY TlsLinks;

    PVOID Vdm;
    PVOID ReservedForNtRpc;
    PVOID DbgSsReserved[2];

    ULONG HardErrorMode;
#ifdef _WIN64
    PVOID Instrumentation[11];
#else
    PVOID Instrumentation[9];
#endif
    GUID ActivityId;

    PVOID SubProcessTag;
    PVOID PerflibData;
    PVOID EtwTraceData;
    PVOID WinSockData;
    ULONG GdiBatchCount;

    union
    {
        PROCESSOR_NUMBER CurrentIdealProcessor;
        ULONG IdealProcessorValue;
        struct
        {
            UCHAR ReservedPad0;
            UCHAR ReservedPad1;
            UCHAR ReservedPad2;
            UCHAR IdealProcessor;
        } s1;
    } u1;

    ULONG GuaranteedStackBytes;
    PVOID ReservedForPerf;
    PVOID ReservedForOle;
    ULONG WaitingOnLoaderLock;
    PVOID SavedPriorityState;
    ULONG_PTR ReservedForCodeCoverage;
    PVOID ThreadPoolData;
    PVOID* TlsExpansionSlots;
#ifdef _WIN64
    PVOID DeallocationBStore;
    PVOID BStoreLimit;
#endif
    ULONG MuiGeneration;
    ULONG IsImpersonating;
    PVOID NlsCache;
    PVOID pShimData;
    USHORT HeapVirtualAffinity;
    USHORT LowFragHeapDataSlot;
    HANDLE CurrentTransactionHandle;
    PTEB_ACTIVE_FRAME ActiveFrame;
    PVOID FlsData;

    PVOID PreferredLanguages;
    PVOID UserPrefLanguages;
    PVOID MergedPrefLanguages;
    ULONG MuiImpersonation;

    union
    {
        USHORT CrossTebFlags;
        USHORT SpareCrossTebBits : 16;
    } u2;
    union
    {
        USHORT SameTebFlags;
        struct
        {
            USHORT SafeThunkCall : 1;
            USHORT InDebugPrint : 1;
            USHORT HasFiberData : 1;
            USHORT SkipThreadAttach : 1;
            USHORT WerInShipAssertCode : 1;
            USHORT RanProcessInit : 1;
            USHORT ClonedThread : 1;
            USHORT SuppressDebugMsg : 1;
            USHORT DisableUserStackWalk : 1;
            USHORT RtlExceptionAttached : 1;
            USHORT InitialThread : 1;
            USHORT SessionAware : 1;
            USHORT LoadOwner : 1;
            USHORT LoaderWorker : 1;
            USHORT SkipLoaderInit : 1;
            USHORT SpareSameTebBits : 1;
        } s2;
    } u3;

    PVOID TxnScopeEnterCallback;
    PVOID TxnScopeExitCallback;
    PVOID TxnScopeContext;
    ULONG LockCount;
    LONG WowTebOffset;
    PVOID ResourceRetValue;
    PVOID ReservedForWdf;
    ULONGLONG ReservedForCrt;
    GUID EffectiveContainerId;

Process Environment Block
Every running process has a PEB, it's a structure that resides in usermode. This structure underlies the functionality of many Windows API functions. For instance it contains the BeingDebugged bool, when you call IsDebuggerPresent it is actually just checking this flag. It contains the ImageBaseAddress which is the memory address of the main .exe.

The PEB looks like this, taken from the x64dbg header file:
C++:
typedef struct _PEB
{
    BOOLEAN InheritedAddressSpace;
    BOOLEAN ReadImageFileExecOptions;
    BOOLEAN BeingDebugged;
    union
    {
        BOOLEAN BitField;
        struct
        {
            BOOLEAN ImageUsesLargePages : 1;
            BOOLEAN IsProtectedProcess : 1;
            BOOLEAN IsImageDynamicallyRelocated : 1;
            BOOLEAN SkipPatchingUser32Forwarders : 1;
            BOOLEAN IsPackagedProcess : 1;
            BOOLEAN IsAppContainer : 1;
            BOOLEAN IsProtectedProcessLight : 1;
            BOOLEAN IsLongPathAwareProcess : 1;
        } s1;
    } u1;

    HANDLE Mutant;

    PVOID ImageBaseAddress;
    _PPEB_LDR_DATA Ldr;
    _PRTL_USER_PROCESS_PARAMETERS ProcessParameters;
    PVOID SubSystemData;
    PVOID ProcessHeap;
    PRTL_CRITICAL_SECTION FastPebLock;
    PVOID AtlThunkSListPtr;
    PVOID IFEOKey;
    union
    {
        ULONG CrossProcessFlags;
        struct
        {
            ULONG ProcessInJob : 1;
            ULONG ProcessInitializing : 1;
            ULONG ProcessUsingVEH : 1;
            ULONG ProcessUsingVCH : 1;
            ULONG ProcessUsingFTH : 1;
            ULONG ProcessPreviouslyThrottled : 1;
            ULONG ProcessCurrentlyThrottled : 1;
            ULONG ReservedBits0 : 25;
        } s2;
    } u2;
    union
    {
        PVOID KernelCallbackTable;
        PVOID UserSharedInfoPtr;
    } u3;
    ULONG SystemReserved[1];
    ULONG AtlThunkSListPtr32;
    PVOID ApiSetMap;
    ULONG TlsExpansionCounter;
    PVOID TlsBitmap;
    ULONG TlsBitmapBits[2];

    PVOID ReadOnlySharedMemoryBase;
    PVOID SharedData; // HotpatchInformation
    PVOID* ReadOnlyStaticServerData;

    PVOID AnsiCodePageData; // PCPTABLEINFO
    PVOID OemCodePageData; // PCPTABLEINFO
    PVOID UnicodeCaseTableData; // PNLSTABLEINFO

    ULONG NumberOfProcessors;
    ULONG NtGlobalFlag;

    LARGE_INTEGER CriticalSectionTimeout;
    SIZE_T HeapSegmentReserve;
    SIZE_T HeapSegmentCommit;
    SIZE_T HeapDeCommitTotalFreeThreshold;
    SIZE_T HeapDeCommitFreeBlockThreshold;

    ULONG NumberOfHeaps;
    ULONG MaximumNumberOfHeaps;
    PVOID* ProcessHeaps; // PHEAP

    PVOID GdiSharedHandleTable;
    PVOID ProcessStarterHelper;
    ULONG GdiDCAttributeList;

    PRTL_CRITICAL_SECTION LoaderLock;

    ULONG OSMajorVersion;
    ULONG OSMinorVersion;
    USHORT OSBuildNumber;
    USHORT OSCSDVersion;
    ULONG OSPlatformId;
    ULONG ImageSubsystem;
    ULONG ImageSubsystemMajorVersion;
    ULONG ImageSubsystemMinorVersion;
    ULONG_PTR ActiveProcessAffinityMask;
    GDI_HANDLE_BUFFER GdiHandleBuffer;
    PVOID PostProcessInitRoutine;

    PVOID TlsExpansionBitmap;
    ULONG TlsExpansionBitmapBits[32];

    ULONG SessionId;

    ULARGE_INTEGER AppCompatFlags;
    ULARGE_INTEGER AppCompatFlagsUser;
    PVOID pShimData;
    PVOID AppCompatInfo; // APPCOMPAT_EXE_DATA

    _UNICODE_STRING CSDVersion;

    PVOID ActivationContextData; // ACTIVATION_CONTEXT_DATA
    PVOID ProcessAssemblyStorageMap; // ASSEMBLY_STORAGE_MAP
    PVOID SystemDefaultActivationContextData; // ACTIVATION_CONTEXT_DATA
    PVOID SystemAssemblyStorageMap; // ASSEMBLY_STORAGE_MAP

    SIZE_T MinimumStackCommit;

    PVOID* FlsCallback;
    LIST_ENTRY FlsListHead;
    PVOID FlsBitmap;
    ULONG FlsBitmapBits[FLS_MAXIMUM_AVAILABLE / (sizeof(ULONG) * 8)];
    ULONG FlsHighIndex;

    PVOID WerRegistrationData;
    PVOID WerShipAssertPtr;
    PVOID pUnused; // pContextData
    PVOID pImageHeaderHash;
    union
    {
        ULONG TracingFlags;
        struct
        {
            ULONG HeapTracingEnabled : 1;
            ULONG CritSecTracingEnabled : 1;
            ULONG LibLoaderTracingEnabled : 1;
            ULONG SpareTracingBits : 29;
        } s3;
    } u4;
    ULONGLONG CsrServerReadOnlySharedMemoryBase;
    PVOID TppWorkerpListLock;
    LIST_ENTRY TppWorkerpList;
    PVOID WaitOnAddressHashTable[128];
    PVOID TelemetryCoverageHeader; // REDSTONE3
    ULONG CloudFileFlags;
}
PEB_LDR_DATA
Most importantly the PEB contains a pointer to the Ldr, defined as _PPEB_LDR_DATA. All the other information provided here was to get you to the Ldr. The Ldr contains linked lists starting with InLoadOrderModuleList which contains all the modules loaded in memory for the process. This is what is used on the backend for ToolHelp32Snapshot etc...

If you have to hide a module to bypass anticheat or something like that, hooking all the functions which wrap this structure isn't always enough. You will find yourself having to parse this list and modify it.

The Ldr looks like this:
C++:
typedef struct _RFW_PEB_LDR_DATA
{
    ULONG Length;
    BOOLEAN Initialized;
    HANDLE SsHandle;
    LIST_ENTRY InLoadOrderModuleList;
    LIST_ENTRY InMemoryOrderModuleList;
    LIST_ENTRY InInitializationOrderModuleList;
    PVOID EntryInProgress;
    BOOLEAN ShutdownInProgress;
    HANDLE ShutdownThreadId;
} *RFW_PPEB_LDR_DATA;
NTQueryInformationProcess
NtQueryInformationProcess is a "undocumented" Windows API function, this is your entry point to grabbing the PEB which will enable you to parse it. Using this function is a good entrypoint to learning about Windows internals. Use the PROCESS_BASIC_INFORMATION argument and you can get an address to the PEB.

C++:
typedef struct _PROCESS_BASIC_INFORMATION
{
    NTSTATUS ExitStatus;
    PPEB PebBaseAddress;
    ULONG_PTR AffinityMask;
    KPRIORITY BasePriority;
    HANDLE UniqueProcessId;
    HANDLE InheritedFromUniqueProcessId;
} PROCESS_BASIC_INFORMATION, *PPROCESS_BASIC_INFORMATION;
Get the PEB using NtQueryInformationProcess() Externally
C++:
typedef NTSTATUS(__stdcall* tNtQueryInformationProcess)
(
    HANDLE ProcessHandle,
    PROCESSINFOCLASS ProcessInformationClass,
    PVOID ProcessInformation,
    ULONG ProcessInformationLength,
    PULONG ReturnLength
    );

tNtQueryInformationProcess NtQueryInfoProc = nullptr;

bool ImportNTQueryInfo()
{
    NtQueryInfoProc = (tNtQueryInformationProcess)GetProcAddress(GetModuleHandle(TEXT("ntdll.dll")), "NtQueryInformationProcess");
    if (NtQueryInfoProc == nullptr)
    {
        return false;
    }
    else return true;
}

PEB GetPEB()
{
    PROCESS_BASIC_INFORMATION pbi;
    PEB peb = { 0 };
    if (!NtQueryInfoProc) ImportNTQueryInfo();

    if (NtQueryInfoProc)
    {
        NTSTATUS status = NtQueryInfoProc(handle, ProcessBasicInformation, &pbi, sizeof(pbi), 0);
        if (NT_SUCCESS(status))
        {
            ReadProcessMemory(handle, pbi.PebBaseAddress, &peb, sizeof(peb), 0);
        }
    }
    return peb;
}
Get the PEB Internally
Much easier internally, you read the fs segment register offset 0x30/0x60 that gives you address of the PEB depending if you're on x86 or x64.
C++:
__readfsdword(0x30); //x86
__readgsqword(0x60); //x64

Parsing the PEB_LDR_DATA linked list
Once you've gotten the PEB you can access the Ldr. Here is a function which takes a module name, such as "client.dll" and returns you the Ldr and a wrapper which grabs the base address.


C++:
PEB* GetPEBInternal()
{
#ifdef _WIN64
    PEB* peb = (PEB*)__readgsqword(0x60);

#else
    PEB* peb = (PEB*)__readfsdword(0x30);
#endif

    return peb;
}

LDR_DATA_TABLE_ENTRY* GetLDREntryInternal(const wchar_t* modName)
{
    LDR_DATA_TABLE_ENTRY* modEntry = nullptr;

    PEB* peb = GetPEBInternal();

    LIST_ENTRY head = peb->Ldr->InMemoryOrderModuleList;

    LIST_ENTRY curr = head;

    for (auto curr = head; curr.Flink != &peb->Ldr->InMemoryOrderModuleList; curr = *curr.Flink)
    {
        LDR_DATA_TABLE_ENTRY* mod = (LDR_DATA_TABLE_ENTRY*)CONTAINING_RECORD(curr.Flink, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks);

        if (mod->BaseDllName.Buffer)
        {
            if (_wcsicmp(modName, mod->BaseDllName.Buffer) == 0)
            {
                modEntry = mod;
                break;
            }
        }
    }
    return modEntry;
}

char* GetModuleBaseAddressInternalPEB(const wchar_t* modName)
{
    LDR_DATA_TABLE_ENTRY* modEntry = GetLDREntryInternal(modName);

    return (char*)modEntry->DllBase;
}
You can see how I use this function to pattern scan modules internally without using ToolHelp32Snapshot Guide - External & Internal Pattern Scanning Guide

There are several other similar functions here
https://guidedhacking.com/threads/internal-safe-getmodulehandlew-getprocaddress.13565/

How to Hide Module from PEB Ldr
Source Code - How to Hide Module from PEB

Here is a function from Broihon which does something similar but it uses NtQueryInformationProcess, MemoryBasicInformation & section data

C++:
UINT EnumModulesNTQVM(HANDLE hTargetProcess)
{
    MEMORY_BASIC_INFORMATION MBI{ 0 };

    SECTION_INFO section_info[2] = { 0, 0 };
    section_info[0].MaxLen = sizeof(SECTION_INFO::pData);
    section_info[0].szData = reinterpret_cast<wchar_t*>(section_info[0].pData);
    section_info[1].MaxLen = sizeof(SECTION_INFO::pData);
    section_info[1].szData = reinterpret_cast<wchar_t*>(section_info[1].pData);

    UINT ret{ 0 };

    while (NT_SUCCESS(NT::NtQueryVirtualMemory(hTargetProcess, MBI.BaseAddress, MEMORYINFOCLASS::MemoryBasicInformation, &MBI, sizeof(MBI), nullptr)))
    {
        if (MBI.State & MEM_COMMIT)
        {
            if (NT_SUCCESS(NT::NtQueryVirtualMemory(hTargetProcess, MBI.BaseAddress, MEMORYINFOCLASS::MemoryMappedFilenameInformation, &section_info[0], sizeof(SECTION_INFO), nullptr)))
            {
                void *    CurrentModuleBase    = MBI.BaseAddress;
                DWORD    CurrentModuleSize    = 0;

                NT::NtQueryVirtualMemory(hTargetProcess, MBI.BaseAddress, MEMORYINFOCLASS::MemoryMappedFilenameInformation, &section_info[1], sizeof(SECTION_INFO), nullptr);

                while (!wcscmp(section_info[0].szData, section_info[1].szData))
                {
                    MBI.BaseAddress = reinterpret_cast<BYTE*>(MBI.BaseAddress) + MBI.RegionSize;
                    CurrentModuleSize += MBI.RegionSize;

                    NT::NtQueryVirtualMemory(hTargetProcess, MBI.BaseAddress, MEMORYINFOCLASS::MemoryBasicInformation, &MBI, sizeof(MBI), nullptr);
                    if (NT_FAIL(NT::NtQueryVirtualMemory(hTargetProcess, MBI.BaseAddress, MEMORYINFOCLASS::MemoryMappedFilenameInformation, &section_info[1], sizeof(SECTION_INFO), nullptr)))
                    {
                        break;
                    }
                }

                printf("%ls\n\t%p\n\t%08X\n\n", section_info[0].szData, CurrentModuleBase, CurrentModuleSize);
            }
            else
            {
                MBI.BaseAddress = reinterpret_cast<BYTE*>(MBI.BaseAddress) + MBI.RegionSize;
            }
        }
        else
        {
            MBI.BaseAddress = reinterpret_cast<BYTE*>(MBI.BaseAddress) + MBI.RegionSize;
        }
    }

    return ret;
}
Conclusion
Now you have a basic understanding of the undocumented nt internals, you've gained some hands on experience parsing the Ldr module list and should you need to use any of these functions or structures you will be ready to do so in the future.

Learn about SysCalls from @timb3r
Tutorial - Understanding Windows SysCalls - SysCall Dumper

At this point in your research you may also want to read this thread
Guide - PE File Format & Windows PE Loader

Discuss - Collection of undocumented windows functions from ntdll
Guide - Hidden structure definitions in UxTheme.dll
Geoff Chappell, Software Analyst
Vergilius Project
 
Last edited:

Kage

Dank Tier Donator
Nobleman
Jan 26, 2019
59
2,553
1
Here is a function from Broihon which does something similar but it uses NtQueryInformationProcess, MemoryBasicInformation & section data
Is it possible to hide from this function? I tried the following steps but nothing works.
1. LoadLibary()
2. Erase PEH
3. Unlink PEB
4. Run NtQueryInformationProcess
 

XdarionX

Dying Light Hacker
Dank Tier VIP
Dank Tier Donator
Mar 30, 2018
876
24,608
116
I wanted to test the limits that work with loadlibary. If there is nothing I will switch to manual mapping
there are no limits, loadlib is not good idea to use because there are milion ways how to detect/block it, from basic hooking LdrLoadDll (if they find out that dll is being unloaded from peb = instant ban, too suspicious) to blocking it with something sneaky:
C++:
ntdll!LdrSystemDllInitBlock:
 db 50 00 00 00 00 00 00 00
    
patched:
 db 50 00 00 00 00 00 E7 F9
source: Developments | Cra0kalo's Development Adventures
 
  • Like
Reactions: Kage

KF1337

*copies code from tutorials, then breaks it.*
Dank Tier Donator
Full Member
Nobleman
Jan 30, 2020
151
3,603
0
Small info on the external method of using NtQueryInformationProcess:

one argument is a pointer to a PROCESS_BASIC_INFORMATION struct. According to NTAPI Undocumented Functions
it should have a size of 0x018, or it does not work afaik.
C++:
struct WINT_PROCESS_BASIC_INFORMATION
{
    NTSTATUS ExitStatus;
    PEB* PebBaseAddress;
    char pad[16];
};
static_assert(sizeof(WINT_PROCESS_BASIC_INFORMATION) == 0x18);
Oh, and the process handle should have (PROCESS_QUERY_INFORMATION | PROCESS_VM_READ) of course.
 
Attention! Before you post:

Read the How to Ask Questions Guide
99% of questions are answered in the Beginner's Guide, do it before asking a question.

No Hack Requests. Post in the correct section.  Search the forum first. Read the rules.

How to make a good post:

  • Fill out the form correctly
  • Tell us the game name & coding language
  • Post everything we need to know to help you
  • Ask specific questions, be descriptive
  • Post errors, line numbers & screenshots
  • Post code snippets using code tags
  • If it's a large project, zip it up and attach it

If you do not comply, your post may be deleted.  We want to help, please make a good post and we will do our best to help you.

Community Mods