v0.1 - initial commit
This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
; 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
|
||||
Reference in New Issue
Block a user