PEB walk
2026-07-05
In C:
#include <windows.h>
#include <stdio.h>
#include <string.h>
FARPROC FindWinExec() {
#ifdef _WIN64
PPEB pPEB = (PPEB)__readgsqword(0x60);
#else
PPEB pPEB = (PPEB)__readfsdword(0x30);
#endif
PPEB_LDR_DATA pLDR = pPEB->Ldr;
LIST_ENTRY* pModuleList = &pLDR->InMemoryOrderModuleList;
LIST_ENTRY* pEntry = pModuleList->Flink;
while (pEntry != pModuleList) {
PLDR_DATA_TABLE_ENTRY pModule = CONTAINING_RECORD(pEntry, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks);
WCHAR* moduleName = pModule->BaseDllName.Buffer;
if (moduleName && (_wcsicmp(moduleName, L"kernel32.dll") == 0) ) { // case insensitive (important)
HMODULE hKernel32 = (HMODULE)pModule->DllBase;
// Ищем WinExec в kernel32.dll
FARPROC pWinExec = GetProcAddress(hKernel32, "WinExec");
if (pWinExec) {
printf("[+] Found WinExec at: 0x%p\n", pWinExec);
return pWinExec;
}
}
pEntry = pEntry->Flink;
}
return NULL;
}
void ParseExeFile(const char* filename) {
HANDLE hFile = CreateFileA(
filename,
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL
);
if (hFile == INVALID_HANDLE_VALUE) {
printf(" cant open %s\n", filename);
return;
}
IMAGE_DOS_HEADER dosHeader;
DWORD bytesRead;
if (!ReadFile(hFile, &dosHeader, sizeof(dosHeader), &bytesRead, NULL)) {
printf(" dos header fail\n");
CloseHandle(hFile);
return;
}
if (dosHeader.e_magic != IMAGE_DOS_SIGNATURE) {
printf(" DOS signature fail\n");
CloseHandle(hFile);
return;
}
SetFilePointer(hFile, dosHeader.e_lfanew, NULL, FILE_BEGIN);
DWORD peSignature;
ReadFile(hFile, &peSignature, sizeof(peSignature), &bytesRead, NULL);
if (peSignature != IMAGE_NT_SIGNATURE) {
printf(" bad pe sig\n");
CloseHandle(hFile);
return;
}
IMAGE_FILE_HEADER fileHeader;
ReadFile(hFile, &fileHeader, sizeof(fileHeader), &bytesRead, NULL);
printf("Number of sections: %d\n", fileHeader.NumberOfSections);
printf("Sizeof optional header: %d\n", fileHeader.SizeOfOptionalHeader);
IMAGE_OPTIONAL_HEADER32 optionalHeader;
ReadFile(hFile, &optionalHeader, sizeof(optionalHeader), &bytesRead, NULL);
printf("Entry point: 0x%X\n", optionalHeader.AddressOfEntryPoint);
printf("Image base: 0x%X\n", optionalHeader.ImageBase);
printf("\nSections: \n");
for (int i = 0; i < fileHeader.NumberOfSections; i++) {
IMAGE_SECTION_HEADER sectionHeader;
ReadFile(hFile, §ionHeader, sizeof(sectionHeader), &bytesRead, NULL);
printf(" %s - VA: 0x%X, Size: 0x%X\n",
sectionHeader.Name,
sectionHeader.VirtualAddress,
sectionHeader.SizeOfRawData);
}
CloseHandle(hFile);
}
void execute(cosnt char* fn) {
FARPROC pWinExec = FindWinExec();
if (pWinExec) {
typedef UINT (*WinExec_t)(LPCSTR, UINT);
WinExec_t WinExec_func = (WinExec_t)pWinExec;
WinExec_func(fn, SW_SHOW);
printf("--- %s executed\n", fn);
}
}
In x86:
; Establish a new stack frame
push ebp
mov ebp, esp
sub esp, 18h ; Allocate memory on stack for local variables
; push the function name on the stack
xor esi, esi
push esi ; null termination
push 63h
pushw 6578h
push 456e6957h
mov [ebp-4], esp ; var4 = "WinExec\x00"
; Find kernel32.dll base address , так же можно найти ntdll и проч
mov ebx, fs:0x30
mov ebx, [ebx + 0x0C]
mov ebx, [ebx + 0x14]
mov ebx, [ebx]
mov ebx, [ebx]
mov ebx, [ebx + 0x10] ; ebx holds kernel32.dll base address
mov [ebp-8], ebx ; var8 = kernel32.dll base address
; Find WinExec address
mov eax, [ebx + 3Ch] ; RVA of PE signature
add eax, ebx ; Address of PE signature = base address + RVA of PE signature
mov eax, [eax + 78h] ; RVA of Export Table
add eax, ebx ; Address of Export Table
mov ecx, [eax + 24h] ; RVA of Ordinal Table
add ecx, ebx ; Address of Ordinal Table
mov [ebp-0Ch], ecx ; var12 = Address of Ordinal Table
mov edi, [eax + 20h] ; RVA of Name Pointer Table
add edi, ebx ; Address of Name Pointer Table
mov [ebp-10h], edi ; var16 = Address of Name Pointer Table
mov edx, [eax + 1Ch] ; RVA of Address Table
add edx, ebx ; Address of Address Table
mov [ebp-14h], edx ; var20 = Address of Address Table
mov edx, [eax + 14h] ; Number of exported functions
xor eax, eax ; counter = 0
.loop:
mov edi, [ebp-10h] ; edi = var16 = Address of Name Pointer Table
mov esi, [ebp-4] ; esi = var4 = "WinExec\x00"
xor ecx, ecx
cld ; set DF=0 => process strings from left to right
mov edi, [edi + eax*4] ; Entries in Name Pointer Table are 4 bytes long
; edi = RVA Nth entry = Address of Name Table * 4
add edi, ebx ; edi = address of string = base address + RVA Nth entry
add cx, 8 ; Length of strings to compare (len('WinExec') = 8)
repe cmpsb ; Compare the first 8 bytes of strings in
; esi and edi registers. ZF=1 if equal, ZF=0 if not
jz start.found
inc eax ; counter++
cmp eax, edx ; check if last function is reached
jb start.loop ; if not the last -> loop
add esp, 26h
jmp start.end ; if function is not found, jump to end
.found:
; the counter (eax) now holds the position of WinExec
mov ecx, [ebp-0Ch] ; ecx = var12 = Address of Ordinal Table
mov edx, [ebp-14h] ; edx = var20 = Address of Address Table
mov ax, [ecx + eax*2] ; ax = ordinal number = var12 + (counter * 2)
mov eax, [edx + eax*4] ; eax = RVA of function = var20 + (ordinal * 4)
add eax, ebx ; eax = address of WinExec =
; = kernel32.dll base address + RVA of WinExec
.end:
add esp, 26h ; clear the stack
pop ebp
ret
In x64:
push rbp
mov rbp, rsp
sub rsp, 40h
xor rsi, rsi
push rsi ; \x0
mov rax, 00636578456e6957h ; "WinExec" LE
push rax
mov [rbp-8], rsp ; var8 = ptr to "WinExec"
mov rbx, gs:[0x60] ; rbx = PEB
mov rbx, [rbx + 0x18] ; rbx = PEB_LDR_DATA
mov rbx, [rbx + 0x20] ; rbx = InMemoryOrderModuleList (1 == the current PE file)
mov rbx, [rbx] ; rbx = ntdll.dll
mov rbx, [rbx] ; rbx = kernel32.dll
mov rbx, [rbx + 0x20] ; rbx = kerrnel32.dll base loading address
mov [rbp-16], rbx ; var16 = kernel32.dll base address
mov eax, [rbx + 3Ch] ; eax = RVA of PE signature
add rax, rbx ; rax = abs addr of PE signature
mov eax, [rax + 88h] ; x64 Export Table Offset == 0x88 (x86 == 0x78)
add rax, rbx ; rax = abs addr Export Table (IMAGE_EXPORT_DIRECTORY)
mov ecx, [rax + 24h] ; ecx = RVA of Ordinal Table
add rcx, rbx ; rcx = abs addr Ordinal Table
mov [rbp-24], rcx ; var24 = Address of Ordinal Table
mov edi, [rax + 20h] ; edi = RVA of Name Pointer Table
add rdi, rbx ; rdi = abs addr Name Pointer Table
mov [rbp-32], rdi ; var32 = Address of Name Pointer Table
mov edx, [rax + 1Ch] ; edx = RVA of Address Table
add rdx, rbx ; rdx = abs addr Address Table
mov [rbp-40], rdx ; var40 = Address of Address Table
mov edx, [rax + 14h] ; edx = Number of exported functions
xor eax, eax ; eax = счетчик (индекс) = 0
.loop:
mov rdi, [rbp-32] ; rdi = var32 (Name Pointer Table)
mov rsi, [rbp-8] ; rsi = var8 ("WinExec")
mov ecx, [rdi + rax*4] ; ecx = RVA N-го имени функции
add rcx, rbx ; rcx = абсолютный адрес строки с именем из таблицы
mov rdi, rcx ; rdi = для сравнения с "WinExec"
mov ecx, 8 ; длина строки "WinExec" = 8 байт
cld ; направление сравнения — слева направо
repe cmpsb ; сравнение строк
jz .found
inc eax ; счетчик++
cmp eax, edx ; if end
jb .loop
;FAIL
xor rax, rax
jmp .end
.found:
; eax хранит индекс совпадения в таблице имен
mov rcx, [rbp-24] ; rcx = var24 (Address of Ordinal Table)
mov rdx, [rbp-40] ; rdx = var40 (Address of Address Table)
; записи в таблице ординалов WORD-sized !!!
; ординал читаем как movzx, чтобы очистить старшие биты RAX
movzx eax, word ptr [rcx + rax*2]
; get RVA по ординалу (записи DWORD-sized)
mov eax, [rdx + rax*4]
add rax, rbx ; rax = абсолютный адрес WinExec
.end:
mov rsp, rbp
pop rbp
ret