Home PEB For Malware Development
Post
Cancel

PEB For Malware Development

PEB

PEB stands for Process Enviornment Block and is a structure in Windows used to hold critical information about a process. It has many fields which can be used for malicious purposes and in favor of malware developers.

All the codes used in this article are included in https://github.com/arimaqz/peb-malware-development.

Table of Contents

PEB structure

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
ntdll!_PEB
   +0x000 InheritedAddressSpace : UChar
   +0x001 ReadImageFileExecOptions : UChar
   +0x002 BeingDebugged    : UChar
   +0x003 BitField         : UChar
   +0x003 ImageUsesLargePages : Pos 0, 1 Bit
   +0x003 IsProtectedProcess : Pos 1, 1 Bit
   +0x003 IsImageDynamicallyRelocated : Pos 2, 1 Bit
   +0x003 SkipPatchingUser32Forwarders : Pos 3, 1 Bit
   +0x003 IsPackagedProcess : Pos 4, 1 Bit
   +0x003 IsAppContainer   : Pos 5, 1 Bit
   +0x003 IsProtectedProcessLight : Pos 6, 1 Bit
   +0x003 IsLongPathAwareProcess : Pos 7, 1 Bit
   +0x004 Padding0         : [4] UChar
   +0x008 Mutant           : Ptr64 Void
   +0x010 ImageBaseAddress : Ptr64 Void
   +0x018 Ldr              : Ptr64 _PEB_LDR_DATA
   +0x020 ProcessParameters : Ptr64 _RTL_USER_PROCESS_PARAMETERS
   +0x028 SubSystemData    : Ptr64 Void
   +0x030 ProcessHeap      : Ptr64 Void
   +0x038 FastPebLock      : Ptr64 _RTL_CRITICAL_SECTION
   +0x040 AtlThunkSListPtr : Ptr64 _SLIST_HEADER
   +0x048 IFEOKey          : Ptr64 Void
   +0x050 CrossProcessFlags : Uint4B
   +0x050 ProcessInJob     : Pos 0, 1 Bit
   +0x050 ProcessInitializing : Pos 1, 1 Bit
   +0x050 ProcessUsingVEH  : Pos 2, 1 Bit
   +0x050 ProcessUsingVCH  : Pos 3, 1 Bit
   +0x050 ProcessUsingFTH  : Pos 4, 1 Bit
   +0x050 ProcessPreviouslyThrottled : Pos 5, 1 Bit
   +0x050 ProcessCurrentlyThrottled : Pos 6, 1 Bit
   +0x050 ProcessImagesHotPatched : Pos 7, 1 Bit
   +0x050 ReservedBits0    : Pos 8, 24 Bits
   +0x054 Padding1         : [4] UChar
   +0x058 KernelCallbackTable : Ptr64 Void
   +0x058 UserSharedInfoPtr : Ptr64 Void
   +0x060 SystemReserved   : Uint4B
   +0x064 AtlThunkSListPtr32 : Uint4B
   +0x068 ApiSetMap        : Ptr64 Void
   +0x070 TlsExpansionCounter : Uint4B
   +0x074 Padding2         : [4] UChar
   +0x078 TlsBitmap        : Ptr64 _RTL_BITMAP
   +0x080 TlsBitmapBits    : [2] Uint4B
   +0x088 ReadOnlySharedMemoryBase : Ptr64 Void
   +0x090 SharedData       : Ptr64 Void
   +0x098 ReadOnlyStaticServerData : Ptr64 Ptr64 Void
   +0x0a0 AnsiCodePageData : Ptr64 Void
   +0x0a8 OemCodePageData  : Ptr64 Void
   +0x0b0 UnicodeCaseTableData : Ptr64 Void
   +0x0b8 NumberOfProcessors : Uint4B
   +0x0bc NtGlobalFlag     : Uint4B
   +0x0c0 CriticalSectionTimeout : _LARGE_INTEGER
   +0x0c8 HeapSegmentReserve : Uint8B
   +0x0d0 HeapSegmentCommit : Uint8B
   +0x0d8 HeapDeCommitTotalFreeThreshold : Uint8B
   +0x0e0 HeapDeCommitFreeBlockThreshold : Uint8B
   +0x0e8 NumberOfHeaps    : Uint4B
   +0x0ec MaximumNumberOfHeaps : Uint4B
   +0x0f0 ProcessHeaps     : Ptr64 Ptr64 Void
   +0x0f8 GdiSharedHandleTable : Ptr64 Void
   +0x100 ProcessStarterHelper : Ptr64 Void
   +0x108 GdiDCAttributeList : Uint4B
   +0x10c Padding3         : [4] UChar
   +0x110 LoaderLock       : Ptr64 _RTL_CRITICAL_SECTION
   +0x118 OSMajorVersion   : Uint4B
   +0x11c OSMinorVersion   : Uint4B
   +0x120 OSBuildNumber    : Uint2B
   +0x122 OSCSDVersion     : Uint2B
   +0x124 OSPlatformId     : Uint4B
   +0x128 ImageSubsystem   : Uint4B
   +0x12c ImageSubsystemMajorVersion : Uint4B
   +0x130 ImageSubsystemMinorVersion : Uint4B
   +0x134 Padding4         : [4] UChar
   +0x138 ActiveProcessAffinityMask : Uint8B
   +0x140 GdiHandleBuffer  : [60] Uint4B
   +0x230 PostProcessInitRoutine : Ptr64     void 
   +0x238 TlsExpansionBitmap : Ptr64 _RTL_BITMAP
   +0x240 TlsExpansionBitmapBits : [32] Uint4B
   +0x2c0 SessionId        : Uint4B
   +0x2c4 Padding5         : [4] UChar
   +0x2c8 AppCompatFlags   : _ULARGE_INTEGER
   +0x2d0 AppCompatFlagsUser : _ULARGE_INTEGER
   +0x2d8 pShimData        : Ptr64 Void
   +0x2e0 AppCompatInfo    : Ptr64 Void
   +0x2e8 CSDVersion       : _UNICODE_STRING
   +0x2f8 ActivationContextData : Ptr64 _ACTIVATION_CONTEXT_DATA
   +0x300 ProcessAssemblyStorageMap : Ptr64 _ASSEMBLY_STORAGE_MAP
   +0x308 SystemDefaultActivationContextData : Ptr64 _ACTIVATION_CONTEXT_DATA
   +0x310 SystemAssemblyStorageMap : Ptr64 _ASSEMBLY_STORAGE_MAP
   +0x318 MinimumStackCommit : Uint8B
   +0x320 SparePointers    : [2] Ptr64 Void
   +0x330 PatchLoaderData  : Ptr64 Void
   +0x338 ChpeV2ProcessInfo : Ptr64 _CHPEV2_PROCESS_INFO
   +0x340 AppModelFeatureState : Uint4B
   +0x344 SpareUlongs      : [2] Uint4B
   +0x34c ActiveCodePage   : Uint2B
   +0x34e OemCodePage      : Uint2B
   +0x350 UseCaseMapping   : Uint2B
   +0x352 UnusedNlsField   : Uint2B
   +0x358 WerRegistrationData : Ptr64 Void
   +0x360 WerShipAssertPtr : Ptr64 Void
   +0x368 EcCodeBitMap     : Ptr64 Void
   +0x370 pImageHeaderHash : Ptr64 Void
   +0x378 TracingFlags     : Uint4B
   +0x378 HeapTracingEnabled : Pos 0, 1 Bit
   +0x378 CritSecTracingEnabled : Pos 1, 1 Bit
   +0x378 LibLoaderTracingEnabled : Pos 2, 1 Bit
   +0x378 SpareTracingBits : Pos 3, 29 Bits
   +0x37c Padding6         : [4] UChar
   +0x380 CsrServerReadOnlySharedMemoryBase : Uint8B
   +0x388 TppWorkerpListLock : Uint8B
   +0x390 TppWorkerpList   : _LIST_ENTRY
   +0x3a0 WaitOnAddressHashTable : [128] Ptr64 Void
   +0x7a0 TelemetryCoverageHeader : Ptr64 Void
   +0x7a8 CloudFileFlags   : Uint4B
   +0x7ac CloudFileDiagFlags : Uint4B
   +0x7b0 PlaceholderCompatibilityMode : Char
   +0x7b1 PlaceholderCompatibilityModeReserved : [7] Char
   +0x7b8 LeapSecondData   : Ptr64 _LEAP_SECOND_DATA
   +0x7c0 LeapSecondFlags  : Uint4B
   +0x7c0 SixtySecondEnabled : Pos 0, 1 Bit
   +0x7c0 Reserved         : Pos 1, 31 Bits
   +0x7c4 NtGlobalFlag2    : Uint4B
   +0x7c8 ExtendedFeatureDisableMask : Uint8B

