Linux Heap Internals

Download as pdf or txt
Download as pdf or txt
You are on page 1of 80
At a glance
Powered by AI
The document discusses techniques for exploiting Linux heap overflows in the current version of GLIBC, including corrupting the free list, using mmap() and munmap() to manipulate memory pages, and overflowing chunks to rewrite GOT entries.

Techniques discussed include corrupting the free list by overwriting forward and backward pointers, overflowing chunks to change their size and merge memory regions, and allocating large chunks to prevent non-writable memory pages from existing.

The document explains how mmap() and munmap() can be used to mark memory pages as unmapped, allowing exploits to rewrite code/data that was previously in a non-writable page. Overflowing chunks can also force memory pages to be kicked out.

PLAY WITH LINUX HEAP

memeda@0ops
[email protected]

1
BACKGROUND

Linux heap becomes hard to exploit due to the new version of


GLIBC.

Hundreds of thousands of assertions there;

ASLR and Non-eXecutable heap.

Heap issues are scarce in CTF games.

spring up in recent games like HITCON CTF & Hack.LU CTF.


2
TOPIC

I’ll focus on Linux heap exploitation with current GLIBC.

Including some public unknown/unfamiliar tricks.

Whether you are a CTF player or a Linux pwner, I believe this


post is worthwhile for you to read through.

3
CATALOGUE
Introduction to GLIBC Heap

View Heap As an Attacker

free()

malloc()

main_arena

mmap() & munmap()

Examples
4
Introduction to GLIBC Heap

5
INTRODUCTION
GLIBC Heap Structure Chunk
PREV_SIZE SIZE
Mem
FD BK
Logical description
USER DATA
Next
Chunk PREV_SIZE SIZE

In memory
(x86_64 ELF)
6
INTRODUCTION

Notice that the range of a chunk is between LABEL Chunk and


LABEL Next Chunk.

PREV_SIZE represents the size of the previous chunk(in memory)


only if the previous chunk is free()’d.

SIZE represents the number of bytes between LABEL Chunk and


LABEL Next Chunk. If the lowest bit of SIZE is cleared as 0, then the
chunk before it is not in use(free).

LABEL Mem makes sense only when this chunk is malloc()’d by user.
&Mem is considered as the return value of malloc().
7
INTRODUCTION

For chunks of certain size range, there is a free list which is a linked list.

FD represents the forward pointer to the next chunk in the linked list.

BK represents the backward pointer to the previous chunk in the


linked list.

The above two fields only make sense when the chunk is free()’d.

User’s data is stored in the region which starts at LABEL Mem. Notice
that the region includes PREV_SIZE of the next chunk.
8
View Heap as an Attacker

free()

9
CORRUPT FREE I

Let’s begin at function free() but not malloc().

In the old version of GLIBC, there is a classical exploitation of


heap overflow with free().

Have a look at the vulnerable program in the next slide.

10
PROTOSTAR HEAP 3

11
PROTOSTAR HEAP 3
In old version of GLIBC malloc, there is function called unlink():

If BK and FD is controlled by us, then (in Linux x86)

*(FD + 12) = BK && *(BK + 8) = FD

AN ARBITRARY WRITE!
12
PROTOSTAR HEAP 3

Then the problem comes, why unlink() is called when 3 free()s called? Since free()
means linking the chunk into the free list.

THE ANSWER is when GLIBC free a chunk, it will going to see whether the chunk
before/after is free. If it is, then the chunk before/after will be unlink()’d off its
double-linked list and these two chunks merge into one chunk.

Pay attention, when *(FD + 12) = BK && *(BK + 8) = FD

You can use *(FD + 12) = BK to do arbitrary write.

But just keep in mind that BK + 8 is accessible.

Another question about this issue is DEF CON CTF 2014 Qual BabyHeap.
13
PROTOSTAR HEAP 3

So for this problem, you can overflow chunk a and overwrite the
heap header of chunk b.

If you set the lowest bit of SIZE of b to 0, then GLIBC fooled to


consider chunk a free()’d. Then unlink() is called on chunk a.

