PEB malware’s favourite place

In many malwares there exist a well known technique, using PEB to get loaded module list. Malware can get all the loaded module list, their base address, exported function addresses. This way they avoid call to ‘GetProcAddress’ ,’LoadLibrary’. Also they can hide their loaded malicious module from the user by unlinking their module entry from the chain. So PEB is very important structure while analyzing malware. This blog provide details about this technique.

What is PEB (Process Environment Block)

PEB is operating system’s user mode structure (can be accessed from both user and kernel mode). Every process has its own PEB structure associated with it. PEB contain all process related information (and more). For each process there is EPROCESS structure maintained in kernel mode. In EPROCESS there is pointer to PEB.

What malware want’s from PEB

Let’s say you are writing some application in which you want to call ‘ReadProcessMemory’ api. you will load the library in which this api available (Kernel32.dll) and then make a call to this API and that’s it.
Now if the malware author goes the same way for all required API’s then this will trigger the AV engines (due to AV’s API hooking technique). To avoid this, malware uses PEB structure to access the required details. They parse PEB ,read image base of required modules, calculate the export addresses and make call to them. So they no need to call GetProcAddress to get address of API. In this way they remain stealthier at some level.

Another thing is by deleting the node from link list , they can hide malicious module that is loaded in memory. So user cannot see its presence in loaded module list.

  • There are some techniques to see these hidden module such as analyzing the VAD structure which is present in EPROCESS, checking hash values in Hashlinks list present in _LDR_DATA_TABLE_ENTRY structure and many other.

How actually PEB parsed by malwares?

Lets look closely how to get all this data from PEB
First we have to get address of PEB (to get PEB address for our own process is easy). FS register contain pointers PEB at 0x30th address.

Now PEB + 0x1c contain ‘Ldr’ . Ldr is pointer to PEB_LDR_DATA structure. Below members present in this structure. This header present in winternl.h.

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

If we get this structure using windbg, it will look like below:

These two structures looks different. This might make confusion but the thing is all these are undocumented internal data structures, so they changes over time with new OS release or in new service pack. Microsoft don’t want you to use them directly without help of API.

At offset 0x14 in this PEB_LDR_DATA structure we can see InMemoryOrderModuleList member. This is of type ‘LIST_ENTRY’ and this is double link list. If you find it confusing, I have tried below diagram to help you digest it.

PEB_LDR_DATA only contain the header of double link list. PEB_LDR_DATA does not contain dll base address and dll names. we must follow the flink from this InMemoryOrderModuleList so that we will now point to next member of InMemoryOrderModuleList. Now this next node is present in LDR_DATA_TABLE_ENTRY structure (as you see in figure). This LDR_DATA_TABLE_ENTRY structure contain the information we interested in. At this point we have address of second member of LDR_DATA_TABLE_ENTRY. Look at below image, we can see there is DllBase member at address 0x18 and FullDllName at 0x24 and we are now pointing to 0x8 in LDR_DATA_TABLE_ENTRY. So in order to access DllBase we will add 0x8 in current address and to access full dll name we will add 0x1c. while accessing FullDllName, first 2 bytes is length and next 2 bytes are for max length, so our dll name will starts after 0x1c + 0x4 bytes. (shown in CPP program at end of this blog)

As we were following InMemoryOrderList we need 0x8 and 0x1c offsets. In case we follow InInitorderList or InLoadOrderList these offsets will change due to their position in LDR_DATA_TABLE_ENTRY.

Similarly to get next loaded module name, follow the Flink and again same process. Remember that since this is double circular link list, you will come to first node, and at that time dont try to get dll name and base address from this node. As this node is in PEB_LDR_DATA and not in LDR_DATA_TABLE_ENTRY.

Below is some cpp code to demonstrate this. In this code I have also added assembly code to show how to get dllBase and fullDllName for first loaded dll. If we put this asm code in loop, we can get all loaded modules information. While in CPP code all modules are printed using for loop. I have used visual studio 2019.

