Comment on page

## Overview

When a chunk is removed from a bin, `unlink()` is called on the chunk. The unlink macro looks like this:
FD = P->fd; /* forward chunk */
BK = P->bk; /* backward chunk */
FD->bk = BK; /* update forward chunk's bk pointer */
BK->fd = FD; /* updated backward chunk's fd pointer */
Note how `fd` and `bk` are written to location depending on `fd` and `bk`- if we control both `fd` and `bk`, we can get an arbitrary write.
Consider the following example:
We want to write the value `0x1000000c` to `0x5655578c`. If we had the ability to create a fake free chunk, we could choose the values for `fd` and `bk`. In this example, we would set `fd` to `0x56555780` (bear in mind the first `0x8` bytes in 32-bit would be for the metadata, so `P->fd` is actually 8 bytes off `P` and `P->bk` is 12 bytes off) and `bk` to `0x10000000`. Then when we `unlink()` this fake chunk, the process is as follows:
FD = P->fd (= 0x56555780)
BK = P->bk (= 0x10000000)
FD->bk = BK (0x56555780 + 0xc = 0x10000000)
BK->fd = FD (0x10000000 + 0x8 = 0x56555780)
This may seem like a lot to take in. It's a lot of seemingly random numbers. What you need to understand is `P->fd` just means 8 bytes off `P` and `P->bk` just means 12 bytes off `P`.
If you imagine the chunk looking like Then the `fd` and `bk` pointers point at the start of the chunk - `prev_size`. So when overwriting the `fd` pointer here:
FD->bk = BK (0x56555780 + 0xc = 0x10000000)
`FD` points to `0x56555780`, and then `0xc` gets added on for `bk`, making the write actually occur at `0x5655578c`, which is what we wanted. That is why we fake `fd` and `bk` values lower than the actual intended write location.
In 64-bit, all the chunk data takes up `0x8` bytes each, so the offsets for `fd` and `bk` will be `0x10` and `0x18` respectively.
The slight issue with the unlink exploit is not only does `fd` get written to where you want, `bk` gets written as well - and if the location you are writing either of these to is protected memory, the binary will crash.

## Protections

More modern libc versions have a different version of the unlink macro, which looks like this:
FD = P->fd;
BK = P->bk;
if (__builtin_expect (FD->bk != P || BK->fd != P, 0))
malloc_printerr (check_action, "corrupted double-linked list", P, AV);
else {
FD->bk = BK;
BK->fd = FD;
}
Here `unlink()` check the `bk` pointer of the forward chunk and the `fd` pointer of the backward chunk and makes sure they point to `P`, which is unlikely if you fake a chunk. This quite significantly restricts where we can write using unlink.