Parsing Remote Process PEB


Contents

Intro


If you've clicked on this document I'm assuming you already know what the PEB is (and thus you can skip this chapter) however for those that don't: PEB stands for Process Environment Block and it is a struct of data containing information about the current process. All native Windows processes have a PEB and it contains information like the beingDebugged flag, all loaded Dynamic Link Libraries and many other process critical information.

Overview


For those that want the challenge of writing this yourself, this section will go over how we go from nothing to extracting any information we want from the PEB:

The first thing we require is the handle to the target process which requires getting the Process ID (PID). There are a few ways you can implement this depending on how this snippet is being implemented:

  • - Loop over all the running processes using CreateToolhelp32Snapshot and then string match for the process(s) you want then storing the returned th32ProcessID value
  • - Take the PID as an argument
  • - Hardcode the PID (may be better if messing with code)
Once you have the PID you can call OpenProcess to retrieve a handle to the process. Once we have a handle we can use NtQueryInformationProcess which returns the PROCESS_BASIC_INFORMATION struct:

typedef struct _PROCESS_BASIC_INFORMATION {
    NTSTATUS  ExitStatus;
    PPEB      PebBaseAddress;
    ULONG_PTR AffinityMask;
    KPRIORITY BasePriority;
    HANDLE    UniqueProcessId;
    HANDLE    InheritedFromUniqueProcessId;
} PROCESS_BASIC_INFORMATION, * PPROCESS_BASIC_INFORMATION;

As we can see the second entry returns us the base address of the PEB. Once we have this address we have two options for parsing PEB information:

  • - Define the PEB structs to correctly store all information from the PEB locally
  • - Use raw offsets to navigate and read the desired variables
For me, I prefer using the raw offsets unless I require many variables or if this code is required to be extendable/used by others. The other reason I prefer raw offsets is because if you want to define the PEB structure you'll be finding and copying a huge amount of sub structures to the point where you have roughly 400 lines of defined structs just to ensure all types in the PEB are defined correctly.

We can finally use ReadProcessMemory with our PEB address plus the offset we want and we're golden!

Getting PEB Offsets


For those of you that tried defining the PEB structure and realized that it's an absolute pain, I'll now show you the easiest way to find the PEB offsets:

For this I'm going to be using WinDBG Preview which you can only download from the Microsoft store. If you don't have access to the Microsoft Store because you value your privacy and are using a stripped burner VM, you can also use the old original WinDBG. Once you lunch WinDBG you can attach to any process and then run the following command: !peb:

Pressing the highlighted base address at the top will then show you all of the structures offsets. See below:

Code Example


Here is an example (in C) which will read the beingDebugged value from a specified process:

#include <windows.h>
#include <winternl.h>
#include <stdio.h>
#include <stdint.h>

// Manually defining NtQueryInformationProcess
typedef NTSTATUS(NTAPI* _NtQueryInformationProcess)(
    IN	HANDLE              ProcessHandle,
    IN	PROCESSINFOCLASS    ProcessInformationClass,
    OUT	PVOID               ProcessInformation,
    IN	ULONG               ProcessInformationLength,
    OUT PULONG              ReturnLength OPTIONAL
);
_NtQueryInformationProcess NtQueryInfoProcess;

int main() {
    HANDLE                      hProcess;
    DWORD                       processId = 1234;
    BYTE                        beingDebugged;
    PROCESS_BASIC_INFORMATION   pbi;

    // Dynamically resolving NtQueryInformationProcess
    NtQueryInfoProcess = (_NtQueryInformationProcess)GetProcAddress(GetModuleHandleA("ntdll"), "NtQueryInformationProcess");
    if (NtQueryInfoProcess == NULL) {
        printf("Failed to resolve NtQueryInformationProcess\n");
        return 0;
    }

    // Get handle to process using pid
    hProcess = OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, FALSE, processId);
    if (!hProcess) {
        printf("OpenProcess failed for pid: %i\n", processId);
        return 0;
    }

    // Get ProcessBasicInformation using the process handle in NtQueryInfoProcess
    NTSTATUS status = NtQueryInfoProcess(hProcess, ProcessBasicInformation, &pbi, sizeof(pbi), 0);
    if (!NT_SUCCESS(status)) {
        printf("NTSTATUS had a failure code when trying NtQueryInfoProcess for pid: %i\n", processId);
        return 0;
    }

    // Offset from PEB->BeingDebugged is +0x02
    if (!ReadProcessMemory(hProcess, (PVOID)((uint64_t)pbi.PebBaseAddress + 0x02), &beingDebugged, sizeof(beingDebugged), NULL)) {
        printf("ReadProcessMeory failed to read ProcessParameters offset for pid %i, Error:%i\n", processId, GetLastError());
        return 0;
    }

    printf("Value in beingDebugged PEB flag is: %i\n", beingDebugged);

    return 1;
}

Hopefully this example is simple enough to expand for your use case. This example is also held in a Github repository on my Github. As per usual any questions, shoot me a direct message on twitter!