#include <windows.h>
#include<winternl.h>
#include<stdio.h>
using namespace std;

void PrintModulenameAndAddresses()
{
	PPEB pPeb = 0;
	PLDR_DATA_TABLE_ENTRY pLdrDataTableEntry = 0;

	// Get PEB address using below method
#ifdef _WIN64
	pPeb = (PPEB)__readgsdword(0x60);
#else
	pPeb = (PPEB)__readfsdword(0x30);
#endif 

	// Print all required addresses
	printf("\n\n Address after parsing pe structure");
	printf("\n peb address : %x", pPeb);
	printf("\n Ldr addrsss : %x", pPeb->Ldr);
	printf("\n InMemoryOrderModuleList address : %x", pPeb->Ldr->InMemoryOrderModuleList);
	printf("\n Next node of InMemoryOrderModuleList address  : %x", *(pPeb->Ldr->InMemoryOrderModuleList.Flink));

	PLIST_ENTRY pLE = &(pPeb->Ldr->InMemoryOrderModuleList);
	printf("\n\nFull dll names\t\t\t Base addresses");
	for (PLIST_ENTRY pl = pLE->Flink; pl != pLE;pl = pl->Flink)
	{
		// Here our InMemoryOrderModuleList comes at 2nd position  (at first position InLoadOrderList present) in LDR_DATA_TABLE_ENTRY
		// so before type casting it, subtract 8 bytes (or 1 as pl is of size 8 byte) from pl
		//
		pl = pl - 1;
		pLdrDataTableEntry = (PLDR_DATA_TABLE_ENTRY)pl;
		printf("\n %ls\t%x", pLdrDataTableEntry->FullDllName.Buffer, (DWORD)pLdrDataTableEntry->DllBase);
		// again reset the pointer , so that we can properly point to next flink address
		//
		pl = pl + 1;
	}
}

int main()
{
	int peb, ldr, *inMemoryOList, nextInMemoryOList, dllBase, dllnameaddress;
	char* dllName;
	int *dllBase_arr[50];
	int *dllname_arr[50];
	int count;
	char format[] = "%x";
	char earth[] = "World";
	__asm
	{
		
		// access PEB
		mov eax, fs: [0x30] ;get peb address
		mov peb, eax

		// go to peb+0x0c to get PEB_LDR_DATA (Ldr)
		mov ebx, [eax + 0xc]; get peb_ldr_data address
		mov ldr, ebx

		// add 0x14 to get InMemoryOrderModuleList address
		mov edx, [ebx + 0x14]; InMemoryOrderModuleList
		mov inMemoryOList, edx

		// save next node address in nextInMemoryOList variable
		mov eax, [inMemoryOList]; // eax contain flink address
		mov eax, [eax]; go to next node // now eax contain next nod address
		mov nextInMemoryOList, eax

		// get dllBase address
		mov eax, [edx + 0x10]
		mov dllBase, eax

		// This will point to dll name field, in which
		// first 2 bytes is length, next 2 bytes is max length
		// so we have added more 4 bytes to get exact dll name address

		mov eax, [edx + 0x1c + 4]; 
		mov dllnameaddress, eax
	}

	printf("\n Addresses using assembly");
	printf("\n PEB address : %x", peb);
	printf("\n Ldr address : %x", ldr);
	printf("\n InMemoryOrderModuleList address : %x", inMemoryOList);
	printf("\n next node InMemoryOrderModuleList address: %x", nextInMemoryOList);
	printf("\n Dll Base address : %x", dllBase);
	printf("\n Dll full name address : %ls", dllnameaddress);

	PrintModulenameAndAddresses();
}

I hope this is clear now. In case any doubt/suggestions please mention in comments.

Happy Learning !!

4 responses to “PEB malware’s favourite place”

Leave a comment

Design a site like this with WordPress.com
Get started