Process Introspection for Fun and Profit


While studying Windows internals for my job, I had to deepen my knowledge of executable loading process, including their memory layout, address relocations and so on.
I came accross the PEB ( process environment block ), a data structure ( mostly undocumented ) that NT systems use internally to handle many aspects of a process, including a list of loaded libraries, environment variables, command line arguments, heap informations, TLS slots and so on.
The interesting fact about the PEB is that can be inspected to obtain those informations without the use of any standard API, thus resulting in an interesting technique to detect bad written virtual machines, emulators and any sort of sandboxing that could be used by a malware analyst or an anti virus product.
Moreover you could check the PEB to detect if a DLL has been injected into your process to perform api hooking.
API hooking softwares usually hooks API such as EnumProcessModules and patch them to hide the presence of the injected module. Inspecting the PEB you will be able to perform the same task only analyzing your address space, thus avoiding API patching.

The address of the structure in our process virtual memory can be obtained with the NtQueryInformationProcess API, once you have the address you can cast it to a pointer to the following type.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
typedef struct _PEB
{
BYTE InheritedAddressSpace;
BYTE ReadImageFileExecOptions;
BYTE BeingDebugged;
BYTE Spare;
DWORD dwMutant;
DWORD dwImageBaseAddress;
PEB_LDR_DATA* LoaderData;
DWORD dwProcessParameters;
DWORD dwSubSystemData;
DWORD dwProcessHeap;
DWORD dwFastPebLock;
DWORD dwFastPebLockRoutine;
DWORD dwFastPebUnlockRoutine;
DWORD dwEnvironmentUpdateCount;
DWORD dwKernelCallbackTable;
DWORD dwEventLogSection;
DWORD dwEventLog;
DWORD dwFreeList;
DWORD dwTlsExpansionCounter;
DWORD dwTlsBitmap;
DWORD dwTlsBitmapBits[0x2];
DWORD dwReadOnlySharedMemoryBase;
DWORD dwReadOnlySharedMemoryHeap;
DWORD dwReadOnlyStaticServerData;
DWORD dwAnsiCodePageData;
DWORD dwOemCodePageData;
DWORD dwUnicodeCaseTableData;
DWORD dwNumberOfProcessors;
DWORD dwNtGlobalFlag;
DWORD dwSpare2;
DWORD dwCriticalSectionTimeout1;
DWORD dwCriticalSectionTimeout2;
DWORD dwHeapSegmentReserve;
DWORD dwHeapSegmentCommit;
DWORD dwHeapDeCommitTotalFreeThreshold;
DWORD dwHeapDeCommitFreeBlockThreshold;
DWORD dwNumberOfHeaps;
DWORD dwMaximumNumberOfHeaps;
DWORD *ProcessHeaps;
DWORD dwGdiSharedHandleTable;
DWORD dwProcessStarterHelper;
DWORD dwGdiDCAttributeList;
DWORD dwLoaderLock;
DWORD dwOSMajorVersion;
DWORD dwOSMinorVersion;
DWORD dwOSBuildNumber;
DWORD dwOSPlatformId;
DWORD dwImageSubSystem;
DWORD dwImageSubSystemMajorVersion;
DWORD dwImageSubSystemMinorVersion;
DWORD GdiHandleBuffer[0x22];
DWORD dwPostProcessInitRoutine;
DWORD dwTlsExpansionBitmap;
BYTE TlsExpansionBitmapBits[0x80];
DWORD dwSessionId;
}
PEB;

As you can see there’s a lot that can be tested to evade sandboxing software due to the fact that most of them won’t emulate a correct PEB, the following source code will demonstrate how to read it and print many informations about the process.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
#include <Windows.h>
#include <stdio.h>
#include <assert.h>

struct PROCESS_BASIC_INFORMATION
{
PVOID Reserved1;
PVOID PebBaseAddress;
PVOID Reserved2[2];
ULONG_PTR UniqueProcessId;
PVOID Reserved3;
};