PEB Structure holds many fields but for this article we’ll be using a few of them that is of use to us.

Accessing Process PEB Structure

To access the a process’s PEB structure you can use two methods.

GS and FS registers

PEB structure is at offset 0x30 in fs register for 32-bit systems and at offset 0x60 in gs register for 64-bit systems:

1
2
3
4
5
6
7
PPEB getLocalPeb() {
#ifdef _WIN64
	return (PPEB)(__readgsqword(0x60));
#else
	return (PPEB)(__readfsdword(0x30));
#endif
}

Note: this method cannot be used to read the PEB structure of a remote process. That can be done using the method below.

WINAPI

We can access the PEB structure using NtQueryInformationProcess and ReadProcessMemory WINAPIs both in local and remote processes:

1
2
3
4
5
6
7
8
9
10
PPEB getPebExtended() {
	PROCESS_BASIC_INFORMATION pbi = { 0 };
	PEB peb = { 0 };
	RtlSecureZeroMemory(&peb, sizeof(PEB));
	RtlSecureZeroMemory(&pbi, sizeof(PROCESS_BASIC_INFORMATION));
	pNtQueryInformationProcess ntQueryInformationProcess = (pNtQueryInformationProcess)GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtQueryInformationProcess");
	ntQueryInformationProcess(GetCurrentProcess(), ProcessBasicInformation, &pbi, sizeof(PROCESS_BASIC_INFORMATION), NULL);
	ReadProcessMemory(GetCurrentProcess(), pbi.PebBaseAddress, &peb, sizeof(PEB), NULL); 
	return &peb;
}

