In this video we'll learn about file stream exploitation, starting by answering the question
"what is a file stream?"
When you request access to a file on Linux, your process is issued with a file descriptor or fd for
short,
not to be confused with the forward pointer fd's we've been dealing with so far. File descriptors are
positive integers that your process can use as a handle to a resource.
But dealing with files via file descriptors can be cumbersome,
you can read and write, but often how we want to deal with files is more nuanced.
That's where GLIBC's file streams come in. They're wrappers around file descriptors that provide some
quality of life functionality like buffered I/O and undo
for example. They take the form of _IO_FILE structs in memory,
these are what functions like fopen() return pointers to.
So let's take a look at the _IO_FILE struct.
One way to quickly inspect GLIBC structs without having to trawl the source code is to use GDB.
If we load a binary into GDB, any one will do so I'll pick our friend
/bin/sh,
and issue the 'start' command to ensure its libraries get loaded,
we can use the 'ptype' command to inspect GLIBC's types.
You can pass the '/o' option to print the offset of each struct member
and don't forget to use the 'struct' keyword when inspecting structs.
In this case _IO_FILE structs are typedef'd to FILE
so we can shorten this command if we want.
pwndbg allows us to shorten this command further using the 'dt' or "dump type" command, which also gives
us neater output.
Be aware that at the time of recording the 'dt' command requires you to wrap the type in quotes
if there's a space in it. For example dt "struct _IO_FILE".
As I mentioned, _IO_FILE structs are wrappers around file descriptors.
The file descriptor in question is held in the '_fileno' member.
You can see that file streams consist primarily of character buffers, which are used to support those
features
I mentioned
like buffered I/O. We'll briefly cover some of the other fields that will be of interest to us during
this course.
There's a '_flags' member at the start of the struct that determines the state of the file stream.
There's also a '_chain' member,
its type indicates that it's a pointer to another _IO_FILE struct, these _chain members form the links
of a singly linked, non-circular list of all the open file streams in a process.
The head of that list is a symbol called '_IO_list_all'.
Every time a new file is opened by a function like fopen(), an _IO_FILE struct is created on the heap and
is linked into the head of the _IO_list_all list,
sort of like a fastbin for files.
The '_mode' field can be used to disable the file stream,
its usefulness will become clear later.
For now, let's revisit _IO_list_all
and note that its type is _IO_FILE_plus.
It wouldn't be GLIBC without a certain amount of indirection and the _IO_FILE_plus struct is
just a wrapper around the _IO_FILE struct that adds a vtable pointer to it, as we can see from the
output of this 'dt' command.
If you're comfortable with what vtables are and how they work, you can skip this next part.
For those of you unfamiliar with vtables, they're one way of implementing polymorphism in object-
oriented languages like C++ and we'll use an example to get familiar with them.
Say we have a class, 'car', that has a function that enables it to accelerate. When we derive further classes
from car, for example Skoda and Ferrari, we might want those different cars to accelerate at different
rates.
So we make accelerate() a so-called "virtual" function, simply meaning that Skoda and Ferrari can override
it with their own implementations.
When a car's accelerate() method is called directly from the car object
the call can go straight to the appropriate version of accelerate().
But consider a function that takes any type of car as an argument, then calls its accelerate() method.
This function could end up calling a Skoda's accelerate() method or a Ferrari's, and there's no way for
the compiler to tell which type of car it's going to receive at runtime.
This is known as polymorphism, and languages like C++ often don't specify how it should be implemented,
but one solution is to use vtables.
Now I am simplifying somewhat, but the basic principle is to give each class that uses virtual functions
a virtual function table or vtable.
Typically, there's a single vtable per class and each instantiated object of that class gets a pointer
to its appropriate vtable.
For example, our Skodas and Ferraris could each get a vtable that looks like this.
The third entry in the Skoda vtable holds the address of the Skoda accelerate() function and the third
entry in the Ferrari vtable points to the Ferrari accelerate() function.
Every Skoda or Ferrari object would also get a vtable pointer to its vtable.
Now when the function I described earlier needs to call the correct version of a car's accelerate()
method,
all it has to do is follow the object's
vtable pointer
and call the third entry in that vtable.
If you've ever exploited C++ binaries, you may have practiced vtable highjacking.
For those of you who haven't, vtable hijacking is simply the process of overwriting an object's
vtable pointer using a bug like an overflow,
then pointing it to a fake vtable that you control.
In this scenario, we could set the third entry of our fake vtable to the address of a one-gadget,
then the next time this object's accelerate() function was called, we'd get a shell.
So if vtables are used in object-oriented languages like C++, what are they doing attached to
_IO_FILE structs in the GNU C library?
Well, the GNU C and C++ libraries are tightly linked,
if you debug a C++ program on Linux, you'll notice that alongside the C++ standard library, the C
standard library has also been loaded.
This is because the GNU C++ library uses some of the C library's
low level functionality rather than reimplementing
it. For example if you debug a C++ program that opens a file and set a breakpoint on the C library's
fopen() function,
you'll notice that the C++ standard library is actually calling into the C standard library to perform
the low level file operations.
This is also true of C++ methods like make_shared() and make_unique(), which both use the C library's malloc
functions under the hood.
In the case of _IO_FILE_plus structs, they're given a vtable pointer to be compatible with the C++ streambuf
class,
and regardless of the reason, this makes GLIBC's file streams vulnerable to vtable hijacking, a form
of file stream exploitation and the method we're going to use to get our House of Orange shell.
So file streams are clearly valuable targets
and since new file streams are created on the heap, they're potentially vulnerable to the different
heap bug classes such as overflows and double-frees that we've leveraged so far.
But can we only target them when a process opens new files?
The answer is no.
Any process that uses the GNU C library will always have at least three file streams; stdin,
stdout and stderr, collectively known as the standard I/O file streams.
Even a program that prints no output and accepts no command line input
will have the standard I/O file streams present in the data section of its copy of GLIBC.
The same is true of more complex programs that use a GUI,
I'll use Firefox as an example since Ubuntu Linux comes with it installed.
Attaching GDB to Firefox
and printing the value at the head of the _IO_list_all list shows that stderr was the last
file to be opened,
represented by the stderr file stream. Following that file stream's '_chain' pointer takes us
to the stdout file stream, then to the stdin file stream where the chain terminates.
The 'xinfo' command shows us that the stdout file stream, represented by an _IO_FILE_plus struct,
resides in GLIBC's data section, along with stdin and stderr.
So even if our binary doesn't open any files or use the standard I/O file streams, they'll still be
present for us to leverage file stream exploitation against.
That's phase three of the House of Orange.
We're going to corrupt a file stream's vtable pointer or perhaps even inject our own fake file stream.
Then, when one of those files streams' member functions is called, execution will be redirected via our
fake vtable, gaining us arbitrary code execution.
Join me in the next video where we'll be learning about the second phase of the House of Orange, the
Unsortedbin Attack.