typedef struct _PEB_LDR_DATA
{
BYTE Reserved1[8];
PVOID Reserved2[3];
LIST_ENTRY InMemoryOrderModuleList;
}
PEB_LDR_DATA ;

typedef struct _TIB
{
PEXCEPTION_REGISTRATION_RECORD* ExceptionList; // FS:0x00
DWORD dwStackBase; // FS:0x04
DWORD dwStackLimit; // FS:0x08
DWORD dwSubSystemTib; // FS:0x0C
DWORD dwFiberData; // FS:0x10
DWORD dwArbitraryUserPointer; // FS:0x14
DWORD dwTIBOffset; // FS:0x18
}
TIB;

typedef struct _PEB
{
BYTE InheritedAddressSpace;
BYTE ReadImageFileExecOptions;
BYTE BeingDebugged;
BYTE Spare;
DWORD dwMutant;
DWORD dwImageBaseAddress;
PEB_LDR_DATA* LoaderData;
DWORD dwProcessParameters;
DWORD dwSubSystemData;
DWORD dwProcessHeap;
DWORD dwFastPebLock;
DWORD dwFastPebLockRoutine;
DWORD dwFastPebUnlockRoutine;
DWORD dwEnvironmentUpdateCount;
DWORD dwKernelCallbackTable;
DWORD dwEventLogSection;
DWORD dwEventLog;
DWORD dwFreeList;
DWORD dwTlsExpansionCounter;
DWORD dwTlsBitmap;
DWORD dwTlsBitmapBits[0x2];
DWORD dwReadOnlySharedMemoryBase;
DWORD dwReadOnlySharedMemoryHeap;
DWORD dwReadOnlyStaticServerData;
DWORD dwAnsiCodePageData;
DWORD dwOemCodePageData;
DWORD dwUnicodeCaseTableData;
DWORD dwNumberOfProcessors;
DWORD dwNtGlobalFlag;
DWORD dwSpare2;
DWORD dwCriticalSectionTimeout1;
DWORD dwCriticalSectionTimeout2;
DWORD dwHeapSegmentReserve;
DWORD dwHeapSegmentCommit;
DWORD dwHeapDeCommitTotalFreeThreshold;
DWORD dwHeapDeCommitFreeBlockThreshold;
DWORD dwNumberOfHeaps;
DWORD dwMaximumNumberOfHeaps;
DWORD *ProcessHeaps;
DWORD dwGdiSharedHandleTable;
DWORD dwProcessStarterHelper;
DWORD dwGdiDCAttributeList;
DWORD dwLoaderLock;
DWORD dwOSMajorVersion;
DWORD dwOSMinorVersion;
DWORD dwOSBuildNumber;
DWORD dwOSPlatformId;
DWORD dwImageSubSystem;
DWORD dwImageSubSystemMajorVersion;
DWORD dwImageSubSystemMinorVersion;
DWORD GdiHandleBuffer[0x22];
DWORD dwPostProcessInitRoutine;
DWORD dwTlsExpansionBitmap;
BYTE TlsExpansionBitmapBits[0x80];
DWORD dwSessionId;
}
PEB;

struct UNICODE_STRING
{
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
};

struct LDR_MODULE
{
LIST_ENTRY InLoadOrderModuleList;
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
PVOID BaseAddress;
PVOID EntryPoint;
ULONG SizeOfImage;
struct UNICODE_STRING FullDllName;
struct UNICODE_STRING BaseDllName;
ULONG Flags;
SHORT LoadCount;
SHORT TlsIndex;
LIST_ENTRY HashTableEntry;
ULONG TimeDateStamp;
};

enum PROCESSINFOCLASS
{
ProcessBasicInformation = 0,
ProcessDebugPort = 7,
ProcessWow64Information = 26,
ProcessImageFileName = 27
};

typedef NTSTATUS (*NtQueryInformationProcess_T)( HANDLE, enum PROCESSINFOCLASS, PVOID, ULONG, PULONG );