You can also set PREV_SIZE of chunk b to fool GLIBC where the
chunk b begins. Craft a fake chunk b there and get an arbitrary
write. :D

14
MODERN UNLINK
[!] New checking: FD->bk == P && BK->fd == P
15
CORRUPT FREE II
Let us have a look at Problem stkof from HITCON CTF 2014.

Pwnable worth 550 points. It is still available on http://ctf2014.hitcon.org/dashboard.html.

The scenario is simple:

You can give a size and malloc a chunk of this size.

There is a global array which records the pointer of every chunk you malloc()’d.

You can write arbitrary long content into the pointer in the global array.

You can free any of the chunk you malloc()’d before.

There is a global variable record the times you malloc()’d.


16
PWNABLE PROBLEM STKOF
As I mentioned
before, in this
scenario, there is a
variable counting
how much chunk
you’ve malloc()’d.
Here it is 3 at
0x602100.
3 chunks’ pointer are stored in a global array at 0x602140, which is 0xe05010,
0xe05040 and 0xe050d0 (Remember it is what we called LABEL Mem).

We can also writing anything long into the data area these pointers pointing to.
17
CORRUPT FREE II

Heap Overflow with free()’ called, everything is nice except that

FD->bk ?= P

BK->fd ?= P

When facing this, the MOST IMPORTANT thing is that find


somewhere in the memory in which the value is P.

PS: Geohot’s futex exploit trick (0x00bf0000) to bypass plist


checking in Android Kernel.
18
CORRUPT FREE II

In this problem, there is a global array which stores all the


pointer pointing to all the chunk. Notice that this pointer is
pointing to LABEL &Mem but not that start of the chunk!

So we need to craft a fake chunk at &Mem. That is not very


difficult because we have heap overflow which means the whole
control of the content of the chunk on the heap.

Another restriction shown in the code before is the if clause.

Just set P->fd_nextsize to NULL then skip if.


19
CORRUPT FREE II
Exploit from acez@Shellphish

https://github.com/acama/ctf-writeups/blob/master/hitcon2014/stkof/x.py

20
CORRUPT FREE II
As you can see, the exploit overwrite chunk 3, place a fake chunk at the location of chunk 4.

The fake heap header of chunk 4 fool GLIBC to believe that the chunk before chunk 4 is
chunk 2.

There is a fake chunk 2 at chunk 2’ Mem. Notice that 0x602150 is the pointer in the global
array which pointing to chunk 2’s Mem(key point here to bypass the check!).

Call free(4) to unlink fake chunk 2(I explained before why this time chunk 2 is to be
unlinked). Then after unlinking, an address in the range of &global_array itself is written into
the global array. That means we can rewrite the content of the global array directly.

If we control the global array, then we can control which pointer we can write into.

This is a case about heap overflow where SPECIFIC WRITE turns into ARBITRARY WRITE!
21
CORRUPT FREE II

Always keep in mind that

When malloc() returns, it gives you the pointer pointing to


LABEL Mem.

When processing some stuff in malloc.c, the pointer of a chunk


(whose type is struct malloc_chunk *) is pointing to LABEL
Chunk.

22
POISONED NULL BYTE

In August, Project Zero released a post about a GLIBC NULL byte off-by-
one exploitation.

The null byte will clear all the status bits of the SIZE of the next chunk.
When we try to free the next chunk, GLIBC is cheated to think the current
chunk is free and then unlink it. I mentioned this scenario just before.

The PREV_SIZE is actually the user data of the chunk but now it will used
to locate the chunk’s position relative to the next chunk.

The exploit described in the post is a local unprivileged exploit with Linux
32bit, which means we could half-disable ASLR and things become easy.
23
POISONED NULL BYTE
The exploit referred in the post used fd_nextsize and bk_nextsize to do the arbitrary write which is also a way besides
directly use fd and bk. Two things:
[1] P->fd_nextsize should not be NULL
[2] FD->fd_nextsize shouldd not be NULL
However, the target in the post is Fedora where that two asserts do not exist. When it comes to Ubuntu, ;-(
24
View Heap as an Attacker

malloc()

25
CHEAT MALLOC I

I mentioned that the free()’d chunks which has a size in certain


range will be put into one free list.

The head of the free list is in main_arena which I’ll cover it later.

The basic idea of this section is to CHEAT GLIBC to malloc the


chunk to the special place you want to. Then you can write to
that heap chunk, which means that you write to that special place.

If there are some important pointers or data structures at that


special place, then it is easy to finish the exploit then. ;-)
26
CHEAT MALLOC I

