6.1.2 pwn NJCTF2017 pingme

下载文件

题目复现

在 6.1.1 中我们看到了 blind ROP,这一节中则将看到 blind fmt。它们的共同点是都没有二进制文件,只提供 ip 和端口。

checksec 如下:

$ checksec -f pingme
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATHFORTIFY Fortified Fortifiable  FILE
No RELRO        No canary found   NX enabled    No PIE          No RPATH   No RUNPATH   No        0               2       pingme

关闭 ASLR,然后把程序运行起来:

$ socat tcp4-listen:10001,reuseaddr,fork exec:./pingme &

Blind fmt 原理及题目解析

格式化字符串漏洞我们已经在 3.3.1 中详细讲过了,blind fmt 要求我们在没有二进制文件和 libc.so 的情况下进行漏洞利用,好在程序没有开启任何保护,利用很直接。

通常有两种方法可以解决这种问题,一种是利用信息泄露把程序从内存中 dump 下来,另一种是使用 pwntools 的 DynELF 模块(关于该模块的使用我们在章节 4.4 中有讲过)。

漏洞利用

确认漏洞

首先你当然不知道这是一个栈溢出还是格式化字符串,栈溢出的话输入一段长字符串,但程序是否崩溃,格式化字符串的话就输入格式字符,看输出。

很明显是格式字符串,而且 ABCD 在第 7 个参数的位置,实际上当然不会这么巧,所以需要使用一个脚本去枚举。这里使用 pwntools 的 fmtstr 模块了:

dump file

接下来我们就利用该漏洞把二进制文件从内存中 dump 下来:

这里构造的 paylaod 和前面有点不同,它把地址放在了后面,是为了防止 printf 的 %s\x00 截断:

另外 .AAA,是作为一个标志,我们需要的内存在 .AAA 的前面,最后,偏移由 7 变为 9。

在没有开启 PIE 的情况下,32 位程序从地址 0x8048000 开始,0x1000 的大小就足够了。在对内存 \x00 进行 leak 时,数据长度为零,直接给它赋值就可以了。

于是就成了有二进制文件无 libc 的格式化字符串漏洞,在 r2 中查询 printf 的 got 地址:

地址为 0x8049974

printf address & system address

接下来通过 printf@got 泄露出 printf 的地址,进行到这儿,就有两种方式要考虑了,即我们是否可以拿到 libc,如果能,就很简单了。如果不能,就需要使用 DynELF 进行无 libc 的利用。

先说第一种:

所以 printf 的地址是 0xf7e0e670(小端序),使用 libc-database 查询得到 libc.so,然后可以得到 printf 和 system 的相对位置。

然后计算得到 printf 的地址:

第二种方法是使用 DynELF 模块来泄露函数地址:

DynELF 不要求我们拿到 libc.so,所以如果我们查询不到 libc.so 的版本信息,该模块就能发挥它最大的作用。

attack

按照格式化字符串漏洞的套路,我们通过任意写将 printf@got 指向的内存覆盖为 system 的地址,然后发送字符串 /bin/sh,就可以在调用 printf("/bin/sh") 的时候实际上调用 system("/bin/sh")

终极 payload 如下,使用 fmtstr_payload 函数来自动构造,将:

虽说有这样的自动化函数很方便,基本的手工构造还是要懂的,看一下生成的 payload 长什么样子:

开头是 printf@got 地址,四个字节分别位于:

然后是格式字符串 %64c%7$hhn%76c%8hhn%67c%9$hhn%24c%10$hhn

就这样将 system 的地址写入了内存。

Bingo!!!

exploit

完整的 exp 如下:

参考资料

Last updated

Was this helpful?