Exploit-Exercise之Protostar-heap

0x00
Protostar涉及栈溢出、堆溢出、格式化字符串漏洞、网络编程、及综合性漏洞。
本文将介绍heap部分。

关于环境准备,在官网https://exploit-exercises.lains.space/protostar/下载即可。下载后得到iso镜像,使用vmware安装。然后使用user/user即可登录
查看ip
在这里插入图片描述

知道ip后可以在kali中ssh连上
在这里插入图片描述

输入user即可
机器上所有需要分析的程序都在如下路径
在这里插入图片描述

0x01
第一关heap0
在这里插入图片描述

这里可以看到存在缓冲区溢出的地方在d->name,我们输入的数据会赋给d-

name,其大小为64字节,如果溢出,则会继续覆写到f->fp;本题如果在正常的执行情况下会执行nowinner(),而攻击者可以通过覆写f->fp,控制程序执行流程,从而执行winner()
我们使用ltrace进行跟踪,来分析malloc调用的情况
ltrace /opt/protostar/bin/heap0 perl -e 'print "A"*64'
在这里插入图片描述

可以看到f->fp地址为0x0804a050,d->data地址为0x0804a008
之间的偏移为72字节,那么我们只要填充72字节,再加4字节来覆盖f->fp,这样就可以控制执行流了
我们使用gdb调试看看
run perl -e 'print "A"x72 ."BBBB"'
在这里插入图片描述

可以看到,确实可以劫持控制流了
接下来来找到winner的地址
在这里插入图片描述

找到地址后表示为小端序,然后替换前面那个pyaload中的BBBB即可
/opt/protostar/bin/heap0 perl -e 'print "A"x72 ."\x64\x84\x04\x08"'
在这里插入图片描述

可以看到已经执行了winner()

0x02
第二关 heap1
在这里插入图片描述

程序中分配了两个internet结构体,每个结构体都有一个单独分配的name指针,这意味说被分配在堆上的internet结构体包含一个指针,该指针指向位于堆上的另一处的内存空间,即指向接收用户输入的buffer。
这里用户要传入两个值,分别赋给i1->name,i2->name
同样通过ltrace分析
在这里插入图片描述

从上图可以看到
1)internet1位于0x0804a008(8字节大小),internet1.priority位于 0x0804a008(4字节大小),所以internet1.name位于0x0804a00c(0x0804a008+4),而由internet1.name指向的内存地址位于0x0804a018(8字节大小)
2)internet2位于 0x0804a028(8字节大小),internet2.priority位于 0x0804a028(4字节大小),internet2.name位于0x0804a02c(4字节大小),由internet2.name指向的内存地址位于0x0804a038(8字节大小)
我们可以控制的是internet1.name和internet2.name
这里的攻击思路是这样子的,因以程序最后只会调用puts(),而没有调用winner,所以我们可以考虑在调用puts时将其劫持,使其指向winner()
具体的操作是这样的:
首先针对我们可以控制的i1->name,通过其进行溢出攻击实现覆写i2->name为puts的地址,再将可以控制的i2->name的缓冲区写为winner的地址。这样,当调用puts时实际上就会调用winner,就实现了控制流的劫持。
接下来进行实际操作
首先找到winner地址
objdump -t /opt/protostar/bin/heap1 | grep winner
在这里插入图片描述

在找到GOT中的put表项
objdump -TR /opt/protostar/bin/heap1 | grep puts
在这里插入图片描述

要写到i1->name的数据为:Ax(0x0804a02c - 0x0804a018) + “\x74\x97\x04\x08” = “A”x 20 . “\x74\x97\x04\x08”
写到i2>name的数据为:\x94\x84\x04\x08
测试如下
/opt/protostar/bin/heap1 perl -e 'print "A"x20 ."\x74\x97\x04\x08"' perl -e 'print "\x94\x84\x04\x08"'
在这里插入图片描述

0x03
第三关 heap2
在这里插入图片描述