Information Gathering

PEB Fields can be used to gather some basic information about the system.

Number of Processors

There is a field called NumberOfProcesses at offset 0x0b8 which holds the number of logical processes:

1
2
3
4
5
DWORD retrieveNumberOfProcesses() {
	PPEB pPeb = getLocalPeb();
	DWORD numberOfProcesses = *(PDWORD)((PBYTE)pPeb + 0x0b8);
	return numberOfProcesses;
}

OS Version

There are 3 fields related to OS version named OSMajorVersion, OSMinorVersion and OSBuildNumber at offsets 0x0a4/0x118, 0x0a8/0x11c and 0x0ac/0x120 respectively:

1
2
3
4
5
6
7
8
9
10
11
12
VOID retrieveOSVersion(OUT PDWORD pMajorVersion, OUT PDWORD pMinorVersion, OUT PDWORD pBuildNumber) {
	PPEB pPeb = getLocalPeb();
#ifdef _WIN64
	*pMajorVersion = *(PDWORD)((PBYTE)pPeb + 0x118);
	*pMinorVersion = *(PDWORD)((PBYTE)pPeb + 0x11c);
	*pBuildNumber = *(PDWORD)((PBYTE)pPeb + 0x120);
#else
	*pMajorVersion = *(PDWORD)((PBYTE)pPeb + 0x0a4);
	*pMinorVersion = *(PDWORD)((PBYTE)pPeb + 0x0a8);
	*pBuildNumber = *(PDWORD)((PBYTE)pPeb + 0x0ac);
#endif
}

Anti Debugging

PEB fields can be checked for the presence of a debugger.

BeingDebugged

there is a field named BeingDebugged which is set to 1 when a debugger is present:

1
2
3
4
BOOL isBeingDebuggedSet() {
	PPEB pPeb = getLocalPeb();
	if (pPeb->BeingDebugged == 1) return true;
}

NtGlobalFlag

There is another field which can be checked named NtGlobalFlag at offset 0x68/0xBC which is set to 0x70 if there is a debugger but note that it’s set to that value only if the debugger itself launched that process.

Value 0x70 is set because it’s the bitwise OR of the following values:

1
2
3
#define FLG_HEAP_ENABLE_TAIL_CHECK 0x10
#define FLG_HEAP_ENABLE_TREE_CHECK 0x20
#define FLG_HEAP_VALIDATE_PARAMETERS 0x40
1
2
3
4
5
6
7
8
9
10
11
12
BOOL isNtGlobalFlagSet() {
	#define FLG_HEAP_ENABLE_TAIL_CHECK 0x10
	#define FLG_HEAP_ENABLE_TREE_CHECK 0x20
	#define FLG_HEAP_VALIDATE_PARAMETERS 0x40
	PPEB pPeb = getLocalPeb();
#ifdef _WIN64
	DWORD ntGlobalFlag = *(PDWORD)((PBYTE)pPeb + 0xBC);
#else 
	DWORD ntGlobalFlag = *(PDWORD)((PBYTE)pPeb + 0x68);
#endif
	if (ntGlobalFlag == FLG_HEAP_ENABLE_TAIL_CHECK | FLG_HEAP_ENABLE_TREE_CHECK | FLG_HEAP_VALIDATE_PARAMETERS) return true;
}

Manipulation

We can also manipulate and change some of the fields in the PEB structure to try some techniques like argument spoofing.