Let us begin with an easy example — fastbin.

When the chunk’s size is small enough, GLIBC called it fastbin.

Different sizes of fastbins also have their free list, but this time it
is a single-linked list.

As you can see later, the single-linked list makes things much
easier.

27
CHEAT MALLOC I
This picture shows two fast bins in
the free list which both have a size of
0x30.

Single-linked list header


0x7ffff7bd1770 pointing to Chunk
0x00e05090.

Chunk 0x00e05090’s FD pointing to


Chunk 0x00e05030.

Chunk 0x00e05030’s FD is NULL,


the end of this linked list.
28
CHEAT MALLOC I

How does GLIBC work when malloc()ing a fast bin?

It just mainly pick out the first (->fd) fast bin in the linked list, and
return it to the user.

That means if we control just one node of this linked list, then we
can cheat GLIBC to malloc the chunk where specified by us,
right?

How to do that?
29
CHEAT MALLOC I

Let us malloc two fast bins. (Btw, thanks to ricky zhou@PPP for his exploit)

1) Free the 2nd fast bin.

2) Overflow the 1st fast bin to change 2nd fast bin’s FD to our specified value.

3) Malloc one time. GLIBC return the 2nd fast bin to us, meanwhile the header of
the single-linked list points to our specified value. The value points to a fake fast bin
entry we crafted already.

4) Malloc another time.

5) Then just write into the new chunk which birth at the specified place you want to
and finish your exploit.
30
CHEAT MALLOC I

Easy to exploit since single-linked list is less troublesome, comparing


with double-linked list with countless security check with normal bins
in the new version of GLIBC malloc.c.

The only check for the fast bin entry in the list is the SIZE! Craft a
proper size for the fake fast bin entry.

Recall the problem STKOF, where the fake fast bin could be?

There is a counting variable at 0x602100, just add it up to a


proper value and it somehow could pretend to be SIZE right? (the
fake chunk at 0x602100 - 0x8)
31
CHEAT MALLOC I

If you can free any where you want, then just free the place you
could write(craft) directly. You don’t necessarily need to work on
the heap and do the overflow.

Or if you can overwrite the malloc()’d pointer to a specify value


and then free it, also take the same effect.

This scenario is mentioned in phrack Malloc_Des-Maleficarum.

32
CHEAT MALLOC II

One may ask whether I could cheat malloc() with normal bins
and double-linked list.

The answer is YES but things are much more complicated.

Let us first check out how does malloc() work this time?

33
CHEAT MALLOC II
I malloc 7 chunks
with normal size
and free 3 of them.
As you can see,
they are both in one
double-linked list.

Then I do
malloc(530).

34
CHEAT MALLOC II

As you can see, strange things happen. Now two double-linked


lists there.

In fact, double-linked list(0x7ffff7bd17b8) is called the unsort bins’


free list.

double-linked list(0x7ffff7bd19b8) is the real free list for the


chunk with size 0x210.

35
CHEAT MALLOC II

At first, all the free()’d chunks are put into unsort bin list.

When alloc() comes, GLIBC travels from the BK of the header of


the unsort bin list.

If the size is fit, then unlink this chunk and return to the user.

If not, put this chunk into its free list according to its size.

I am going to give an illustration about exploiting this.


36
CHEAT MALLOC II
Overflow Chunk 0xe05210
to change 0xe05630’s BK
to an address which points
to a craft heap chunk. The
chunk has a right
SIZE(0x211).

As you can see, after I


malloc(530), the header of
the unsort bin list’s BK
points to my fake chunk at
0xe10000.
37
CHEAT MALLOC II

A very important thing is that during the process shown before,


there is NO CHECKING like P->FD->BK == P and so on.