本题的要求是打印出you have logged in already,根据条件,一旦我们输入login,程序会校验auth->auth整型数是否为0,如果不是0,则打印成功的语句
程序运行起来后可以使用这几个命令:auth,reset,service,login。简单运行起来看看
reset会调用free释放auth对象的空间
在这里插入图片描述

运行之后我们可以看到,使用了reset命令后,auth指针仍然指向原来分配的内存空间
在这里插入图片描述

如果输入login的话,则会校验auth->auth,默认打印please enter your password
这里的关键在于service命令,service会调用strdup()在堆上分配空间,由于glibc的堆管理特性,堆块的分配遵循最佳适应原则。如果我们先auth分配堆空间,然后reset,再service,则service分配到的堆空间其实和auth的是一样的,可以如下所示验证
在这里插入图片描述

那么这就是典型的UAF,use-after-free释放后重利用漏洞,我们通过service来覆写auth->auth为非0的值即可,我们这里就用1来覆写
由于本题没有其他限制条件,所以我们不需要精确的偏移,直接写大量的1就可以了
测试如下
在这里插入图片描述

可以看到,打印了成功利用后的语句

0x04
第四关,heap3
在这里插入图片描述

我们的目标是控制程序执行流,使其调用winner()
dlmalloc将堆视为一系列不同的chunk,在堆的高地址的最近一个chunk称之为wildnerss chunk。chunk有两种类型,分别为已分配的和未分配的,英文分别为allocated,free
已分配的chunk的布局如下
在这里插入图片描述

未分配的chunk布局如下
在这里插入图片描述

可以看到,bk位于chunk的起始处偏移12字节的地方,fd位于块的起始处偏移8字节的地方。这一点在之后写payload时需要注意
可以看到无论是哪一种chunk,都由两个区域组成,一个区域用于存放用户数据,一个区域存放元数据,即metadata(prev_size,size,fd,bk,它们每一个都占4字节)。
Fd,bk只有在chunk是未分配时才会用到,如果chunk已被分配,则这两处也可以存放用户数据
Prev_size只有在前一个chunk为未分配状态时才会用到,如果chunk已被分配,则这一处也可以存放用户数据
size的最低位用于指出前一个chunk是allocated还是free,如果最低位被置1,则表示前一个chunk是allocated,如果被置0,则表示前一块是free
当一个chunk被释放时,如果是通过free()来释放,如果这个chunk的前一个chunk是free的,则free()还会调用unlink()将前一个chunk从双向链表中摘下,并将这两个chunk合并
Free()的关键代码
在这里插入图片描述

Unlink()的关键代码
在这里插入图片描述

回到我们的题目上来,我们将会利用free()的特性来进行攻击。
我们在buffer B中创建一个假的chunk,这样当buffer B调用free()时,让它错误的认为它的前一个chunk是free的,然后就会调用unlink()将假的chunk从双向链表中摘下。
这里的关键就是在unlink()
对于我们创建的假的chunk,首先要确保buffer B在调用free()时认为这个假的chunk是free的,所以我们要将size的最低位设为0,我们这里将其设置为一个负数即可–我们将Buffer B的size设置为-4 (0xfffffffc),就可以满足这个要求(这样,不但满足了最低位为0的要求,而且可以让它认为假的chunk的大小为4字节),然后还要设置prev_size为一个负数(在free()的代码中可以看到,dlmalloc是通过调用chunk_at_offset(p, -(long)prevsz)来计算p的,也就是说它会减去prev_size的值以得到前一个chunk开始的地址),我们这里设为-8,这样它就会认为前一个块位于chunk B之前偏移8字节的位置,这个位置就是我们伪造的chunk的位置。
我们将假的chunk的bk设为nop sled的地址,fd设为puts函数地址减12字节。因为在unlink()中有FD->bk=BK,而bk距离chunk起始处的偏移为12字节
我们将Got表中puts(这里注意一下,如下所示,虽然源码中是printf,但是got表中是puts,这是因为编译器会自动用puts代替printf,因为前者速度更快效率更高)的地址改为指向我们的nop sled的地址,这样执行了几个nop操作后就会命中我们的shellcode。
下图是找到puts在got的地址
在这里插入图片描述

