Fileless malware and PEB enumeration
I was reverse engineering a fileless (meaning the malicious payload is only in the system’s memory) malware sample and I came across this technique which apparently is quite popular in fileless malware. So, this is what this post will be about. How fileless malware take advantage of PEB (Process Environment Block) enumeration to work. You can see the PEB structure as defined in Winternl.h header file below.
typedef struct _PEB { BYTE Reserved1[2]; BYTE BeingDebugged; BYTE Reserved2[1]; PVOID Reserved3[2]; PPEB_LDR_DATA Ldr; PRTL_USER_PROCESS_PARAMETERS ProcessParameters; BYTE Reserved4[104]; PVOID Reserved5[52]; PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoutine; BYTE Reserved6[128]; PVOID Reserved7[1]; ULONG SessionId; } PEB, *PPEB;
When a malware injects a payload into memory it needs to somehow find which API calls to use. This means it has to find where those are located in memory. A common method to do this is using PEB which is always located at the same offset. Specifically, at offset 0x30 from the “fs” register. The assembly instructions you see below will load the PEB pointer to the “edx” register.
xor edx, edx ; Make sure edx is empty mov edx, fs:[edx+30h] ; Get the address of PEB
Now that the malware has a starting point, it can get advantage of the “Ldr” pointer which is PEB. “Ldr” is technically a pointer to a “PEB_LDR_DATA” structure which contains a linked list (InMemoryOrderModuleList) of the loaded modules. Here you can see how this is defined in Winternl.h header file.
typedef struct _PEB_LDR_DATA { BYTE Reserved1[8]; PVOID Reserved2[3]; LIST_ENTRY InMemoryOrderModuleList; } PEB_LDR_DATA, *PPEB_LDR_DATA;
And below you can see the equivalent assembly instructions that will help us find the “Ldr” pointer. We are just using the PEB pointer that we discovered above and add 0x0C to it which will lead us to the location of the “Ldr” pointer, and we store its value in the “edx” register.
xor edx, edx ; Make sure edx is empty mov edx, fs:[edx+30h] ; Get the address of PEB mov edx, [edx+0Ch] ; Get the address of PEB->Ldr
Within the “Ldr” as you saw from the type definition above, there is a doubly-linked list named “InMemoryOrderModuleList”. This doubly-linked list contains the modules that are loaded in this process. Once again, the “LIST_ENTRY” data type is defined in the Winternl.h header file and you can see it here.
typedef struct _LIST_ENTRY { struct _LIST_ENTRY *Flink; struct _LIST_ENTRY *Blink; }LIST_ENTRY, *PLIST_ENTRY;
The code can now just iterate through the “InMemoryOrderModuleList” linked list to enumerate the loaded modules that are available. You can see the equivalent assembly code below which is similar to the above. However, now “edx” register points to the first module (specifically a pointer to a “LDR_DATA_TABLE_ENTRY” structure) that is available. By increasing the offset we can iterate through all of them.
xor edx, edx ; Make sure edx is empty mov edx, fs:[edx+30h] ; Get the address of PEB mov edx, [edx+0Ch] ; Get the address of PEB->Ldr mov edx, [edx+14h] ; Get the PEB->Ldr->InMemoryOrderModuleList
From this point on, the malware can identify the modules it needs to use and reference them directly. This method is very popular in fileless malware as it can be used to dynamically discover loaded modules when a payload is injected in memory and executed via another process. For example, a common method is to use the third entry of the list (which includes the base address of kernel32.dll) and enumerate the export table of kernel32.dll to find LoadLibrary() and start loading arbitrary DLLs required for its operation. Here is a sample code that gets the base address of kernel32.dll which can be used to discover LoadLibrary() to be able to load modules dynamically.
xor edx, edx ; Make sure edx is empty mov edx, fs:[edx+30h] ; Get the address of PEB mov edx, [edx+0Ch] ; Get the address of PEB->Ldr mov edx, [edx+14h] ; Get the PEB->Ldr->InMemoryOrderModuleList mov edx, [edx] ; Second entry in PEB->Ldr->InMemoryOrderModuleList mov edx, [edx] ; Third entry (kernel32.dll) in PEB->Ldr->InMemoryOrderModuleList mov edx, [edx+10h] ; The base address of the third entry (kernel32.dll)
Leave a comment