And after this, you are able to cheat GLIBC to malloc at the
place you want(in this case 0xe10000). But during this, Chunk
0xe10000 will be unlinked so make sure its BK->FD points to
0xe10000 this time.

For this case, I just bring out my ideas. Let me know if you have
other nice ways to exploit it.
38
CHEAT MALLOC III

There is another important concept called wilderness or the top


chunk.

In a word, if there is no proper chunk in the free list, then GLIBC


will splice a certain size out of the top chunk and then return it
to the user.

That means the top chunk usually has a large SIZE and be located
at the bottom of the heap (behind all the normal heap chunks).

39
CHEAT MALLOC III
Malloc_Des-Maleficarum also described a trick which cheats malloc by using the top chunk.

[1] You can use overflow to change the SIZE of the top chunk to 0xffffffff(Linux x86).

[2] Then you just malloc, and if the control flow goes into use_top, then actually you can
malloc whatever large size you want.

[3] You can craft a special size s. Then after malloc()ing, the top chunk’s address will change
to the original address plus s. In fact, we can specify any new address we want by specifying
s.

[4] If we malloc again, then we could get a chunk at the special place we want.

This trick may not make sense when meet ASLR since at most time the location of the top
chunk cannot be predicted.
40
View Heap as an Attacker

main_arena

predict heap chunk’s location despite ASLR

41
MAIN_ARENA

It’s the time for us back to the most basic and important
structure main_arena.

Hack the core then we control all.

42
MAIN_ARENA
main_arena is defined as below(Linux x86_64):

It is a structure of type malloc_state and the size is 0x888. 43


MAIN_ARENA

Array fastbinY

The value in each entry represents the head of (single-linked)


free list of the fastbins which has a size in certain range.

top

It is the top-most chunk. When there is no good target chunk


for a new malloc request in these free lists, it is used. (label
use_top in function _int_malloc)
44
MAIN_ARENA

Array bins

Heads of all the (double-linked) free list of different size ranges.

next

Pointing to the next arena.

system_mem

Memory allocated from the system in this arena.


45
FAKE MAIN_ARENA

A POINTER pointing to arena structure is near TLS.

How to use heap overflow to rewrite it? I’ll talk about it later.

Now, let us have a discussion on how to do an exploitation when


we could specify the arena which we can fully controlled.

46
FAKE MAIN_ARENA

The main idea is to specify the head of the linked list in the arena,
and then cheat GLIBC to malloc a new chunk at that specified
place we prefer.