ProcessParameters

This field is a structure of type RTL_USER_PROCESS_PARAMATERES:

1
2
3
4
5
6
7
8
9
10
ntdll!_RTL_USER_PROCESS_PARAMETERS
    ...
   +0x038 CurrentDirectory : _CURDIR
   ...
   +0x060 ImagePathName    : _UNICODE_STRING
   +0x070 CommandLine      : _UNICODE_STRING
    ...
ntdll!_CURDIR
   +0x000 DosPath          : _UNICODE_STRING
   +0x010 Handle           : Ptr64 Void

These fields’ usage are shown below: parameters So by changing those fields’ values we can spoof these when viewed in an app like procmon or procexp.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
VOID spoofLocal() {
	PPEB pPeb = getLocalPeb();
	const wchar_t spoof[] = L"C:\\Windows\\System32\\notepad.exe";
	const wchar_t spoofDir[] = L"C:\\Windows\\System32";
	USHORT spoodSize = wcslen(spoof) * sizeof(wchar_t);
	USHORT spoodDirSize = wcslen(spoofDir) * sizeof(wchar_t);
	pPeb->ProcessParameters->CommandLine.Buffer = (PWSTR)spoof;
	pPeb->ProcessParameters->CommandLine.Length = spoodSize;
	pPeb->ProcessParameters->ImagePathName.Buffer = (PWSTR)spoof;
	pPeb->ProcessParameters->ImagePathName.Length = spoodSize;
	PCURDIR pCurDir = (PCURDIR)((PBYTE)pPeb->ProcessParameters + 0x038);
	pCurDir->DosPath.Buffer = (PWSTR)spoofDir;
	pCurDir->DosPath.Length = spoodDirSize;
}

And the result is:

spoofed

But notice that field Parent is still set to VsDebugConsole.exe when ran in Visual Studio, To change that we have to use the PPID spoofing technique in conjuction with that technique. But PPID spoofing works when creating new processes so we have to manipulate remote process’s PEB this time:

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
VOID spoofRemote() {
	//PPID spoofing
	SIZE_T sAttrList = NULL;
	STARTUPINFOEXA siEx = { 0 };
	PROCESS_INFORMATION pi = { 0 };
	RtlSecureZeroMemory(&siEx, sizeof(STARTUPINFOEXA));
	RtlSecureZeroMemory(&pi, sizeof(PROCESS_INFORMATION));
	siEx.StartupInfo.cb = sizeof(STARTUPINFOEXA);
	HANDLE hParentProcess = OpenProcess(MAXIMUM_ALLOWED, FALSE, <pid>);
	InitializeProcThreadAttributeList(NULL, 1, NULL, &sAttrList);
	siEx.lpAttributeList = (PPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sAttrList);
	InitializeProcThreadAttributeList(siEx.lpAttributeList, 1, NULL, &sAttrList);
	UpdateProcThreadAttribute(siEx.lpAttributeList, NULL, PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, &hParentProcess, sizeof(HANDLE), NULL, NULL);
	CreateProcessA(NULL, (LPSTR)"C:\\windows\\system32\\notepad.exe", NULL, NULL, FALSE, EXTENDED_STARTUPINFO_PRESENT, NULL, NULL, &siEx.StartupInfo, &pi);

	// manipulate PEB
	PPEB pPeb = getPebExtended();

	const wchar_t spoof[] = L"C:\\Windows\\System32\\calc.exe";
	const wchar_t spoofDir[] = L"C:\\Windows\\System32	";
	USHORT spoodSize = wcslen(spoof) * sizeof(wchar_t);
	USHORT spoodDirSize = wcslen(spoofDir) * sizeof(wchar_t);
	//we have to store 'spoof' in target process address space to be able to use it
	LPVOID remoteCommandLine = VirtualAllocEx(pi.hProcess, NULL, spoodSize, MEM_COMMIT, PAGE_READWRITE);
	WriteProcessMemory(pi.hProcess, remoteCommandLine, spoof, spoodSize, NULL);
	WriteProcessMemory(pi.hProcess, (char*)pPeb->ProcessParameters + offsetof(RTL_USER_PROCESS_PARAMETERS, CommandLine.Buffer), &remoteCommandLine, sizeof(LPVOID), NULL);
	WriteProcessMemory(pi.hProcess, (char*)pPeb->ProcessParameters + offsetof(RTL_USER_PROCESS_PARAMETERS, ImagePathName.Buffer), &remoteCommandLine, sizeof(LPVOID), NULL);

	LPVOID remoteCurrentDirectory = VirtualAllocEx(pi.hProcess, NULL, spoodDirSize, MEM_COMMIT, PAGE_READWRITE);
	WriteProcessMemory(pi.hProcess, remoteCurrentDirectory, spoofDir, spoodDirSize, NULL);
	PCURDIR pCurDir = (PCURDIR)((PBYTE)pPeb->ProcessParameters + 0x038);
	WriteProcessMemory(pi.hProcess, (char*)pCurDir + offsetof(CURDIR, DosPath.Buffer), &remoteCurrentDirectory, sizeof(LPVOID), NULL);

}

