Wednesday, August 16, 2023

Windows Process Initialization

Most code on Windows runs in user-space. This means that, when we first run a program, it needs to perform some rituals to successfully callback into the kernel. In my last post, we saw a stack trace just before our program unwound, and how upon hitting its first return address, we initiated contact with the kernel32 DLL, where various x64 functions reside.

Here we'll use the previous binary from our last post to look at Windows process initialization. In the other post, we briefly mentioned NTDLL.DLL, LdrInitializeThunk and LdrpInitializeProcess. These functions are important in the early stages of process initialization. They're essentially invoked before any other significant actions take place, initializing and establishing essential information contained within the Process Environment Block, or PEB.

LdrInitializeThunk is the entry point of NTDLL.DLL. So, in this vein, it's basically a kernel callback that jump starts process initialization. Though perhaps it is not the most interesting callback. But it is a stepping stone involved in this sort of back and forth with user space and kernel space. The Process Environment Block (PEB) is a fundamental data structure that holds details about a process which influence its behavior and interactions with Windows. It essentially bridges the gap between user space and kernel space.

Within the PEB lies the PEB_LDR_DATA structure, which serves as a repository for a variety of loader-related information. It helps manage loaded modules — Dynamic Link Libraries. And the LDR_DATA_TABLE_ENTRY structure is essentially NTDLL’s record of how each DLL is loaded into the process's memory space.

Each instance of LDR_DATA_TABLE_ENTRY encapsulates attributes of a loaded DLL, including its base address, entry point, DLL name, and various flags detailing its loading state and characteristics.

In a ritual initialization, LdrInitializeProcess helps setup the Process Environment Block, initializing the PEB_LDR_DATA structure, and the LDR_DATA_TABLE_ENTRY structure, so the process's interactions with DLLs will work correctly.

And inside the Process Environment Block structure lies the Thread Information Block, or Thread Environment Block (TEB) structure.

So, to recap: each process has both an associated Process Environment Block and Thread Environment Block. That is to say, each PEB also holds a TEB — a block of information about a processes threads. The two terms, "Thread Information Block" and "Thread Environment Block" are essentially interchangeable terms. Some history from Wikipedia on the subject:

In computing, the Win32 Thread Information Block (TIB) is a data structure in Win32 on x86 that stores information about the currently running thread. It is also known as the Thread Environment Block (TEB) for Win32. It descended from, and is backward-compatible on 32-bit systems with, a similar structure in OS/2.[1]

The Thread Information Block is officially undocumented for Windows 9x. The Windows NT series DDK (as well as the MinGW/ReactOS implementation) includes a struct NT_TIB in winnt.h that documents the subsystem independent part. Even before Thread Information Block was effectively documented, many applications have already started using its fields that they are effectively a part of the API. The first field containing the SEH frame, in particular, is directly referenced by the code produced by Microsoft's own compiler.[1] The Win32 subsystem-specific part of the Thread Environment Block is undocumented, but Wine includes a definition in winternl.h.[2]

The Thread Information Block can be used to get a lot of information on the process without calling Win32 API. Examples include emulating GetLastError(), GetVersion(). Through the pointer to the Process Environment Block one can obtain access to the import tables (IAT), process startup arguments, image name, etc. It is accessed from the FS segment register on 32-bit Windows and GS on 64-bit Windows.

Microsoft's documentation of the Process Environment Block prototype is currently defined by this definition:

typedef struct _PEB {
  BYTE                          Reserved1[2];
  BYTE                          BeingDebugged;
  BYTE                          Reserved2[1];
  PVOID                         Reserved3[2];
  PPEB_LDR_DATA                 Ldr;
  PRTL_USER_PROCESS_PARAMETERS  ProcessParameters;
  PVOID                         Reserved4[3];
  PVOID                         AtlThunkSListPtr;
  PVOID                         Reserved5;
  ULONG                         Reserved6;
  PVOID                         Reserved7;
  ULONG                         Reserved8;
  ULONG                         AtlThunkSListPtr32;
  PVOID                         Reserved9[45];
  BYTE                          Reserved10[96];
  PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoutine;
  BYTE                          Reserved11[128];
  PVOID                         Reserved12[1];
  ULONG                         SessionId;
} PEB, *PPEB;

We can examine this structure in WindDbg like so. Here we can see this in practice! And some structures Microsoft doesn't list in its simple prototype definition, too:

0:000> !peb
PEB at 000000927af26000
    InheritedAddressSpace:    No
    ReadImageFileExecOptions: No
    BeingDebugged:            Yes
    ImageBaseAddress:         00007ff619330000
    NtGlobalFlag:             70
    NtGlobalFlag2:            0
    Ldr                       00007ffce4b343c0
    Ldr.Initialized:          Yes
    Ldr.InInitializationOrderModuleList: 0000021699163e20 . 00000216991646f0
    Ldr.InLoadOrderModuleList:           0000021699163ff0 . 0000021699166f70
    Ldr.InMemoryOrderModuleList:         0000021699164000 . 0000021699166f80
                    Base TimeStamp                     Module
            7ff619330000 64dc8e79 Aug 16 04:53:13 2023 C:\Users\User\Downloads\createfile.exe
            7ffce49b0000 eee69ec7 Jan 03 17:42:15 2097 C:\Windows\SYSTEM32\ntdll.dll
            7ffce34e0000 a6d7fcaa Sep 13 15:59:38 2058 C:\Windows\System32\KERNEL32.DLL
            7ffce21b0000 c42b59fb Apr 17 03:23:39 2074 C:\Windows\System32\KERNELBASE.dll
            7ffce36f0000 c4d8152c Aug 26 03:52:12 2074 C:\Windows\System32\msvcrt.dll
    SubSystemData:     0000000000000000
    ProcessHeap:       0000021699160000
    ProcessParameters: 0000021699163500
    CurrentDirectory:  'C:\Windows\system32\'
    WindowTitle:  'C:\Users\User\Downloads\createfile.exe'
    ImageFile:    'C:\Users\User\Downloads\createfile.exe'
    CommandLine:  'C:\Users\User\Downloads\createfile.exe'
    DllPath:      '< Name not readable >'
    Environment:  00000216991614b0

And likewise, we can also step into our program to observe the Thread Environment Block in the Windows Debugger as well:

0:000> !teb
TEB at 000000e75546c000
    ExceptionList:        0000000000000000
    StackBase:            000000e755800000
    StackLimit:           000000e7557fd000
    SubSystemTib:         0000000000000000
    FiberData:            0000000000001e00
    ArbitraryUserPointer: 0000000000000000
    Self:                 000000e75546c000
    EnvironmentPointer:   0000000000000000
    ClientId:             00000000000020fc . 00000000000004b4
    RpcHandle:            0000000000000000
    Tls Storage:          000002cbfe1644e0
    PEB Address:          000000e75546b000
    LastErrorValue:       0
    LastStatusValue:      0
    Count Owned Locks:    0
    HardErrorMode:        0

Let's take a look at a definition regarding PEB_LDR data per Microsoft's documents. Microsoft Windows formally documents _LDR_DATA_TABLE_ENTRY, which is part of PEB_LDR_DATA structure, with a rather simplististic prototype like so:

typedef struct _LDR_DATA_TABLE_ENTRY {
    PVOID Reserved1[2];
    LIST_ENTRY InMemoryOrderLinks;
    PVOID Reserved2[2];
    PVOID DllBase;
    PVOID EntryPoint;
    PVOID Reserved3;
    UNICODE_STRING FullDllName;
    BYTE Reserved4[8];
    PVOID Reserved5[3];
    union {
        ULONG CheckSum;
        PVOID Reserved6;
    };
    ULONG TimeDateStamp;
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;

But if we trace over over binary with WinDbg, we can examine this structure seems to have some extended attributes and additional properties as well:

0:000>  dt _LDR_DATA_TABLE_ENTRY
ntdll!_LDR_DATA_TABLE_ENTRY
   +0x000 InLoadOrderLinks : _LIST_ENTRY
   +0x010 InMemoryOrderLinks : _LIST_ENTRY
   +0x020 InInitializationOrderLinks : _LIST_ENTRY
   +0x030 DllBase          : Ptr64 Void
   +0x038 EntryPoint       : Ptr64 Void
   +0x040 SizeOfImage      : Uint4B
   +0x048 FullDllName      : _UNICODE_STRING
   +0x058 BaseDllName      : _UNICODE_STRING
   +0x068 FlagGroup        : [4] UChar
   +0x068 Flags            : Uint4B
   +0x068 PackagedBinary   : Pos 0, 1 Bit
   +0x068 MarkedForRemoval : Pos 1, 1 Bit
   +0x068 ImageDll         : Pos 2, 1 Bit
   +0x068 LoadNotificationsSent : Pos 3, 1 Bit
   +0x068 TelemetryEntryProcessed : Pos 4, 1 Bit
   +0x068 ProcessStaticImport : Pos 5, 1 Bit
   +0x068 InLegacyLists    : Pos 6, 1 Bit
   +0x068 InIndexes        : Pos 7, 1 Bit
   +0x068 ShimDll          : Pos 8, 1 Bit
   +0x068 InExceptionTable : Pos 9, 1 Bit
   +0x068 ReservedFlags1   : Pos 10, 2 Bits
   +0x068 LoadInProgress   : Pos 12, 1 Bit
   +0x068 LoadConfigProcessed : Pos 13, 1 Bit
   +0x068 EntryProcessed   : Pos 14, 1 Bit
   +0x068 ProtectDelayLoad : Pos 15, 1 Bit
   +0x068 ReservedFlags3   : Pos 16, 2 Bits
   +0x068 DontCallForThreads : Pos 18, 1 Bit
   +0x068 ProcessAttachCalled : Pos 19, 1 Bit
   +0x068 ProcessAttachFailed : Pos 20, 1 Bit
   +0x068 CorDeferredValidate : Pos 21, 1 Bit
   +0x068 CorImage         : Pos 22, 1 Bit
   +0x068 DontRelocate     : Pos 23, 1 Bit
   +0x068 CorILOnly        : Pos 24, 1 Bit
   +0x068 ChpeImage        : Pos 25, 1 Bit
   +0x068 ChpeEmulatorImage : Pos 26, 1 Bit
   +0x068 ReservedFlags5   : Pos 27, 1 Bit
   +0x068 Redirected       : Pos 28, 1 Bit
   +0x068 ReservedFlags6   : Pos 29, 2 Bits
   +0x068 CompatDatabaseProcessed : Pos 31, 1 Bit
   +0x06c ObsoleteLoadCount : Uint2B
   +0x06e TlsIndex         : Uint2B
   +0x070 HashLinks        : _LIST_ENTRY
   +0x080 TimeDateStamp    : Uint4B
   +0x088 EntryPointActivationContext : Ptr64 _ACTIVATION_CONTEXT
   +0x090 Lock             : Ptr64 Void
   +0x098 DdagNode         : Ptr64 _LDR_DDAG_NODE
   +0x0a0 NodeModuleLink   : _LIST_ENTRY
   +0x0b0 LoadContext      : Ptr64 _LDRP_LOAD_CONTEXT
   +0x0b8 ParentDllBase    : Ptr64 Void
   +0x0c0 SwitchBackContext : Ptr64 Void
   +0x0c8 BaseAddressIndexNode : _RTL_BALANCED_NODE
   +0x0e0 MappingInfoIndexNode : _RTL_BALANCED_NODE
   +0x0f8 OriginalBase     : Uint8B
   +0x100 LoadTime         : _LARGE_INTEGER
   +0x108 BaseNameHashValue : Uint4B
   +0x10c LoadReason       : _LDR_DLL_LOAD_REASON
   +0x110 ImplicitPathOptions : Uint4B
   +0x114 ReferenceCount   : Uint4B
   +0x118 DependentLoadFlags : Uint4B
   +0x11c SigningLevel     : UChar
   +0x120 CheckSum         : Uint4B
   +0x128 ActivePatchImageBase : Ptr64 Void
   +0x130 HotPatchState    : _LDR_HOT_PATCH_STATE

If we continue stepping through the program with WinDbg, we enter RtlCriticalSection. Our simple binary is just a single function call, but we still enter this section because Windows performs various synchronization routines when interacting with other system components and initializing our process. And eventually NtSetEvent, before calling BaseThreadInitThunk.

BaseThreadInitThunk is the entry point for all user-mode threads in Windows. And after this, though, we see Windows query for information about the system by calling RtlGetSuiteMask, RtlGetCurrentServiceSessionId, and then IsTerminalServerCompatible, just before handling our program's headers via RtlImageNtHeader.

Checks to isSystemLUID are made to check whether a given locally unique identifier (LUID) represents the LUID of the Windows system account. These identifiers are used to distinguish and identify users, groups, and privileges in the Windows security model.

After this, Windows finally calls out to generate an appropriate handle to the access token associated with our process via NtOpenProcessToken. An undocumented function in the Windows Native API.

Microsoft Windows then uses a set of obscure undocumented calls that query information about the machine, and eventually perform even more synchronization tasks which involve various file-locks to prevent race conditions during process initialization, before eventually arriving at the core of our program and leading printf statement.

No comments:

Post a Comment