Hello world!
It’s July and this is another blog post on malware analysis. I came up with the idea to write this blog because I feel that beginners in the malware analysis field should understand this topic thoroughly as it is commonly used by modern malware.
The reasons malware authors implement this technique are fairly simple.
- To avoid straightforward inspection of API function calls in the Import Address Table (IAT) by analysts, making it harder for analysts to identify and guess the malware’s behavior based on the imported functions.
- To make it difficult for analysts to quickly identify which API functions the malware is calling while reversing (statically/debugging) the sample.
- Shellcode often uses this PEB walk to reconstruct the import table and then resolve Windows API.
Long short story in malware context, the PEB contains the information about loaded modules (malware’s interests: kernel32.dll
and ntdll.dll
) that have been mapped into process space which can be use this information to dynamically resolve function in the particular DLL. By walking through the PEB structure, malware can extract the base addresses of these DLLs and then resolve the addresses of specific functions within these DLLs.
For example, the figure below shows the list of imports of a program that uses the API MessageBoxA
to print a message.
In contrast, the next figure shows the imports of another program that performs the same function to call MessageBoxA
to print a message, but with PEB walk implemented.
Thus, analyst can’t get the idea of the program behavior if they depend on IAT list.
What is PEB
When a process running, Windows OS will allocates a Process Environment Block (PEB) structure for the process. The PEB is created by the kernel and it basically contains fields that provide information such as loaded modules, process parameters, environment variables, and many more.
Below is the PEB structure from MSDN (not fully documented) contains all parameters associated by system with current process:
From the above structure members, we can see the Ldr
member. This member contains a pointer to a PEB_LDR_DATA
structure, which contains information about all of the loaded modules (EXEs/DLLs) in the current process, including the doubly-linked list InMemoryOrderModuleList
. This InMemoryOrderModuleList
is a linked list that used to find the addresses of loaded DLLs.
In this structure, a process (malware) would used InMemoryOrderModuleList
to enumerate loaded modules. This linked list contains entries for each module, represented by LDR_DATA_TABLE_ENTRY
structures, which provide detailed information about each module.
If you use WinDbg
and type !peb
, the Ldr.InMemoryOrderModuleList
field will show you the list of DLLs loaded for the current process (Notepad).
0:007> !peb
PEB at 000000e8561f3000
InheritedAddressSpace: No
ReadImageFileExecOptions: No
BeingDebugged: Yes
ImageBaseAddress: 00007ff6fd930000
NtGlobalFlag: 0
NtGlobalFlag2: 0
Ldr 00007ffda341c4c0
Ldr.Initialized: Yes
Ldr.InInitializationOrderModuleList: 00000231c3282dd0 . 00000231c32af100
Ldr.InLoadOrderModuleList: 00000231c3282f40 . 00000231c32af470
Ldr.InMemoryOrderModuleList: 00000231c3282f50 . 00000231c32af480
Base TimeStamp Module
7ff6fd930000 61fc30c0 Feb 04 03:45:04 2022 C:\Windows\system32\notepad.exe
7ffda32b0000 8a1bb6f3 Jun 05 07:08:35 2043 C:\Windows\SYSTEM32\ntdll.dll
7ffda15b0000 9ec9da27 Jun 02 23:58:31 2054 C:\Windows\System32\KERNEL32.DLL
7ffda0ef0000 f7a99bd4 Sep 02 15:03:48 2101 C:\Windows\System32\KERNELBASE.dll
7ffda3240000 660e896d Apr 04 19:05:17 2024 C:\Program Files (x86)\Common Files\X.dll
7ffda1dc0000 d8a41a47 Mar 05 20:21:27 2085 C:\Windows\System32\GDI32.dll
7ffda1260000 c6e09c3a Sep 25 11:47:06 2075 C:\Windows\System32\win32u.dll
7ffda0940000 4894be87 Aug 03 04:07:35 2008 C:\Windows\System32\gdi32full.dll
7ffda0a60000 39255ccf May 19 23:25:03 2000 C:\Windows\System32\msvcp_win.dll
7ffda0d10000 81cf5d89 Jan 05 22:32:41 2039 C:\Windows\System32\ucrtbase.dll
7ffda1ab0000 95bf155e Aug 12 04:53:50 2049 C:\Windows\System32\USER32.dll
7ffda1750000 c16ed6b5 Nov 02 06:56:53 2072 C:\Windows\System32\combase.dll
7ffda2ed0000 582ed2ab Nov 18 18:06:35 2016 C:\Windows\System32\RPCRT4.dll
<--- snippet --->
Accessing PEB structure in code
So, how can malware access this structure? One of the way are to direct access via inline assembly:
#include <stdio.h>
#include <Windows.h>
int main() {
PVOID peb;
__asm {
mov eax, fs:[0x30]
mov peb, eax
}
printf("PEB Address: %p\n", peb);
return 0;
}
In the above code, the code uses __asm
keyword to insert assembly instructions directly. In that particular code block, it access TEB via the fs
segment register. fs
is a segment register used in 32-bit Windows to point to the TEB. In this case, fs:[0x30]
is the offset within the TEB that contains a pointer to the PEB.
If we compiled the code above, and reverse the code in IDA, we’ll see IDA decompiler represent the fs:[0x30]
as NtCurrentPeb()
and dword ptr [ebp+var_C]
in the IDA View-A
represent variable peb
in the original code.
Thus, during our reverse engineering malware activities, findings fs:[0x30]
in the assembly view or NtCurrentPeb()
in pseudocode helps identify activities involving PEB access.
PEB Walk
In malware context, the PEB contains the information about loaded modules (malware’s interests: kernel32.dll
and ntdll.dll
) that have been mapped into process space which can be use this information to dynamically resolve function in the particular DLL. By walking through the PEB structure, malware can extract the base addresses of these DLLs and then resolve the addresses of specific functions within these DLLs.
For example, kernel32.dll
contains two important functions that malware often seeks:
LoadLibraryA(libraryname)
: Loads the specified DLL into the process.GetProcAddress(hmodule, functionname)
: Retrieves the address of an exported function or variable from the specified DLL.
In above example, the goal of the PEB walk in malware is straightforward, which is to identify the address of the LoadLibraryA
function inside kernel32.dll
and use it to load a library and its functions dynamically.
To simply put the flow, here are the ‘process’ of the PEB walk for kernel32.dll
and resolve addresses of LoadLibraryA
and GetProcAddress
then use it to invoke MessageBoxA
.
- Obtains and access the PEB structure of the current process
- Navigate to the
PEB_LDR_DATA
structure by using theLdr
member of thePEB
- Iterate through the
InLoadOrderModuleList
to find theLDR_DATA_TABLE_ENTRY
forkernel32.dll
- Once the entry for
kernel32.dll
is found, extract its base address - Manually parse the export table of
kernel32.dll
to resolve the addresses ofLoadLibraryA
andGetProcAddress
- Load
user32.dll
usingLoadLibraryA
- Get the address of
MessageBoxA
fromuser32.dll
usingGetProcAddress
- Display the message “PEB walk success” using
MessageBoxA
Full source code:
#include <stdio.h>
#include <windows.h>
typedef struct _UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} UNICODE_STRING, * PUNICODE_STRING;
typedef struct _LDR_DATA_TABLE_ENTRY {
LIST_ENTRY InLoadOrderLinks;
LIST_ENTRY InMemoryOrderLinks;
LIST_ENTRY InInitializationOrderLinks;
PVOID DllBase;
PVOID EntryPoint;
ULONG SizeOfImage;
UNICODE_STRING FullDllName;
UNICODE_STRING BaseDllName;
ULONG Flags;
USHORT LoadCount;
USHORT TlsIndex;
LIST_ENTRY HashLinks;
PVOID SectionPointer;
ULONG CheckSum;
ULONG TimeDateStamp;
PVOID LoadedImports;
PVOID EntryPointActivationContext;
PVOID PatchInformation;
} LDR_DATA_TABLE_ENTRY, * PLDR_DATA_TABLE_ENTRY;
typedef struct _PEB_LDR_DATA {
ULONG Length;
BOOLEAN Initialized;
HANDLE SsHandle;
LIST_ENTRY InLoadOrderModuleList;
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
} PEB_LDR_DATA, * PPEB_LDR_DATA;
typedef struct _PEB {
BOOLEAN InheritedAddressSpace;
BOOLEAN ReadImageFileExecOptions;
BOOLEAN BeingDebugged;
BOOLEAN SpareBool;
HANDLE Mutant;
PVOID ImageBaseAddress;
PPEB_LDR_DATA Ldr;
} PEB, * PPEB;
typedef FARPROC(WINAPI* GETPROCADDRESS)(HMODULE, LPCSTR);
typedef HMODULE(WINAPI* LOADLIBRARYA)(LPCSTR);
typedef int (WINAPI* MESSAGEBOXA)(HWND, LPCSTR, LPCSTR, UINT);
PVOID GetProcAddressKernel32(HMODULE hModule, LPCSTR lpProcName) {
PIMAGE_DOS_HEADER pDOSHeader = (PIMAGE_DOS_HEADER)hModule;
PIMAGE_NT_HEADERS pNTHeaders = (PIMAGE_NT_HEADERS)((BYTE*)hModule + pDOSHeader->e_lfanew);
PIMAGE_EXPORT_DIRECTORY pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((BYTE*)hModule + pNTHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
DWORD* pAddressOfFunctions = (DWORD*)((BYTE*)hModule + pExportDirectory->AddressOfFunctions);
DWORD* pAddressOfNames = (DWORD*)((BYTE*)hModule + pExportDirectory->AddressOfNames);
WORD* pAddressOfNameOrdinals = (WORD*)((BYTE*)hModule + pExportDirectory->AddressOfNameOrdinals);
for (DWORD i = 0; i < pExportDirectory->NumberOfNames; i++) {
char* functionName = (char*)((BYTE*)hModule + pAddressOfNames[i]);
if (strcmp(functionName, lpProcName) == 0) {
return (PVOID)((BYTE*)hModule + pAddressOfFunctions[pAddressOfNameOrdinals[i]]);
}
}
return NULL;
}
int main() {
PEB* peb;
PLDR_DATA_TABLE_ENTRY module;
LIST_ENTRY* listEntry;
HMODULE kernel32baseAddr = NULL;
GETPROCADDRESS ptrGetProcAddress = NULL;
LOADLIBRARYA ptrLoadLibraryA = NULL;
MESSAGEBOXA ptrMessageBoxA = NULL;
__asm {
mov eax, fs: [0x30]
mov peb, eax
}
listEntry = peb->Ldr->InLoadOrderModuleList.Flink;
do {
module = CONTAINING_RECORD(listEntry, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks);
char baseDllName[256];
int i;
for (i = 0; i < module->BaseDllName.Length / sizeof(WCHAR) && i < sizeof(baseDllName) - 1; i++) {
baseDllName[i] = (char)module->BaseDllName.Buffer[i];
}
baseDllName[i] = '\0';
if (_stricmp(baseDllName, "kernel32.dll") == 0) {
kernel32baseAddr = (HMODULE)module->DllBase;
}
listEntry = listEntry->Flink;
} while (listEntry != &peb->Ldr->InLoadOrderModuleList);
if (kernel32baseAddr) {
ptrGetProcAddress = (GETPROCADDRESS)GetProcAddressKernel32(kernel32baseAddr, "GetProcAddress");
ptrLoadLibraryA = (LOADLIBRARYA)GetProcAddressKernel32(kernel32baseAddr, "LoadLibraryA");
HMODULE user32Base = ptrLoadLibraryA("user32.dll");
ptrMessageBoxA = (MESSAGEBOXA)ptrGetProcAddress(user32Base, "MessageBoxA");
ptrMessageBoxA(NULL, "PEB walk success", "Success", MB_OK);
}
return 0;
}
Understand the code
Let’s breakdown the important code step by step to understand how it works.
First, we define all the necessary PEB structures that are required to enable us to interact with the PEB.
typedef struct _LDR_DATA_TABLE_ENTRY {
LIST_ENTRY InLoadOrderLinks;
LIST_ENTRY InMemoryOrderLinks;
LIST_ENTRY InInitializationOrderLinks;
PVOID DllBase;
PVOID EntryPoint;
ULONG SizeOfImage;
UNICODE_STRING FullDllName;
UNICODE_STRING BaseDllName;
ULONG Flags;
USHORT LoadCount;
USHORT TlsIndex;
LIST_ENTRY HashLinks;
PVOID SectionPointer;
ULONG CheckSum;
ULONG TimeDateStamp;
PVOID LoadedImports;
PVOID EntryPointActivationContext;
PVOID PatchInformation;
} LDR_DATA_TABLE_ENTRY, * PLDR_DATA_TABLE_ENTRY;
typedef struct _PEB_LDR_DATA {
ULONG Length;
BOOLEAN Initialized;
HANDLE SsHandle;
LIST_ENTRY InLoadOrderModuleList;
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
} PEB_LDR_DATA, * PPEB_LDR_DATA;
typedef struct _PEB {
BOOLEAN InheritedAddressSpace;
BOOLEAN ReadImageFileExecOptions;
BOOLEAN BeingDebugged;
BOOLEAN SpareBool;
HANDLE Mutant;
PVOID ImageBaseAddress;
PPEB_LDR_DATA Ldr;
// Omitted other members for brevity
} PEB, * PPEB;
Then we define function pointer types for the Windows API functions we need:
GETPROCADDRESS
forGetProcAddress
LOADLIBRARYA
forLoadLibraryA
MESSAGEBOXA
forMessageBoxA
typedef FARPROC(WINAPI* GETPROCADDRESS)(HMODULE, LPCSTR);
typedef HMODULE(WINAPI* LOADLIBRARYA)(LPCSTR);
typedef int (WINAPI* MESSAGEBOXA)(HWND, LPCSTR, LPCSTR, UINT);
Then, the code proceed to manually resolves the address of an exported function from kernel32.dll
:
PVOID GetProcAddressKernel32(HMODULE hModule, LPCSTR lpProcName) {
PIMAGE_DOS_HEADER pDOSHeader = (PIMAGE_DOS_HEADER)hModule;
PIMAGE_NT_HEADERS pNTHeaders = (PIMAGE_NT_HEADERS)((BYTE*)hModule + pDOSHeader->e_lfanew);
PIMAGE_EXPORT_DIRECTORY pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((BYTE*)hModule + pNTHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
DWORD* pAddressOfFunctions = (DWORD*)((BYTE*)hModule + pExportDirectory->AddressOfFunctions);
DWORD* pAddressOfNames = (DWORD*)((BYTE*)hModule + pExportDirectory->AddressOfNames);
WORD* pAddressOfNameOrdinals = (WORD*)((BYTE*)hModule + pExportDirectory->AddressOfNameOrdinals);
for (DWORD i = 0; i < pExportDirectory->NumberOfNames; i++) {
char* functionName = (char*)((BYTE*)hModule + pAddressOfNames[i]);
if (strcmp(functionName, lpProcName) == 0) {
return (PVOID)((BYTE*)hModule + pAddressOfFunctions[pAddressOfNameOrdinals[i]]);
}
}
return NULL;
}
It basically does:
- Parse the DOS header, NT headers, and export directory.
- Traverse the export directory to match the function name.
- Return the address of the matched function.
And then we go to the main
function. In the main function, we start by obtaining the PEB address using inline assembly.
int main() {
PEB* peb;
PLDR_DATA_TABLE_ENTRY module;
LIST_ENTRY* listEntry;
HMODULE kernel32baseAddr = NULL;
GETPROCADDRESS ptrGetProcAddress = NULL;
LOADLIBRARYA ptrLoadLibraryA = NULL;
MESSAGEBOXA ptrMessageBoxA = NULL;
__asm {
mov eax, fs:[0x30]
mov peb, eax
}
We then traverse the InLoadOrderModuleList
to find the LDR_DATA_TABLE_ENTRY
for kernel32.dll
:
// Traverse the InLoadOrderModuleList to find kernel32.dll
listEntry = peb->Ldr->InLoadOrderModuleList.Flink;
do {
module = CONTAINING_RECORD(listEntry, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks);
char baseDllName[256];
int i;
for (i = 0; i < module->BaseDllName.Length / sizeof(WCHAR) && i < sizeof(baseDllName) - 1; i++) {
baseDllName[i] = (char)module->BaseDllName.Buffer[i];
}
baseDllName[i] = '\0';
if (_stricmp(baseDllName, "kernel32.dll") == 0) {
kernel32baseAddr = (HMODULE)module->DllBase;
}
listEntry = listEntry->Flink;
} while (listEntry != &peb->Ldr->InLoadOrderModuleList);
Finally, we use the resolved GetProcAddress
and LoadLibraryA
functions to:
- Load
user32.dll
. - Resolve the address of
MessageBoxA
. - Display a message box with the text “PEB walk success”.
if (kernel32baseAddr) {
ptrGetProcAddress = (GETPROCADDRESS)GetProcAddressKernel32(kernel32baseAddr, "GetProcAddress");
ptrLoadLibraryA = (LOADLIBRARYA)GetProcAddressKernel32(kernel32baseAddr, "LoadLibraryA");
HMODULE user32Base = ptrLoadLibraryA("user32.dll");
ptrMessageBoxA = (MESSAGEBOXA)ptrGetProcAddress(user32Base, "MessageBoxA");
ptrMessageBoxA(NULL, "PEB walk success", "Success", MB_OK);
}
return 0;
}
Reversing our PEB walk program
If we open the compiled program in IDA, this is the hex-ray decompilation result with the explaination in the red comment box. The color of the box in the original code (right side) represent the same meaning code in decompiled code (left side).
However, it looks like the generated pseudocode is not readable and clear enough for us to understand the code. Let’s rename the variables to this decompiled code as shown in figure below.
Next, what we want to do is to apply structure into the decompiled code. So, how to do it?
First, go to View -> Open Subviews -> Local Types. You will represent with this view:
Right click on the Local Types view -> Choose Insert… -> Add our custom structure into the box like in the below figure.
typedef struct _LDR_DATA_TABLE_ENTRY {
LIST_ENTRY InLoadOrderLinks;
LIST_ENTRY InMemoryOrderLinks;
LIST_ENTRY InInitializationOrderLinks;
PVOID DllBase;
PVOID EntryPoint;
ULONG SizeOfImage;
UNICODE_STRING FullDllName;
UNICODE_STRING BaseDllName;
ULONG Flags;
USHORT LoadCount;
USHORT TlsIndex;
LIST_ENTRY HashLinks;
PVOID SectionPointer;
ULONG CheckSum;
ULONG TimeDateStamp;
PVOID LoadedImports;
PVOID EntryPointActivationContext;
PVOID PatchInformation;
} LDR_DATA_TABLE_ENTRY, * PLDR_DATA_TABLE_ENTRY;
typedef struct _PEB_LDR_DATA {
ULONG Length;
BOOLEAN Initialized;
HANDLE SsHandle;
LIST_ENTRY InLoadOrderModuleList;
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
} PEB_LDR_DATA, * PPEB_LDR_DATA;
typedef struct _PEB {
BOOLEAN InheritedAddressSpace;
BOOLEAN ReadImageFileExecOptions;
BOOLEAN BeingDebugged;
BOOLEAN SpareBool;
HANDLE Mutant;
PVOID ImageBaseAddress;
PPEB_LDR_DATA Ldr;
} PEB, * PPEB;
Click OK, then our custom structure will be available in the list!
Now we can proceed use our custom structure in the code. But, first we need to find the variable that use the structure which is in this case, the variable is moduleEntry
because it’s looks like moduleEntry
is being used as a pointer to LDR_DATA_TABLE_ENTRY
.
To use our custom structure, Right click on the variable and choose Convert to struct
Then, select our custom structure, and click OK
Then, this is the final result after adding the structure.
We also can apply this for custom function GetProcAddress_via_PEParser
.
In this code, it appears to be parsing the PE headers of a module to find the address of a specific function.
Thus, this involves working with the IMAGE_DOS_HEADER
, IMAGE_NT_HEADERS
, and IMAGE_EXPORT_DIRECTORY
structures.
Using the same step, let’s add these following structures:
struct IMAGE_DOS_HEADER {
uint16_t e_magic;
uint16_t e_cblp;
uint16_t e_cp;
uint16_t e_crlc;
uint16_t e_cparhdr;
uint16_t e_minalloc;
uint16_t e_maxalloc;
uint16_t e_ss;
uint16_t e_sp;
uint16_t e_csum;
uint16_t e_ip;
uint16_t e_cs;
uint16_t e_lfarlc;
uint16_t e_ovno;
uint16_t e_res[4];
uint16_t e_oemid;
uint16_t e_oeminfo;
uint16_t e_res2[10];
int32_t e_lfanew;
};
struct IMAGE_FILE_HEADER {
uint16_t Machine;
uint16_t NumberOfSections;
uint32_t TimeDateStamp;
uint32_t PointerToSymbolTable;
uint32_t NumberOfSymbols;
uint16_t SizeOfOptionalHeader;
uint16_t Characteristics;
};
struct IMAGE_DATA_DIRECTORY {
uint32_t VirtualAddress;
uint32_t Size;
};
#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES 16
struct IMAGE_OPTIONAL_HEADER32 {
uint16_t Magic;
uint8_t MajorLinkerVersion;
uint8_t MinorLinkerVersion;
uint32_t SizeOfCode;
uint32_t SizeOfInitializedData;
uint32_t SizeOfUninitializedData;
uint32_t AddressOfEntryPoint;
uint32_t BaseOfCode;
uint32_t BaseOfData;
uint32_t ImageBase;
uint32_t SectionAlignment;
uint32_t FileAlignment;
uint16_t MajorOperatingSystemVersion;
uint16_t MinorOperatingSystemVersion;
uint16_t MajorImageVersion;
uint16_t MinorImageVersion;
uint16_t MajorSubsystemVersion;
uint16_t MinorSubsystemVersion;
uint32_t Win32VersionValue;
uint32_t SizeOfImage;
uint32_t SizeOfHeaders;
uint32_t CheckSum;
uint16_t Subsystem;
uint16_t DllCharacteristics;
uint32_t SizeOfStackReserve;
uint32_t SizeOfStackCommit;
uint32_t SizeOfHeapReserve;
uint32_t SizeOfHeapCommit;
uint32_t LoaderFlags;
uint32_t NumberOfRvaAndSizes;
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
};
struct IMAGE_NT_HEADERS {
uint32_t Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER32 OptionalHeader;
};
struct IMAGE_EXPORT_DIRECTORY {
uint32_t Characteristics;
uint32_t TimeDateStamp;
uint16_t MajorVersion;
uint16_t MinorVersion;
uint32_t Name;
uint32_t Base;
uint32_t NumberOfFunctions;
uint32_t NumberOfNames;
uint32_t AddressOfFunctions;
uint32_t AddressOfNames;
uint32_t AddressOfNameOrdinals;
};
Then convert the variables to the custom structures:
hModule
=IMAGE_DOS_HEADER
v6
,v5
,v4
,v7
=IMAGE_EXPORT_DIRECTORY
This is the final result, including several rename variables:
Finally, after adding all the custom structures and renaming the variables, the code is more readable. The other subfunctions have not been renamed because they appear to be part of a runtime library function that performs error handling and possibly runtime checks. Therefore, there is no need for us to analyze them.
Sum up
In this blog, we learned about the Process Environment Block (PEB), how malware authors can access PEB information, and how it can be abused. We also learned how to reverse-engineer our own program that uses PEB walk and perform custom structure addition in IDA Pro.
Understanding the PEB walk technique is crucial for malware analysis, as it is commonly used in modern malware to dynamically resolve API functions. By walking through the PEB, malware can locate important DLLs such as kernel32.dll
and user32.dll
, and extract the addresses of functions. This method helps malware avoid straightforward inspection of API calls in the Import Address Table (IAT), making it more difficult for analysts to identify and understand the malware’s behavior.