remote spoof

Modules

PEB can also be used to list and return address of loaded modules.

List of Modules

If you recall there was a field named Ldr. This field has a struture of type PEB_LDR_DATA:

1
2
3
4
5
6
7
8
9
10
ntdll!_PEB_LDR_DATA
   +0x000 Length           : Uint4B
   +0x004 Initialized      : UChar
   +0x008 SsHandle         : Ptr64 Void
   +0x010 InLoadOrderModuleList : _LIST_ENTRY
   +0x020 InMemoryOrderModuleList : _LIST_ENTRY
   +0x030 InInitializationOrderModuleList : _LIST_ENTRY
   +0x040 EntryInProgress  : Ptr64 Void
   +0x048 ShutdownInProgress : UChar
   +0x050 ShutdownThreadId : Ptr64 Void

And in PEB_LDR_DATA structure we have yet another field of type LIST_ENTRY which is a doubly-linked list named InMemoryOrderModuleList containing a list of loaded modules in the process address space and has two fields:

1
2
3
ntdll!_LIST_ENTRY
   +0x000 Flink            : Ptr64 _LIST_ENTRY
   +0x008 Blink            : Ptr64 _LIST_ENTRY

These are used to navigate around in the list. And each element in that list is of type LDR_DATA_TABLE_ENTRY:

1
2
3
4
5
6
ntdll!_LDR_DATA_TABLE_ENTRY
    ...
   +0x020 InInitializationOrderLinks : _LIST_ENTRY
    ...
   +0x048 FullDllName      : _UNICODE_STRING
    ...

Now we don’t need most of this structure therefore I did not include them here. We are interested in FullDllName field for this section, which as the name suggests, Contains the DLL name:

1
2
3
4
5
6
7
8
9
10
VOID listAllModules() {
	PPEB pPeb = getLocalPeb();
	PEB_LDR_DATA* pLdr = (PEB_LDR_DATA*)pPeb->Ldr;
	LIST_ENTRY* pModuleList = (LIST_ENTRY*)&pLdr->InMemoryOrderModuleList;
	LIST_ENTRY* pFirstEntry = pModuleList->Flink;
	for (LIST_ENTRY* pListEntry = pFirstEntry; pListEntry != pModuleList; pListEntry = pListEntry->Flink) {
		LDR_DATA_TABLE_ENTRY* pEntry = (LDR_DATA_TABLE_ENTRY*)pListEntry;
		printf("%S\n", pEntry->FullDllName.Buffer);
	}
}

Custom GetModuleHandle

You can also implement your own GetModuleHandle using the function explained above. This time we’ll also be using InInitializationOrderLinks from which we can get the module address:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
HMODULE customGetModuleHandle(const wchar_t* szDllName) {
	PPEB pPeb = getLocalPeb();
	PEB_LDR_DATA* pLdr = (PEB_LDR_DATA*)pPeb->Ldr;
	LIST_ENTRY* pModuleList = (LIST_ENTRY*)&pLdr->InMemoryOrderModuleList;
	LIST_ENTRY* pFirstEntry = pModuleList->Flink;
	for (LIST_ENTRY* pListEntry = pFirstEntry; pListEntry != pModuleList; pListEntry = pListEntry->Flink) {
		LDR_DATA_TABLE_ENTRY* pEntry = (LDR_DATA_TABLE_ENTRY*)pListEntry;
		if (lstrcmpW(szDllName, pEntry->FullDllName.Buffer) == 0) {
			LIST_ENTRY* pInInitializationOrderLinks = (LIST_ENTRY*)((PBYTE)pEntry + 0x020); //0x020 is offset of InInitializationOrderLinks
			return (HMODULE)pInInitializationOrderLinks->Flink;
			//return (HMODULE)pEntry->Reserved2[0]
		}
	}
}

InInitializationOrderLinks is also called Reserved2 in LDR_DATA_TABLE_ENTRY structure in winternl.h header file so we can use

1
hModule = (HMODULE)pEntry->Reserved2[0];

as well.

This post is licensed under CC BY 4.0 by the author.