Pavel Yosifovich, co-author of “Windows Internals 7th Edition, Part 1” and author of “Windows Native API Programming”, continues his series building a code version of VMMap. Part 2 taught the tool to identify the files behind mapped and image blocks. This time the target is threads: the stack every thread owns, and the Thread Environment Block that VMMap tucks away under “private data.”
How Do You Enumerate Every Thread in a Process?
VMMap shows two thread-related things for every process: the thread stacks, and the TEBs. Getting a list of threads to work with means enumerating them first, and Pavel picks the simplest documented route: the tool help APIs, already familiar from enumerating processes. CreateToolhelp32Snapshot takes a snapshot, but unlike a process snapshot there is no way to scope it to a single process up front. The enumeration it returns with Thread32First and Thread32Next is always system-wide, so the tool has to filter every THREADENTRY32 it gets back down to the target process itself. That filtering needs the process ID, which the tool caches once with GetProcessIdOfThread rather than looking it up on every thread.
How Do You Get a Thread’s TEB Address?
Getting the Process Environment Block earlier in the series meant calling NtQueryInformationProcess. Threads have an equivalent: NtQueryInformationThread. The catch is that winternl.h, the header the tool has leaned on so far, only exposes a limited thread information class, enough to check for pending I/O or read a thread’s description, but nothing that hands back the TEB address. Getting THREAD_BASIC_INFORMATION, the structure that actually contains the TEB pointer, means moving to PHNT, the Process Hacker NT headers Pavel has used elsewhere in this series for full structure definitions of the PEB and TEB. With a handle from OpenThread and a call to NtQueryInformationThread, the TEB address for every thread in the process becomes available.
How Do You Read a Thread Stack Out of Another Process?
The TEB address alone is not the stack. The stack base and limit live inside the TEB’s first member, the NT_TIB substructure, and since this TEB belongs to a different process, reading it means ReadProcessMemory, the same function the tool would use to read any memory in a remote process. Only the NT_TIB portion needs to be read, since that is the only part of the TEB the tool actually uses, and the stack base and stack limit copy straight out of it. If the read fails, the tool zeroes the structure so a failed read reads as a clear sentinel value rather than garbage.
What Went Wrong Along the Way?
Two bugs surfaced live, and both were the kind that show up the moment code actually runs against a real process instead of just compiling. The first: ReadProcessMemory returned ERROR_INVALID_HANDLE, because the process was opened with PROCESS_QUERY_INFORMATION only, missing the PROCESS_VM_READ access right that reading its address space actually requires. The second, once the access right was added: the call still failed, this time because the handle being passed to ReadProcessMemory was a thread handle instead of the process handle the function actually expects. Both were fixed by breaking into the debugger, checking GetLastError, and reading the parameter list against what was actually being passed, not by guessing.
Why Doesn’t the Stack Range Match What VMMap Shows?
Once threads matched correctly to their TEBs and stacks, one gap remained. The stack base and limit read from the TEB reflect only the stack’s current, already-committed extent, not the full reserved region VMMap displays as three separate chunks: reserved, a guard page, and committed. Matching VMMap’s view exactly would mean grouping every block sharing the same allocation base as one logical stack, something Pavel leaves as an exercise rather than building out in this video.
What This Means Practically
- Use
CreateToolhelp32SnapshotwithThread32First/Thread32Nextto enumerate threads, and filter by process ID, since the snapshot itself is always system-wide winternl.honly exposes limited thread information classes; getting the TEB address requiresTHREAD_BASIC_INFORMATIONfrom the PHNT headers- Reading another process’s TEB or stack means opening it with
PROCESS_VM_READin addition toPROCESS_QUERY_INFORMATION, and callingReadProcessMemorywith the process handle, not a thread handle - A thread’s TEB holds its stack base and limit inside the
NT_TIBsubstructure, but that range reflects only the current committed stack, not the full reserved-plus-guard region a tool like VMMap displays
Part 4 picks up with image blocks: parsing PE sections the way VMMap already does.
Keep Learning
If you want to go further with the Native API and virtual memory internals behind this tool, these TrainSec courses go deeper:
- Windows Native API Programming: covers
NtQueryInformationProcess,NtQueryInformationThread, the PEB and TEB, and the rest of the native API surface in full - Windows System Programming 2: covers the virtual memory, thread, and process APIs this tool is built on
Related reading in the knowledge library: Process Memory Map in Code, Part 1 and Part 2, and VMMap Basics, the GUI-first walkthrough this series builds toward matching.