来自 xctf 的一道格式化字符串漏洞的 pwn 题
前置知识:printf
printf 可能对于大多数人来说非常熟悉了,不就是打印一些字符串吗?然而,printf 可不止那么简单……
如果我们运行以下命令:
|
|
输出总是随机的值比如:
|
|
但是实际上,这只是内存数据上的随机,而她们实际上是 3 个栈上的连续内存地址。
n$ 参数字段
这里的 n 代表数字,用于选择第几个参数
比如 1$ 代表第一个参数, 3$ 代表参数 3。
运行 printf 的时候这些参数都会跟着入栈。
%n 格式化符
不产生任何输出,相反,到目前为止函数所产生的输出字符串数目将被保存到对应的参数中。
分析程序
32 位 elf 文件。有金丝雀保护,不过我现在还不知道 canary 到底怎么保护栈溢出。反正本题也不需要。
直接丢 ida pro 分析 main 函数。
可以看到要想获得 flag 就必须要让 pwnme 这个变量为 8。
在这里我们可以看到 pwnme 位于 .bss 字段,也获得了它的地址。现在就是要寻找到改写这个地址数据的方法了。
发现 printf(s) ,没有什么保护措施,可以尝试用格式化字符串漏洞来对 pwnme 地址写入。
首先看看 s 字符串,使用 fgets 从 stdin 读得,我们可以先尝试往 s 添几个格式化符试试:
确实是可行的,那么我们就一鼓作气先写个 pyton 脚本看看:
可以发现这里读到的参数到第 10 个为 aaaa 的 16 进制
至于为什么是第 10 个,大抵是因为 fgets 作妖吧。
可以通过如下 python 代码判断出它是 aaaa:
|
|
那么我们可以尝试将这个替换为 pwnme 的地址,然后将 8 以某种方式丢到这个地址的数据中。就可以拿到 flag 了。
%10$n 的作用
%n 获取 printf 函数的输出长度
10$ 从栈上取第 10 个参数
和起来就是向栈上第 10 个参数写入到这个参数值所指向的内存地址。
那么我们可以分析下,如果我们第一个参数放 pwnme 的地址,那么它在 32 位中占 4 个字节,然后我们再补上 4 个字节的数据就是我们所要的 8 。然后通过 %10$n 就能向 pwnme 写入数据 8 了。
这样就能写出攻击载荷:
|
|
这时候我们向 s 字符串注入载荷就能进行覆写。
完整 python 脚本如下:
|
|