; resolver.inc — shared x64 position-independent building blocks for winpwn ; shellcraft templates: find kernel32's base via the PEB (no hardcoded ; addresses) and resolve any export by name (no hashing, just a linear ; name-table scan — simple, auditable, and plenty fast for a handful of ; one-shot resolutions). ; ; Calling convention (our own, not WinAPI): standard x64 fastcall-ish, ; args in RCX/RDX, result in RAX. Both routines preserve every ; non-volatile register they touch, so callers can keep values live in ; r12-r15 across calls to either of these. ; get_kernel32_base() -> RAX = kernel32.dll base address ; ; Walks PEB->Ldr->InMemoryOrderModuleList. Entry 0 is always the running ; executable itself, entry 1 is ntdll.dll, entry 2 is kernel32.dll — this ; fixed load order is the same trick essentially every public Windows x64 ; shellcode relies on. get_kernel32_base: mov rax, [gs:0x60] ; TEB->ProcessEnvironmentBlock (gs:0x60 on x64) mov rax, [rax+0x18] ; PEB->Ldr mov rax, [rax+0x20] ; Ldr->InMemoryOrderModuleList.Flink -> entry0.InMemoryOrderLinks mov rax, [rax] ; entry0.Flink -> entry1 (ntdll.dll) mov rax, [rax] ; entry1.Flink -> entry2 (kernel32.dll) mov rax, [rax+0x20] ; entry2.DllBase (DllBase sits 0x20 past InMemoryOrderLinks) ret ; find_export(RCX = module base, RDX = pointer to NUL-terminated ASCII name) ; -> RAX = absolute address of the export, or 0 if not found. ; ; Walks IMAGE_EXPORT_DIRECTORY.AddressOfNames linearly comparing each ; entry against the target string, then follows AddressOfNameOrdinals and ; AddressOfFunctions to the RVA. NT_HEADER+0x88 is the canonical ; DataDirectory[0] (export table) offset for PE32+. find_export: push r12 push r13 push r14 push r15 push rsi push rdi push rbx mov r12, rcx ; r12 = module base (preserved for the whole routine) mov r13, rdx ; r13 = target name pointer (preserved) mov eax, [r12+0x3C] ; e_lfanew add rax, r12 ; rax = NT header VA mov eax, [rax+0x88] ; export dir RVA (OptionalHeader64.DataDirectory[0].VirtualAddress) add rax, r12 mov r14, rax ; r14 = export directory VA mov ebx, [r14+0x18] ; ebx = NumberOfNames mov r9d, [r14+0x20] ; AddressOfNames RVA add r9, r12 ; r9 = AddressOfNames VA (array of DWORD RVAs -> name strings) mov r10d, [r14+0x24] ; AddressOfNameOrdinals RVA add r10, r12 ; r10 = AddressOfNameOrdinals VA (array of WORD ordinals) mov r11d, [r14+0x1C] ; AddressOfFunctions RVA add r11, r12 ; r11 = AddressOfFunctions VA (array of DWORD RVAs -> code) xor r15, r15 ; r15 = loop index i .loop: cmp r15, rbx jge .notfound mov eax, [r9 + r15*4] ; nameRVA for entry i add rax, r12 mov rsi, rax ; rsi = candidate name VA mov rdi, r13 ; rdi = target name VA (reset every attempt) .cmp_loop: mov al, [rsi] mov cl, [rdi] cmp al, cl jne .next test al, al je .found ; both hit NUL with every byte equal -> match inc rsi inc rdi jmp .cmp_loop .next: inc r15 jmp .loop .found: movzx rax, word [r10 + r15*2] ; ordinal index for entry i mov eax, [r11 + rax*4] ; function RVA add rax, r12 ; absolute address jmp .done .notfound: xor rax, rax .done: pop rbx pop rdi pop rsi pop r15 pop r14 pop r13 pop r12 ret ; resolve_export(RCX = kernel32 base, RDX = DLL name ptr or 0, R8 = func name ptr) ; -> RAX = absolute address of the export, or 0 on any failure. ; ; find_export alone only works if the target module is *already* loaded ; into the process -- fine for kernel32 itself, not fine for e.g. user32.dll ; in a plain console process. When RDX is 0 this is exactly find_export ; (the function is assumed already loaded at the module base in RCX). ; When RDX is a DLL name pointer, LoadLibraryA is resolved out of kernel32 ; (itself just another find_export call) and called to load-or-fetch the ; named module, then find_export resolves R8 inside *that* module's base. resolve_export: push r12 ; kernel32 base push r13 ; dll name ptr (or 0) push r14 ; func name ptr push rbp ; stash pre-align rsp, same pattern as winexec_x64.asm's start mov rbp, rsp and rsp, ~0xF mov r12, rcx mov r13, rdx mov r14, r8 test r13, r13 jnz .need_loadlibrary mov rcx, r12 mov rdx, r14 call find_export jmp .resolve_done .need_loadlibrary: mov rcx, r12 lea rdx, [rel name_loadlibrarya] call find_export test rax, rax jz .resolve_fail mov rcx, r13 ; LoadLibraryA(lpLibFileName) sub rsp, 0x20 ; shadow space required before any WinAPI call call rax add rsp, 0x20 test rax, rax jz .resolve_fail mov rcx, rax ; the newly (or already) loaded module's base mov rdx, r14 call find_export jmp .resolve_done .resolve_fail: xor rax, rax .resolve_done: mov rsp, rbp pop rbp pop r14 pop r13 pop r12 ret name_loadlibrarya: db "LoadLibraryA", 0