int main()
{
HMODULE hNtdll;
NTSTATUS status;
ULONG bytesReturned;
PLIST_ENTRY lEntry;
NtQueryInformationProcess_T pfnNtQueryInformationProcess;
DWORD dwTIB;
struct PROCESS_BASIC_INFORMATION basicInfo;
PEB* peb;
TIB *tib;
struct LDR_MODULE *pLdrModule;

hNtdll = LoadLibrary(L"ntdll.dll");

pfnNtQueryInformationProcess = (NtQueryInformationProcess_T)GetProcAddress(hNtdll, "NtQueryInformationProcess" );

assert(pfnNtQueryInformationProcess != NULL);

status = pfnNtQueryInformationProcess( GetCurrentProcess(), ProcessBasicInformation, &basicInfo, sizeof(basicInfo), &bytesReturned );

assert(status == 0 && bytesReturned == sizeof(basicInfo));

// get our TIB address ( same as mov eax, fs:0x18 )
dwTIB = __readfsdword(0x18);
tib = (TIB *)dwTIB;

// get our PEB address
peb = (PEB *)basicInfo.PebBaseAddress;

printf( "TIB.ExceptionList : %08X\n", tib->ExceptionList );
printf( "TIB.dwStackBase : %08X\n", tib->dwStackBase );
printf( "TIB.dwStackLimit : %08X\n", tib->dwStackLimit );
printf( "TIB.dwSubSystemTib : %08X\n", tib->dwSubSystemTib );
printf( "TIB.dwFiberData : %08X\n", tib->dwFiberData );
printf( "TIB.dwArbitraryUserPointer : %08X\n", tib->dwArbitraryUserPointer );
printf( "TIB.dwTIBOffset : %08X\n", tib->dwTIBOffset );

printf( "\n" );

printf( "PEB.InheritedAddressSpace : %08X\n", peb->InheritedAddressSpace );
printf( "PEB.ReadImageFileExecOptions : %08X\n", peb->ReadImageFileExecOptions );
printf( "PEB.BeingDebugged : %08X\n", peb->BeingDebugged );
printf( "PEB.Spare : %08X\n", peb->Spare );
printf( "PEB.dwMutant : %08X\n", peb->dwMutant );
printf( "PEB.dwImageBaseAddress : %08X\n", peb->dwImageBaseAddress );
printf( "PEB.LoaderData : %08X\n", peb->LoaderData );
printf( "PEB.dwProcessParameters : %08X\n", peb->dwProcessParameters );
printf( "PEB.dwSubSystemData : %08X\n", peb->dwSubSystemData );
printf( "PEB.dwProcessHeap : %08X\n", peb->dwProcessHeap );
printf( "PEB.dwFastPebLock : %08X\n", peb->dwFastPebLock );
printf( "PEB.dwFastPebLockRoutine : %08X\n", peb->dwFastPebLockRoutine );
printf( "PEB.dwFastPebUnlockRoutine : %08X\n", peb->dwFastPebUnlockRoutine );
printf( "PEB.dwEnvironmentUpdateCount : %08X\n", peb->dwEnvironmentUpdateCount );
printf( "PEB.dwKernelCallbackTable : %08X\n", peb->dwKernelCallbackTable );
printf( "PEB.dwEventLogSection : %08X\n", peb->dwEventLogSection );
printf( "PEB.dwEventLog : %08X\n", peb->dwEventLog );
printf( "PEB.dwFreeList : %08X\n", peb->dwFreeList );
printf( "PEB.dwTlsExpansionCounter : %08X\n", peb->dwTlsExpansionCounter );
printf( "PEB.dwTlsBitmap : %08X\n", peb->dwTlsBitmap );
printf( "PEB.dwReadOnlySharedMemoryBase : %08X\n", peb->dwReadOnlySharedMemoryBase );
printf( "PEB.dwReadOnlySharedMemoryHeap : %08X\n", peb->dwReadOnlySharedMemoryHeap );
printf( "PEB.dwReadOnlyStaticServerData : %08X\n", peb->dwReadOnlyStaticServerData );
printf( "PEB.dwAnsiCodePageData : %08X\n", peb->dwAnsiCodePageData );
printf( "PEB.dwOemCodePageData : %08X\n", peb->dwOemCodePageData );
printf( "PEB.dwUnicodeCaseTableData : %08X\n", peb->dwUnicodeCaseTableData );
printf( "PEB.dwNumberOfProcessors : %08X\n", peb->dwNumberOfProcessors );
printf( "PEB.dwNtGlobalFlag : %08X\n", peb->dwNtGlobalFlag );
printf( "PEB.dwSpare2 : %08X\n", peb->dwSpare2 );
printf( "PEB.dwCriticalSectionTimeout1 : %08X\n", peb->dwCriticalSectionTimeout1 );
printf( "PEB.dwCriticalSectionTimeout2 : %08X\n", peb->dwCriticalSectionTimeout2 );
printf( "PEB.dwHeapSegmentReserve : %08X\n", peb->dwHeapSegmentReserve );
printf( "PEB.dwHeapSegmentCommit : %08X\n", peb->dwHeapSegmentCommit );
printf( "PEB.dwHeapDeCommitTotalFreeThreshold : %08X\n", peb->dwHeapDeCommitTotalFreeThreshold );
printf( "PEB.dwHeapDeCommitFreeBlockThreshold : %08X\n", peb->dwHeapDeCommitFreeBlockThreshold );
printf( "PEB.dwNumberOfHeaps : %08X\n", peb->dwNumberOfHeaps );
printf( "PEB.dwMaximumNumberOfHeaps : %08X\n", peb->dwMaximumNumberOfHeaps );
printf( "PEB.*ProcessHeaps : %08X\n", peb->ProcessHeaps );
printf( "PEB.dwGdiSharedHandleTable : %08X\n", peb->dwGdiSharedHandleTable );
printf( "PEB.dwProcessStarterHelper : %08X\n", peb->dwProcessStarterHelper );
printf( "PEB.dwGdiDCAttributeList : %08X\n", peb->dwGdiDCAttributeList );
printf( "PEB.dwLoaderLock : %08X\n", peb->dwLoaderLock );
printf( "PEB.dwOSMajorVersion : %08X\n", peb->dwOSMajorVersion );
printf( "PEB.dwOSMinorVersion : %08X\n", peb->dwOSMinorVersion );
printf( "PEB.dwOSBuildNumber : %08X\n", peb->dwOSBuildNumber );
printf( "PEB.dwOSPlatformId : %08X\n", peb->dwOSPlatformId );
printf( "PEB.dwImageSubSystem : %08X\n", peb->dwImageSubSystem );
printf( "PEB.dwImageSubSystemMajorVersion : %08X\n", peb->dwImageSubSystemMajorVersion );
printf( "PEB.dwImageSubSystemMinorVersion : %08X\n", peb->dwImageSubSystemMinorVersion );
printf( "PEB.dwPostProcessInitRoutine : %08X\n", peb->dwPostProcessInitRoutine );
printf( "PEB.dwTlsExpansionBitmap : %08X\n", peb->dwTlsExpansionBitmap );
printf( "PEB.dwSessionId : %08X\n", peb->dwSessionId );

printf( "\n" );

// get the main module
pLdrModule = (struct LDR_MODULE*)peb->LoaderData->InMemoryOrderModuleList.Flink;

printf
(
"%-17S base@ %10p size: %10x flags: %12x\n",
pLdrModule->FullDllName.Buffer,
pLdrModule->BaseAddress,
pLdrModule->SizeOfImage,
pLdrModule->Flags
);

// loop each loaded module
for( lEntry = peb->LoaderData->InMemoryOrderModuleList.Flink->Flink; pLdrModule->BaseAddress != 0; lEntry = lEntry->Flink )
{
pLdrModule = (struct LDR_MODULE*)lEntry;

printf
(
"%-17S base@ %10p size: %10x flags: %12x\n",
pLdrModule->FullDllName.Buffer,
pLdrModule->BaseAddress,
pLdrModule->SizeOfImage,
pLdrModule->Flags
);
}

FreeLibrary( hNtdll );

return 0;
}
Become a Patron!