Post

Finding Module Address through PEB

Finding Module Address through PEB

Introduction

Generally, programs resolve specific module addresses (such as kernel32.dll) by calling windows api like GetModuleHandle() or through native api like NtQuerySystemInformation. However, this leaves alot of traces and can be easily detected by EDRs or by simplying analysing the Import Address Table (IAT) in the PE strucuture of the binary program. To stay stealthy, malware devs resolve module addresses through PEB walk.

What is PEB?

When we execute a program, _TEB and _PEB structures are initalised by the kernel.

  • TEB (Thread Environment Block): A Structure that stores information specific to a thread
1
2
3
4
5
6
7
8
9
10
11
12
typedef struct _TEB {
  PVOID Reserved1[12];
  PPEB  ProcessEnvironmentBlock;
  PVOID Reserved2[399];
  BYTE  Reserved3[1952];
  PVOID TlsSlots[64];
  BYTE  Reserved4[8];
  PVOID Reserved5[26];
  PVOID ReservedForOle;
  PVOID Reserved6[4];
  PVOID TlsExpansionSlots;
} TEB, *PTEB;
  • PEB (Process Environment Block): A structure that contains information about the entire process, including loaded modules.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
typedef struct _PEB {
  BYTE                          Reserved1[2];
  BYTE                          BeingDebugged;
  BYTE                          Reserved2[1];
  PVOID                         Reserved3[2];
  PPEB_LDR_DATA                 Ldr;
  PRTL_USER_PROCESS_PARAMETERS  ProcessParameters;
  PVOID                         Reserved4[3];
  PVOID                         AtlThunkSListPtr;
  PVOID                         Reserved5;
  ULONG                         Reserved6;
  PVOID                         Reserved7;
  ULONG                         Reserved8;
  ULONG                         AtlThunkSListPtr32;
  PVOID                         Reserved9[45];
  BYTE                          Reserved10[96];
  PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoutine;
  BYTE                          Reserved11[128];
  PVOID                         Reserved12[1];
  ULONG                         SessionId;
} PEB, *PPEB;

Viewing Structures with WinDbg

Let’s verify these structures by opening a process in WinDbg

(Please note that the below images will only show the relevant fields and not all fields)

1
dt _teb

dt _teb

This command displays the _TEB structure. Take note of ProcessEnvironmentBlock field of which points to the _PEB. In a 64 bit architecture, the offset is 0x060 and In 32 bit, it is 0x030.

1
dt _PEB

dt _peb

This command display the structure of _PEB which has Ldr field which points to _PEB_LDR_DATA structure.

1
dt _PEB_LDR_DATA

dt _PEB_LDR_DATA

The _PEB_LDR_DATA structure contains InLoadOrderModuleList, InMemoryOrderModuleList, InInitializationOrderModuleList. We will focus on InMemoryOrderModuleList, which the head of a doubly linked list that contains the loaded modules of the process. Each item in the list is a pointer to an LDR_DATA_TABLE_ENTRY structure.

1
dt _LDR_DATA_TABLE_ENTRY

dt _LDR_DATA_TABLE_ENTRY

This command display the stucture of _LDR_DATA_TABLE_ENTRY which is a linked list. This structure holds DllBase(the base address of the loaded DLL) and the FullDllName, BaseDllName. These are teh values we need for a PEB walk. Take note of InMemoryOrderLinks field’s offset at 0x010. We will come back to it later.

PEB Walk in WinDBG

1
! _peb

! _peb

This command will display the current PEB information of the process running. Take note of loaded Dlls. At this moment, we are only interested in Ldr address, which is 00007ffd5ac1c4c0.

1
dt _PEB_LDR_DATA 00007ffd5ac1c4c0

dt _PEB_LDR_DATA 00007ffd5ac1c4c0

This display the _PEB_LDR_DATA of our current process. What intersts us is the InMemoryOrderModuleList address. This address points directly to the InMemoryOrderLinks field inside the _LDR_DATA_TABLE_ENTRY structure.

To view the very start of _LDR_DATA_TABLE_ENTRY structure properly, we can take the address 0x00000244ed3 and substract by 0x10 (the offset of InMemoryOrderLinks, see structure _LDR_DATA_TABLE_ENTRY for more details)

1
dt _LDR_DATA_TABLE_ENTRY 0x00000244ed3-10

dt _LDR_DATA_TABLE_ENTRY 0x00000244ed3-10

Perfect, now we can see FullDllName, BaseDllName and DllBase which is what we want. Currently the BaseDllName is helloworld.exe which is the executable of our process.

We will continue to navigate Flink pointer inside InMemoryOrderLinks and subtract 0x10 to get the loaded modules.

1
dt _LDR_DATA_TABLE_ENTRY 0x00000244ed4b2f30-10

dt _LDR_DATA_TABLE_ENTRY 0x00000244ed4b2f30-10

Now the BaseDllName is ntdll.dll.

1
dt _LDR_DATA_TABLE_ENTRY 0x00000244ed4b37b0-10

dt _LDR_DATA_TABLE_ENTRY 0x00000244ed4b37b0-10

Next the BaseDllname is kernel32.dll which is our generally the common goal for malwares.

1
dt _LDR_DATA_TABLE_ENTRY 0x00000244ed4b3e90-10

dt _LDR_DATA_TABLE_ENTRY 0x00000244ed4b3e90-10

Continuing the peb walk, we get BaseDllName kernelbase.dll

1
dt _LDR_DATA_TABLE_ENTRY 0x00000244ed4b6ad0-10

dt _LDR_DATA_TABLE_ENTRY 0x00000244ed4b6ad0-10

At the end we get, msvcrt.dll

Visual Recap

Visual recap

Resources

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