Understanding the Process Environment Block (PEB) for Malware Analysis
Analysis of the Key PEB Techniques for Malware Analysis and Reverse Engineering
Table of Contents
• 1. Introduction
• 2. Overview of the Process Environment Block (PEB)
• 3. Overview of the Thread Environment Block (TEB)
• 4. Accessing the Thread Environment Block (TEB)
• 5. Accessing the Process Environment Block (PEB)
• 6. Key PEB Fields Exploited by Malware Developers
◦ 6.1. Anti-Debugging Fields
— 6.1.1. BeingDebugged
— 6.1.2. NtGlobalFlag
— 6.1.3. ProcessHeap
— 6.1.4. Other Anti-Debugging & VM Detection Fields
◦ 6.2. ImageBaseAddress
— 6.2.1. Process Hollowing
◦ 6.3. Ldr
— 6.3.1. StealC API Hashing Analysis
◦ 6.4. ProcessParameters
— 6.4.1. Command Line Manipulation
— 6.4.2. BlackMatter Ransomware UAC Bypass Analysis
• 7. Conclusion
• References
• Samples/IOCs
1. Introduction
Hi everyone, in my early stages of malware analysis/RE, there were some topics I didn’t truly understand but I presumed I did. I only realized what I was missing afterward.
PEB was one of the topics I wish I had learned more about, instead of just memorizing it the popular way, which is “FS:30 it’s PEB, it should be anti-debug.” So, in case there are others here like I was, this blog post can be helpful for you. I aim to create a baseline of practical knowledge about PEB from the aspect of malware analysis. Nonetheless, I will provide links for further reading. So, let’s begin with some basics.
2. Overview of the Process Environment Block (PEB)
In simple terms, PEB is a structure, an “information board”, that holds some data about a process for the Operating System. [1]
The PEB has some beneficial use cases for both the OS and the application itself. For the OS, PEB provides a standardized way to access important details about a process. It includes runtime data, loaded modules (DLLs), environment variables, and more.
For the application itself, PEB also allows the application to access these data. Since our favorite applications are malware, our journey starts here. That feature allows the application (malware) to know what’s going on, where it is, how it is executed, etc. It allows the malware to manipulate itself with different techniques with these data at runtime. We will touch on some of the common techniques.
Also keep in mind that PEB specifically exists in Windows; the closest structure in Linux is “task_struct”. [2]
The !peb
command in WinDbg displays a summary of the Process Environment Block (PEB) for the debugged process. Figure 1 shows an executable's PEB summary, also illustrating the typical structure and contents of a PEB.
Also worth mentioning is that PEB can be used by a process to access the other process’s metadata. Formal inter-process communications can take place with pipes, message queues, shared memory, and so on.[3] However, thanks to the PEB, a process can simply read another process’s metadata just from a structure. Still, this doesn’t make inter-process communication obsolete, since PEB doesn’t include user-defined data meant for sharing between processes.
3. Overview of the Thread Environment Block (TEB)
PEB and its content can be accessed from the user mode. However, before accessing the PEB, we have to first access the Thread Environment Block (TEB), which serves as an information board like PEB, but for the thread this time.
As the program code is running in the thread, in other words, the “inner layer”, we have to access the process’s PEB, the “outer layer,” through the TEB. Hence, we can say that TEB is a gateway to reach PEB in our case as illustrated in Figure 2.
Because the Thread Environment Block (TEB) evolved from the Thread Information Block (TIB), the TEB sometimes referred to as the TIB. However, even though the TEB is based on the TIB and includes similar information, it is a more extended and different structure. The TIB was used prior to Windows NT versions, while the TEB has been used from Windows NT through Windows 11.
4. Accessing the Thread Environment Block (TEB)
So, how do we access the TEB? The TEB and PEB cannot be referenced directly in high-level code; instead, we need registers and pointers to point to their addresses. Our famous FS and GS registers aid us here. In Windows, the FS and GS registers are used to access the TEB. One is for 32-bit systems, while the other is for 64-bit systems, respectively. Let’s take a look at what the TEB structure contains and how we can access the PEB through the TEB.
Figure 3 shows a part of a well-described table taken from Wikipedia, based on Wine’s work on Windows Internals. [4][5] The first column, “Pointer,” indicates that the corresponding offset given to the FS or GS register provides the address of the data stored in memory as described in the “Description” column.
Addresses stored in the TEB are not limited to these. For a full list, you can refer to the reference given for the table. TEB and similar Windows Internal structures are not fully documented officially by Microsoft due to security reasons. So we refer to the researches here.
Also here is a part of an executable’s TEB structure shown in Figure 4. Thanks to the WinDbg, we can see the summary of the TEB structure in the left pane with the !teb
command and the actual TEB structure in the right pane with the dt _TEB <TEB_ADDRESS>
command. We can see the located PEB address in both panes.
5. Accessing the Process Environment Block (PEB)
There are two common ways to access the PEB in the wild:
You can check the Figure 3 to remind the corresponding offsets.
1- Direct Access:
Directly reach and store the PEB address in the eax
register by using the offset fs:[30h]
(as illustrated in Figure 5).
2- Indirect Access:
First, access the TEB base address by using the offset fs:[18h]
, then obtain the PEB’s address by adding the offset 0x30h
(as illustrated in Figure 6).
Why is there a Indirect Access method when we can directly access the PEB as in the first method? The reason lies in code stability and optimization concerns (for more details, you can check out this great blog post). [6]
Once the PEB address is obtained and stored in the eax
register, we can then access the PEB’s fields through offsets by adding them to the register that stores the PEB’s base address.
Figure 7 shows a part of the offsets and the corresponding fields found in the PEB structure.
While the PEB contains many fields beyond those mentioned, we will focus on the key ones relevant to malware analysis and reverse engineering.
6. Key PEB Fields Exploited by Malware Developers
Here are the most common PEB fields frequently used by malware developers, along with brief descriptions of each:
0x002 BeingDebugged, Anti-Debug
0x068 NtGlobalFlag, Anti-Debug
0x018 ProcessHeap, Anti-Debug
0x00c Ldr, API Hashing
0x008 ImageBaseAddress, Process Hollowing
0x010 ProcessParameters, UAC Bypass
While there are more PEB fields that malware may use, these are the most frequently encountered. Let’s examine each field in detail with practical examples.
6.1. Anti-Debugging Fields
The most common fields that serve anti-debug purposes are BeingDebugged, NtGlobalFlag, and ProcessHeap fields in the PEB.
6.1.1. BeingDebugged
The BeingDebugged flag in the PEB also triggers the IsDebuggerPresent()
and NtQueryProcessInformation()
functions, which are also commonly utilized. However, some malware developers still prefer to check the BeingDebugged field manually.
Simply, the BeingDebugged flag is set to 0x1
if the process was created with the debugger. Otherwise it is set to 0x0
.
Note that the BeingDebugged flag is only affected by the user-mode debuggers; kernel-mode debuggers don’t affect this flag.
In the code snippet shown in Figure 8:
- First, the PEB is accessed using the offset
0x30h
. - Then, the corresponding offset
0x2
for the BeingDebugged field is added to the PEB’s base address.
As a result, the BeingDebugged field is accessed, and the flag value is retrieved. The flag is set to 0x1
when the program is executed with a debugger, and 0x0
during normal execution.
6.1.2. NtGlobalFlag
The NtGlobalFlag is set to 0x0
by default and it relies on a number of global flags. When a process is created with a debugger, the following global flags are set to the values indicated in parentheses:
- FLG_HEAP_ENABLE_TAIL_CHECK (0x10)
- FLG_HEAP_ENABLE_FREE_CHECK (0x20)
- FLG_HEAP_VALIDATE_PARAMETERS (0x40)
These flag settings result in NtGlobalFlag being set to 0x70
when a process is created with a debugger. If a debugger is not attached, or attached to a process after it has been created, these flags are not affected, and NtGlobalFlag remains 0x0
.
In the code snippet illustrated in Figure 9, the offset of the NtGlobalFlag field (0x68
) is added to the PEB’s base address. This results in a value of 0x70
when the program is run with a debugger and a value of 0x0
under normal execution circumstances.
6.1.3. ProcessHeap
The base address of the heap can be obtained from the ProcessHeap field of the PEB. As like PEB, the heap is a structure comprising records and fields.
Debugger usage on the program affects two fields of the heap called Flags, and ForceFlags. These flags are normally used to tell the kernel whether the heap was created within a debugger. As a matter of course, malware developers use it for anti-debug purposes.
Accessing the Flags and the ForceFlags fields through the PEB is illustrated in Figure 10.
The values of these flags can vary between different Windows versions and architectures. For more information about these flags, you can refer to this resource. The key point is to check whether the results are zero. If they are not, the program is being debugged.
In the code snippet illustrated in Figure 11:
- The corresponding offset for the ProcessHeap field
0x18
is added to the PEB’s base address. - After Heap was accessed through PEB,
0x40
and0x44
offsets added to the heap’s base adddress seperately to check Flags and ForceFlags fields.
As a result, 0x0
result is displayed under the normal execution circumstances, and other results besides 0x0
displayed when the program is under the debugger.
6.1.4. Other Anti-Debugging & VM Detection Fields
The mentioned fields are not half of it but the most common ones are widely used for anti-debug/VM detection purposes.
Also, not only are some of the fields accessed directly, but they are also used as parameters or dependencies in various API functions.
For instance, the NumberOfProcessors and the ProcessParameters fields in the PEB can be checked directly for VM detection. However, a more common approach involves using API functions like GetSystemInfo()
and NtQuerySystemInformation()
for VM detection, which already rely on several parameters, including the NumberOfProcessors and ProcessParameters field.
6.2. ImageBaseAdress
Each time a process starts, the OS decides where it should be loaded in virtual memory to avoid any conflicts in memory, whether the process can be loaded into the desired location or if it needs to be rebased. This process sometimes causes in a rebase in memory.
Thus, the ImageBaseAdress field in the PEB provides the executable image’s base address (Figure 12) to the OS, as well as to the other processes. Our processes, malware of course, take advantages of this field too.
6.2.1. Process Hollowing
Because this field provides a process’s base address in memory to other processes, malware developers exploit this field to locate and manipulate a process’s memory region. One of the techniques used here known as Process Injection, specifically Process Hollowing.
In summary; malware, rather than executing the malicious code itself, reaches/creates another (legal) process and injects its malicious code to it to perform its functions under the legal process resulting in bypassing firewall/IPS systems. Step by step it’s performed in:
- Malware starts a legitimate process in suspended mode to load its executable section into memory
- Malware uses ImageBaseAddress field to find the base address of the suspended legit process to unmap/hollow out its executable section to inject its malicious code
- After hollowing out the legit process, it deallocates and reallocates the memory region with read, write, and execute permissions
- Then rewrites the legit file’s PE header to inject the code into the newly allocated memory
- Lastly, changes the entry point to the malicious code’s entry point and runs its malicious image under the legal process.
6.3. Ldr
The Ldr, which stands for Image Loader, not only aids the OS in preparing the execution environment for the execution but also maintains executable optimization for OS. It parses the Import Adress Table (IAT), loads/unloads modules (DLLs) at runtime on demand, and so on. [7]
In the context of malware, the Ldr field is one of the main reasons for accessing the PEB. Since its implementation varies among the malware, that makes it one of the most powerful anti-analysis techniques and popular between the malware developers.
Nevertheless, the main idea behind it stays still and it’s commonly used for API hashing, to hinder API calls. In the following subsection, we will examine one of its implementation in the StealC malware family to brighten up the Ldr more.
Briefly, the Ldr (Loader) field in the PEB points to the PEB_LDR_DATA structure. As each process has a list of loaded modules (DLLs) to be executed and run properly, this structure handles these loaded modules. [8]
There are three fields in PEB_LDR_DATA that help us to access these module lists which indicated in Figure 13. Although they both allow us to access loaded modules and both used by malware developers, there are minor differences to aid in different cases:
- InLoadOrderModuleList: Maintains the load order, aids in debugging module dependency issues.
- InMemoryOrderModulelist: Maintains the memory order, aids in debugging memory access issues.
- InInitializationOrderModuleList: Maintains the initialization order, aids in diagnosing the problems related to the setup and configuration of modules.
Their differences serve for diagnosing specific concerns. When it comes to malware, they are both used and really no need to be confused about since they are quite close in the context of malware analysis. For further reading, here is a good blog post.
Let’s pick InMemoryOrderModuleList field to continue with. This field is a Double Linked List structure. For those anyone not familiar with Linked List structure, it’s a data structure that allows you to traverse between the lists. Thus, the InMemoryOrderModuleList field has a Flink (which is Forward link, you can think as a next button), and a Blink (which is Back Link, you think as a previous button) fields to traverse between the modules.
Each module has its own table entry called LDR_DATA_TABLE_ENTRY. Similar to the PEB, this table serves as an information board for the each module, including the details such as its name, path, and other relevant information. Thanks to the InMemoryOrderModuleList field, we can traverse between these modules’s information boards and obtain the desired information. Figure 14 illustrates the traversing between the modules.
6.3.1. StealC API Hashing Analysis
Let’s inspect one of the implementations from the StealC to clear up any confusion.
Note that, the sample to be inspected is from the Feb 2023, despite StealC having evolved since then. This is because the sample from Feb 2023 is quite straightforward to inspect one of the API Hashing implementations.
StealC uses Dynamic API Resolution anti-analysis technique to hinder its API calls, as can be seen from its self-modifying text section, limited imports, and encoded strings. Indicators are gathered and shown in Figure 15.
Since our focus is on analyzing the Ldr implementation rather than the entire analysis of StealC sample, to summarize, StealC contains the encrypted API functions (using RC4 and then Base64) within its code. After decrypting them, it uses Ldr to load the functions.
Referring to Figure 16:
- Malware obtains and stores the PEB address into the
eax
register withfs:[00000030h]
- Adds offset
0x0c
to the base address of the PEB with[eax+0Ch]
to access Ldr, the PEB_LDR_DATA structure. - Afterwards, adds offset
0x0c
onto the Ldr base address to access the InLoadOrderModuleList field to traverse between modules. - Iterates two times, which leads to Executable->ntdll.dll->Kernel32.dll
- Lastly, takes the DllBase address of the Kernel32.dll from its information board, from its LDR_DATA_TABLE_ENTRY.
After obtaining the base address of the Kernel32.dll, the malware uses GetProcAddress()
to retrieve the corresponding function addresses from the DLL base address with the decrypted function names, as seen in Figure 17. Afterwards, these functions and libraries loaded by the first loaded function, LoadLibrary()
function.
As mentioned, different implementations of Ldr can be found in various malware such as Rhadamanthys, Oski Stealer, Lumma Stealer, etc. Worth to mention that there is an awesome blog post about Lumma Stealer by Alessandro Strino, which I strongly recommend reading. It can help clarify any remaining confusion about Ldr. Here is the link to the blog post.
6.4. ProcessParameters
ProcessParameters field points to a structure too, it points to the _RTL_USER_PROCESS_PARAMETERS structure.
The most significant fields are as follows:
- CurrentDirectory (0x24)
- DllPath (0x30)
- ImagePathName (0x38)
- CommandLine(0x40)
- Environment (0x48)
As their names suggest, these fields contain information such as the full executable path, command line arguments, environment variables, and more.
Some of the fields of the _RTL_USER_PROCESS_PARAMETERS structure are shown in Figure 18.
The ProcessParameters field can serve several purposes, including VM detection and information-gathering techniques.
For example, the Environment value stores environment variables in an array. Since one of these variables is “COMPUTERNAME=”, malware developers can iterate through the Environment field values to gather the computer name. They commonly check it against names like “John Doe,” “sandbox,” “test,” etc., to detect VM machines. Additionally, with the same field, malware developers might want to determine if they are on valuable systems by analyzing the computer name.
However, there are two other significant techniques we should discuss: Command Line Manipulation and UAC Bypass.
6.4.1. Command Line Manipulation
There are many malware families utilize their various malicious functions depending on command-line parameters/arguments. Thus, manipulating the command-line arguments after execution may mean a lot for the malware’s operations.
I’d like to state that this technique is not common in malware, however, this technique has a place in most of the malware development courses, books, and blog posts. So I wanted to include this technique. Here are some of the use cases in malware:
- Executing the malware initially without any arguments to discover the system, then creating a process with specialized command-line arguments tailored to the target system.
- Evading EDR/IDS systems that use detection mechanism based on the command-line argument strings.
To achieve this, the common APIs are employed as follows:
NtQueryInformationProcess()
to obtain the PEB of the target processReadProcessMemory()
to read the RTL_USER_PROCESS_PARAMETERS structure from the PEB of the target processWriteProcessMemory()
to write the new command-line arguments into the process’s memory.
To access through PEB with offsets, Figure 19 illustrates the journey to the command-line buffer.
Figure 20 demonstrates an implementation of accessing the CommandLine field using the relevant offsets in a disassembler, which will lead us to the next technique.
6.4.2. BlackMatter Ransomware UAC Bypass Analysis
BlackMatter Ransomware uses ProcesssParameters field for Privilege Escalation purposes after a series of admin privilege controls:
- First, it calls the
SHTestTokenMembership()
function to check whether the process’s token a member of the Administrators group in the built-in domain. [9] - Then, check the OS version whether the system is above Windows 7 to run its functions properly.
- Lastly, checks the SID SubAuthority/RID value to check whether the user has the admin authority.[10]
Figure 21 illustrates the if
block that combines aforementioned functions to analyze if the process is with admin privileges.
After the examination, if the admin privilege controls fail, program enters into the if condition and UAC Bypass function invokes.
UAC Bypass in BlackMatter ransomware takes place with masquerading the PEB. To achieve this, BlackMatter uses a variant of Masquerade-PEB.ps1 script, which is also used with BlackCat ransomware.
Figure 22 shows the similarity between a one of the PEB Masquerade script and the pseudocode of the BlackMatter displayed on a disassembler.
Figure 23 includes the instructions and the corresponding pseudocode of the PEB masquerading process. Let’s examine the instructions step by step:
- Accessing the PEB Base Address:
The instructionmov ebx, large fs:30h
accesses the PEB. - Entering the Critical Section:
We see that offset0x1Ch
is accessed here withpush dword ptr [ebx+1Ch]
instruction. This offset corresponds to the FastPebLock field, which is a CRITICAL_SECTION used by Windows to synchronize the PEB accesses.
So, prior to trying to write to the PEB, the write access must be established by the FastPebLock field. To achieve this,RtlEnterCriticalSection()
function is used to access the critical section, ensuring that subsequent writes to the PEB are thread-safe. - Accessing ProcessParameters:
After entering the critical section, the instructionmov esi, [ebx+10h]
stores the address of the ProcessParameters field. - Modifying the ImagePathName:
The instructionlea eax, [esi+38h]
loads the address of the ImagePathName field from the ProcessParameters into theeax
register.
Right after that, “dllhost_path” variable(C:\\Windows\\System32\\dllhost.exe
) pushed, writed into the ImagePathName field. - Modifying the CommandLine:
Similarly, the instructionlea eax, [esi+40h]
loads the address of theCommandLine
field from theProcessParameters
structure into theeax
register.
Right after that, “dllhost_cmdline” variable(\"C:\\Windows\\System32\\dllhost.exe\
) pushed, writed into the ImagePathName field. - Leaving the Critical Section:
The instructionpush dword ptr [ebx+1Ch]
andRtlLeaveCriticalSection()
release the critical section.
By modifying the ImagePathName and CommandLine fields from the ProcessParameters field within the PEB, the process masquerades as dllhost.exe
from the System32 directory. This allows the process to run COM objects as dllhost.exe
. Afterwards this masquerading process, BlackMatter uses related COM object and elevates the new process to the admin privilege.
7. Conclusion
I highly recommend analyzing the BlackMatter ransomware sample provided in the IoC section. The sample lacks obfuscation and invokes PEB and TEB techniques for most of its functions. It is an excellent resource to train the PEB techniques.
A thorough understanding of the PEB and its fields is crucial for malware analysis. As new techniques and methods emerge frequently and malware becomes more sophisticated each day, staying current with these concepts can greatly help in identifying and addressing sophisticated threats.
I hope this blog post has provided a clear understanding of the aforementioned techniques. If you have any questions, please feel free to reach out to me via the contact links in the “About” section of my profile. Have a great day!
References
[1] https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/pebteb/peb/index.htm
[2] https://linux-kernel-labs.github.io/refs/heads/master/lectures/processes.html#struct-task-struct
[3] https://learn.microsoft.com/en-us/windows/win32/ipc/interprocess-communications
[4] (Figure)https://en.wikipedia.org/wiki/Win32_Thread_Information_Block
[6] https://devblogs.microsoft.com/oldnewthing/20220919-00/?p=107195
[7] https://learning.oreilly.com/library/view/windows-r-internals-sixth/9780735671294/ch03s10.html
[8] https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/ntpsapi_x/peb_ldr_data.htm
[10] https://www.ntfs.com/ntfs-permissions-security-identifier.htm
Samples/IoCs
- StealC Sample
— MD5:0d049f764a22e16933f8c3f1704d4e50
— SHA1:5faad57c7341f76c18ae813e9fa9fbfe434f7b41
— SHA256:77d6f1914af6caf909fa2a246fcec05f500f79dd56e5d0d466d55924695c702d
— MalwareBazaar Link - BlackMatter Sample
— MD5:50c4970003a84cab1bf2634631fe39d7
— SHA1:721a749cbd6afcd765e07902c17d5ab949b04e4a
— SHA256:520bd9ed608c668810971dbd51184c6a29819674280b018dc4027bc38fc42e57
— MalwareBazaar Link