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, &sectionHeader, 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