将该地址减12,即0x0804b128-0xc=0x0804b11c
那么nop sled的地址呢?
Nop sled的地址就是第一次malloc分配的缓冲区的起始地址,所以我们在第一次malloc后下端点再看eax寄存器的值即可
在这里插入图片描述

在0x0804889e下断点并执行
在这里插入图片描述

命中断点后查看寄存器
在这里插入图片描述

所以nop sled的地址为0x0804c008
接下来就是shellcode了
首先要找到winner的地址
在这里插入图片描述

然后用汇编写指令:
push 0x08048864
Ret
0x08048864是winner()的地址,这两条指令的作用就是将winner()的地址放到栈里,然后利用ret指令,从栈取出地址并跳转过去
我们将这两条指令写到asm里
在这里插入图片描述

然后编译
在这里插入图片描述

链接
在这里插入图片描述

然后就可以使用objdump提取出shellcode了
在这里插入图片描述

即“\x68\x64\x88\x04\x08\xc3“
现在需要的数据都有了,我们结合下图来看看现在我们的构造
在这里插入图片描述

Buffer A:内容为nop sled+shellcode+填充(红色框起来的部分)+修改的prev_size和size(蓝色框起来的部分,这是通过在使用strcpy填充buffer A时故意溢出,覆盖了buffer b的前8字节)
Buffer B:内容为8字节填充+puts在got中的地址减12+nop sled的地址
Buffer C:内容随意,这里就是一个C
具体来看,
Argv[1]的内容中nop sled共14字节,shellcode为6字节,32字节的缓冲区还剩12字节,这里我们填充12个A,之后再加上0xfffffff8(-8),0xfffffffc(-4)分别用于填充prev_size,size
Argv[2]的内容中8字节的填充内容随意,这里填充的是两个deadbeef,然后是\x1c\xb1\x04\x08,\x08\xc0\x04\x08
Argv[3]的内容为C
这样我们就在buffer B中创建了一个假的chunk(上图黄色部分所示),这样在程序执行到free(b)时,会认为它的前一个chunk是free的,然后就会调用unlink()将假的chunk从双向链表中摘下。
一般unLink过程的图示如下
在这里插入图片描述

对应着我们现在的情况,上图的P就是我们的伪造的chunk,其fd是Puts地址减12,即\x1c\xb1\x04\x08,bk是nop sled地址即\x08\xc0\x04\x08。
再结合unlink代码
在这里插入图片描述

那么在调用unlink时
第一行:BK=0x0804c008
第二行:FD=0x0804b11c
第三行:FD->bk=0x0804c008(这里是关键,bk是距离chunk偏移12字节,所以这里的FD->bk实际上就是0x0804b11c+12=0x0804b128,即puts在Got表中的地址,所以通过这一条代码,在调用puts时,实际是指向了0x0804c008,也就是nop sled的地址,之后就会滑行到我们的shellcode并开始执行)
第四行:BK->fd=0x0804b11c
故完整的exp如下:
./heap3 python -c 'print "\x90"*14+"\x68\x64\x88\x04\x08\xc3"+"A"*12+"\xf8\xff\xff\xff"+"\xfc\xff\xff\xff"' python -c 'print "\xde\xad\xbe\xef"*2+"\x1c\xb1\x04\x08"+"\x08\xc0\x04\x08"' C

可以看到,成功控制了执行流,调用了winner()
在这里插入图片描述

0x05
参考:
1.https://exploit-exercises.lains.space/protostar/
2.https://medium.com/bugbounty/
3.https://medium.com/@airman604/
4.https://medium.com/@coturnix97/exploit-exercises-protostar/
5.https://secinject.wordpress.com/2018/01/18/protostar-heap3/
6.https://www.davidxia.com/2020/04/how-to-exploit-dlmalloc-unlink/

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值