But things are not very easy. The specified place(the fake chunk)
must satisfy many conditions, otherwise malloc will go to
failure. ;-(

47
FIGHTING WITH ASSERTIONS

Aug 18 07:55:39 <tomcr00se> how did you link in 0x602100


without throwing one of 10 million asserts

48
FIGHTING WITH ASSERTIONS

Let us trace back the source.

Before _libc_malloc exit, there is an assert on victim, the chunk which is


going to return to you.

[1] If victim == NULL :D just don’t care this

[II] If the chunk is labelled mmapped

A chunk is mmapped when its SIZE has 0x2 bit set. 49


FIGHTING WITH ASSERTIONS

[III] If ar_ptr == victim’s arena ptr

Due to the faked arena we crafted, chunk_non_main_arena will return true.

If the SIZE has 0x4 bit set, it means NON_MAIN_ARENA.

And it is very hard to control the value which address (ptr &
~(HEAP_MAX_SIZE - 1)) pointing to.

It is very hard to control on Linux x86_64. If ptr is not big enough, it


will cause NULL pointer dereference.

So in a word, the best choice is to satisfy [II] and then bypass this assert. 50
BTW…

Actually when we call free(p), it will call arena_for_chunk(p) to get


the arena of the chunk.

What if we use heap overflow to set a chunk’s


NON_MAIN_ARENA bit?

It will then try (ptr & ~(HEAP_MAX_SIZE - 1))->ar_ptr to get


the arena pointer…

That may cause problems, which is mentioned in phrack


Malloc_Des-Maleficarum.
51
FIGHTING WITH ASSERTIONS

To satisfy [11], you faked chunk’s SIZE should have 0x2 bit
(mmapped).

If you deep into _int_malloc, you will find out that sometimes
before it return p; to exit, it will finally reset the head of the chunk
which it will then return to you, which means you’ll lose the 0x2
bit.

We do not hope that happens.

52
FIGHTING WITH ASSERTIONS

3 scenarios where the head of our crafted chunk won’t be reset

fastbin size range;

corresponding small bin’s free list is not empty;

a free()’d candidate chunk in unsort bin free list and the size of
free()’d candidate chunk in the free list is exactly the same as
our crafted chunk’s size.

53
FIGHTING WITH ASSERTIONS

Again, fastbin becomes our first choice, since we must deal with FD
and BK when use whether normal small bin or normal large bin.

[1] A crafted chunk you want to malloc on which has a proper SIZE
(0x2 bit set).

Remember an non-NULL illegal FD of this chunk will not make a


crash during this malloc() but will bring a side effect in the
future.

[II] The entry of fastbinY’s free list should be set to the address of
your crafted chunk. (&SIZE - 0x8 on Linux x86_64).
54
A FAKE ARENA EXAMPLE
You do not need to specify the whole 0x888 bytes. For example, if you use fast bin to exploit, you
may just specify the first several bytes. The picture above is for Linux x86_64. 55
DEFEAT ASLR

Now let’s back to the problem: where is the pointer of


main_arena? How could we touch it?

If the size you want to malloc is not less than 128KB, then GLIBC
may use mmap() to allocate a new area for you.

There are many gaps in the program’s VM map, and GLIBC will
use these large gaps to satisfy your malloc request.

56
DEFEAT ASLR
main_arena is in the marked area(TLS is there), and if we can malloc a chunk before this area and
overflow it, then we can overwrite main_area. 57
DEFEAT ASLR

Although the existence of ASLR, I would like to say that it is


possible to know it is the time I malloc()’d just before TLS.

In fact, the location of the chunk you malloc()’d could be


predicted even on Linux x86_64.

58
DEFEAT ASLR

Let’s consider a program(stkof) which doesn’t have any chunk


malloc()’d before the user’s input comes.

[1] Then I try to malloc(2147483648) as many times as possible.


(2G)

[2] And then I malloc(135168). The chunk will be located just


before TLS. I will fill it with As to give an illustration.

Note that all the malloc()s in [1]&[2] will actually call mmap().
59
_nl_global_locale

arena

DEFEAT ASLR
The chunk just before arena is located at 0x7f0313248010. And with no surprise, the pointer of arena
is at 0x7f031326a700. I am able to overflow it for sure. Btw, the stack canary is at 0x7f031326a768. 60
DEFEAT ASLR

Before the pointer of the arena, there are some values.

Actually most of these values can just be overwritten to 0x0


except the first entry(which is located at 0x7f031326a690 in the
previous picture).

Its symbol is _nl_global_locale.

And is used in ____strtoll_l_internal.

61
DEFEAT ASLR

We should put an accessible pointer


for a fake _nl_global_locale. And
think it as %r8, make sure that these
will not cause a segmentation fault:

mov 0x8(%r8), %rax

mov 0x68(%r8),%r15

testb $0x20,0x1(%r15,%rax,2) 

Here %rax usually range


from ord(‘0’) to ord(‘9’)

62
DEFEAT ASLR

OK! Now just replace the pointer of the arena with an address
which a fake arena structure is located at there.

BUT! Due to ASLR, you may not know the exact address of your
fake arena structure ;-(

63
DEFEAT ASLR
Actually there are two gaps and the location of these two gaps are easy to predicted.
64
DEFEAT ASLR

When I malloc() for hundreds of thousands of times, the process will


try its best to bring out its virtual memory to satisfy my demand.

And finally the chunk will be located between 0x10000 - 0x400000 and
0x401000 - 0x601010.

As you can see, I get two chunks, one is at 0x10000 and one is at
0x401000.

* You can just put the fake arena and the fake _nl_global_locale in the
0x10000 chunk and then overwrite the pointer of the arena to
0x10010 (0x10000 + 0x10 is the beginning of the user data).
65
FURTHERMORE

The main_arena exploit on x86 is much more easier so I do not


mention here.

A writeup from 217 refers this:

http://217.logdown.com/posts/241446-isg-2014-pepper

66
View Heap as an Attacker

mmap() and munmap()

67
OWN THE PAGE

Here I’m going to talk about a very powerful trick.

Turn a r— page into rw- page without calling mprotect()


or even unmap .text page.

Special thanks to jerry@217 for sharing with me this idea.

68
OWN THE PAGE

As I mentioned before, if a chunk’s SIZE has 0x2 bit, it means this


chunk is MMAPPED.

And if we want to free a MMAPPED chunk, GLIBC will not call


free() routine for normal heap chunk but munmap()!

If you could overwrite the SIZE of a chunk, set its 0x2 bit and any
large size you want, then free it!

* The memory of the size will be directly kicked out!


69
OWN THE PAGE
As I mentioned before, I could
malloc()’d for thousands of
times to push GLIBC to
malloc a chunk at 0x300000
and 0x200000.

I overflow the chunk at


0x200000 to make 0x300000’s
SIZE to 0x200002.

As you can see, 0x400000 -


0x401000 is .text section of
the process.
70
OWN THE PAGE
It is really interesting right? Memory from 0x300000 to 0x500000 is munmap()’d by me. $rip is at
0x400b7f but it is an illegal address now! 71
OWN THE PAGE
GOT is in the marked page. We could allocate a chunk in 0x401000-0x601000, overflow it and
rewrite GOT. 72
OWN THE PAGE
The sad thing is that if we want to overwrite GOT, we must cross 0x601000-0x602000, but
however this page is non-writable! 73
OWN THE PAGE

I use heap overflow to change the


SIZE of chunk to 0x101002. Then
free it!

With no surprise, memory from


0x501000 to 0x602000 is kicked
out.

That means the non-writable


page has already gone away.

74
OWN THE PAGE
What will happen if we malloc(1052600) then?
:D Non-writable page 0x601000-0x602000 will never be existed. Just overflow it to write GOT! 75
OWN THE PAGE

It is a very interesting trick about mmap() and munmap() in


GLIBC heap management.

And keep in mind that the fake SIZE of the chunk you set must
be times of a page size, due to ASSERT ;-(.

76
RESOURCES
https://code.google.com/p/google-security-research/issues/detail?id=96

There are some ‘null byte off-by-one on heap’ war-game style programs, which are all
good materials to exercise Linux heap pwning.

CTF Pwnables

STKOF https://github.com/hitcon2014ctf/ctf/raw/master/
a679df07a8f3a8d590febad45336d031-stkof

OREO https://github.com/lovelydream/CTF/blob/master/
oreo_35f118d90a7790bbd1eb6d4549993ef0

PEPPER https://github.com/lovelydream/CTF/blob/master/
pepper_e87791048cc540b725046a96d6724d8b
77
END…

I’ve tried my best to refer all the ways I know to exploit the heap
overflow with the newest version of GLIBC.

Basically I do not consider PIE in the slides but ASLR & NX.

The gdb plugins I used to help debugging heap is libheap.

If you have any other fantastic heap exploit tricks, please share
with me.

Thanks and look forward to your suggestion.


78
REFERENCE
[1] http://conceptofproof.wordpress.com/2013/11/19/protostar-heap3-walkthrough/

[2] http://acez.re/

[3] https://rzhou.org/~ricky/hitcon2014/stkof/test.py

[4] https://www.blackhat.com/presentations/bh-usa-07/Ferguson/Whitepaper/bh-usa-07-ferguson-WP.pdf

[5] http://217.logdown.com/posts/241446-isg-2014-pepper

[6] http://googleprojectzero.blogspot.sg/2014/08/the-poisoned-nul-byte-2014-edition.html

[7] http://sebug.net/paper/phrack/66/p66_0x0A_Malloc_Des-Maleficarum.txt

[8] http://www.phrack.org/issues/57/8.html

…and glibc source for sure…


79
THANKS TO

❤Team 0ops

@217 winesap jerry

@Blue-Lotus Kelwin

@KeenTeam Liang Chen

80

You might also like