Hello everyone, and welcome back to Mastering Software Exploitation.
I'm excited as we reach to our final section, and in this section we are going to learn about Heap
Exploitations 101, which means that we are going to show a precise example of a step by step exploitation
of an overflow vulnerability in Linux systems.
So let's dive in first as always.
Definitions.
So what is the heap memory heap is a dynamically allocated memory, which means that every time you
use malloc or new or choose your preferred language, this buffer or area is being allocated in the
dynamic memory of the system.
The way that the system actually manages the heap memory is different, uh, and OS specific.
So by management, I mean the mechanisms to track free memory blocks, allocated memory object sizes,
and how I want to arrange different sizes and other types of metadata to actually control and manage
all those different processes, threads that are asking memory to make sure that I can manage that for
them in the heap memory.
We are going to focus on the Linux slab allocator, which is the standard memory allocator in Linux
kernel.
One thing to note about exploiting heap memory or prerequisite of exploiting a heap memory vulnerability,
is that the first critical step is actually thoroughly understand how your target system manages the
memory.
This sounds easy, like you can read some documentation on Google or something like that, but it's
actually not even Linux kernel doesn't have the heap memory management completely documented, um,
in a way that is easy to just read a blog and understand.
Many times there is a need to dive into the code and understand the nuances of what's happening when
all of the cache is full, when specific buckets are full, when a lot of objects are being freed at
the same time.
Is it the same like a few objects being released one by one?
Those sorts of things, especially in sophisticated systems with multiple CPUs and cache per CPU, is
actually crucial to your heap exploitations.
So first advice is really understand the memory manager of the system that you are targeting.
What is the heap memory in Linux kernel then?
So in Linux kernel, we manage the memory in fixed size chunks that are called slabs.
This basically means that if I have a 30 bytes buffer and 28 bytes buffer, they are all going to go
to the cache k malloc 32.
So we have uh the slab maintaining size caches.
And when we call malloc or k malloc if we are in the kernel the memory comes for the corresponding slab.
There is no metadata stored in the object.
And the pointer that we get as a return of the allocation is actually the beginning of the usable memory
that we can use.
So you can see down, uh, in illustration we just have objects one by another.
Some of them are free, some of them are used.
And it's a simple visual of one, uh, slab Cash in the memory.
So let's take it a step even more abstract than that.
Um, and this is basically what we have reused.
Free used, uh, objects.
This, of course, is being changed.
If someone is freeing this use object, it becomes free.
If someone is allocating a memory, it can get a pointer to that free space here and so on and so forth.
And now a naive example of a vulnerability in the heap.
So very similar to the Stack Overflow vulnerability we've seen in section one.
We actually have a buffer here.
Only this time the buffer is allocated via malloc.
Malloc actually causes the kernel to allocate a buffer in the heap memory.
And in this case the buffer is 256 56 bytes.
Which means we are going to get into k malloc 256.
Now this buffer is going to be allocated somewhere in the slab before and after.
They are going to be many different types of objects.
We don't know who they are, they are used or not.
But what we do know is if we overflow this buffer on the heap, we are going to corrupt what's next,
what's actually in the memory after our vulnerable buffer?
Do we know what it is?
We don't.
And this is exactly the magic in heap exploitations into Stack Overflow example.
If you remember we know exactly what are we going to overwrite?
The return address is always there for us.
We can count on the return address to be there, and we can count on the return address to be executed
when the function exits.
This is perfect, right?
We have an address that is being executed.
We know where it is and we can overflow it easy in the heap.
It's quite difficult.
We don't know what we are going to overflow, and we don't even know if this memory is being used in
any interesting way.
Remember to exploit.
We have to cause an unintended behavior of the system.
If the memory after our vulnerable buffer is not being used by anything interesting, that can influence
the execution flow of the program.
We are not doing anything.
We didn't exploit anything.
So we understand that we need to somehow control and predict what will be after us in the memory.
But how can we do that?
The memory so sophisticated blocks of memory are being allocated and freed all the time.
The kernel is managing the memory for all of the processes.
This sounds like almost impossible, but I will walk you through a simple steps that makes it very possible.
So our primary goal in exploiting a heap vulnerability is to override some kind of a function pointer
so we can redirect the execution flow.
And this is what we are going to focus on in this section.
The key exploitation strategies in heap are called heap feng shui or heap shaping, which is basically
precisely arranging the heap layout to position vulnerable objects that we think we can use to exploit
next to our target object.
Next, free exploitation techniques are not covered in this section, but it's another thing that you
can explore on your own time.
So successful exploitation basically requires shaping the heap to our desire.
And now let's talk and define target objects in hip exploitations, we must control what memory we overflow
into.
So in the illustration, you can see we have the vulnerable object allocated somewhere we don't know
where.
And we're asking ourselves, who are you?
This memory that is after us.
We don't know.
All we can do right now is overflow the vulnerable object.
So we need to control the object that's being placed after sometimes before the vulnerable object.
Depending if we have an underflow or an overflow.
So we can overflow a memory that is strategic to us and not just random memory.
Let's call this desired strategic object that we want to overflow the target object, the thing, the
object that we want to get right here after the vulnerable object.
The target object has very specific characteristics, and I'll divided it to three main critical criterias
to be.
Able to classify as a target object.
The first one is controlled allocations.
This basically means that we, as the attacker that has some kind of outside communication to the system,
is able to trigger allocations of the target objects on the heap.
This means that I can perform certain operations if it's asking something, if it's sending, uh, certain
packets to the system, and in reaction, the system is causing a certain object or target object to
be allocated on the heap.
Put simply, the attacker is able to cause the target object to be allocated on the memory in its own
discretion.
It can do something that will cause target object to be allocated.
The second criteria is that the target object has to contain a function pointer.
If not a function pointer, then some other critical element that we want to control.
But usually, and for the sake of simplicity, in this example, we want the target object to have a
function pointer so that we can overwrite it with our malicious address, which can be a beginning of
a rope chain depending on the system that we exploit.
The third criteria is that this function pointer has to be Triggerable Triggerable.
Sorry Triggerable execution.
So we must be able to trigger the execution of this function pointer that we override.
Otherwise we will have a function pointer that we override and with malicious address.
But that malicious address never gets executed kid, right?
So we need to somehow make sure that we can cause this function pointer to be executed, or just count
on the system to reach to this function pointer at some point, but better to control always.
So let's take a step by step walkthrough of heap Feng Shui.
First step is finding this target object.
We just defined exactly what we are looking for.
Now it's time to go to the system that we want to exploit and actually find the target object that we
can use.
Usually we will take a look at existing objects of the system, like file structs and those sort of
things, and see if some of the objects there can be useful for us as a target object.
Second is spray.
We call it spray the heap.
So what it means is that we are going to make and cause the system to allocate so many of these target
objects, so that eventually the heap will be filled with many, many, many, many, many target objects.
So I jumped a little bit here, but let's stay focused on step number two.
So requirement number one of target objects is that we can control their allocations.
If we can control their allocations we can just decide to allocate 1000 of target objects.
Why not.
We can do that.
And then what will happen is that the heap memory is at some point we're going to be filled with so
much target objects that hopefully and actually most likely one of those target objects are going to
be allocated right next to our vulnerable buffer.
Imagine it this way.
You have a function that is exploiting the system.
This function opens two threads.
One of them is spraying the heap with 2000 target objects being allocated one by one, and the other
thread is allocating the vulnerable buffer and overflowing it.
Imagine what will happen.
We have one heap and those threads are running simultaneously.
Most likely the vulnerable buffer will be allocated next to one of those target buffers that the other
threads keep spraying.
This is exactly what we want.
The third step is to trigger the vulnerable code.
Now that we have the target buffer, the target object allocated right next to the vulnerable memory,
we can trigger the vulnerability and overflow the memory.
This will Uh, overflow directly to our target buffer.
At this point, we have a target object with a function pointer.
Right.
Because requirements number three means a number two means the target object has a function pointer.
So the only thing we need to do is to trigger.
Now the use of this now malicious function pointer that we overflow.
We can do that because we took care of it.
In step number one we chose an targets object that contains a function pointer that we can trigger the
execution of.
So at this point we have the heap looking some something like that target object again and again and
again.
At some point the vulnerable object allocated between a few target objects.
In this target object there is a function pointer somewhere Error, and we perform the overflow to cause
this function pointer to point to a malicious address.
And then we trigger the execution of the function pointer to execute malicious code on the system.
To make it even more real.
Let me show you an example.
A real one simple one from Linux systems.
So the struct file in Linux, one of the most used files, uh, most used structs, sorry.
In Linux.
And one of the most known objects as well is actually a great target object.
Let's see why.
First, it has a controlled allocation trigger.
Basically, every system call such as open socket and almost most of the user space operations.
Create a new struct file allocations in the kernel memory space.
This means that from an outsider perspective attacker perspective, almost every interaction that I
do with the with the with the system, with the target system that I I'd like to will probably cause
the system to allocate a file operation, a file structure.
So that's that's great because as you know, in Linux a lot of things happen through Syscalls.
And most of the syscalls use struct file.
So that's good for us.
Second it has many function pointers.
So every struct file contains file operation structure.
And it contains multiple function pointers that can be overwritten to redirect the execution flow as
we need.
Lastly, it is also quite simple to trigger the execution of one of those function pointers at least.
So simply performing standard operations on the file that was opened like read, write, pipe, and
so on will execute the compromise function pointer from the um, from the file operations inside the
struct file.
So at this point, struct file seems like a great target object to be placed right after the vulnerable
object.
The function pointer that we are going to override is going to be one of the functions in file operations.
We will choose the actual one according to which one we can trigger.
If we can trigger a pipe, a write or read, and so on.
In most cases this can be used as a privilege escalation vulnerability, which means the attacker already
have the ability to run code on the user space.
Let's say you downloaded something from the internet and it has malicious code in it.
This originally is running the user space, but running on the user space gets the attacker the ability
to create a new file and perform operations on the file like read write pipe.
This means that with an additional heap overflow vulnerability and these capabilities to control what
gets what gets allocated on the heap, the attacker on the user space can perform a privilege escalation
to execute code on the kernel.
And a real world example is exactly that.
Uh, this is a pipe overflow is actually a local privilege escalation vulnerability, because a completely
remote attacker that does not have any access to the system cannot really trigger the vulnerability.
So in order to exploit this vulnerability, we assume the attacker already have some user space, uh,
code execution.
This is why it's called local privilege escalation.
But just as a note, there are heap overflows that can be exploited completely remotely and not just
privilege escalation vulnerabilities.
I highly recommend those, uh, further reading, because heap exploitations are getting more and more
complicated, and it depends on the Isn't the vulnerability that we need to exploit.
Remember, an exploitation is always connected to a specific vulnerability, and sometimes it requires
us to get even more sophisticated.
And these examples are good, and there are many more online.
But the basics of what we are trying to achieve, what we need to learn to achieve that and how it works
is being presented here.
Just to give you a head start on getting into the deep waters of hip exploitation, by the way, this
vulnerability, the CVE mentioned here has a publicly available exploit code.
So you can actually take a look of a real vulnerability and exploitation.
What's next?
So first you're going to write your first exploitation exploit.
The lab is going to take you step by step so that everything will be clear and you can practice what
we've learned.
And perhaps in the future I will, uh, present an advanced course covering even more interesting topics
in real world exploitations and how they look like for security engineers, researchers, and just everyone
that want to be more familiar with that subject.
I really enjoy.
I really hope you enjoy the course.
Thank you so much for attending and feel free to contact me.
Always happy to to hear feedback and think about other ideas on additional courses or changes to this
one.
So please feel free to reach out and I hope this has been helpful for you.
Thank you so much.