6.1.13 pwn 34C3CTF2017 readme_revenge

下载文件

题目复现

这个题目实际上非常有趣。

$ file readme_revenge
readme_revenge: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.32, BuildID[sha1]=2f27d1b57237d1ab23f8d0fc3cd418994c5b443d, not stripped
$ checksec -f readme_revenge
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FORTIFY Fortified Fortifiable  FILE
Partial RELRO   Canary found      NX enabled    No PIE          No RPATH   No RUNPATH   Yes     3               45      readme_revenge

与我们经常接触的题目不同,这是一个静态链接程序,运行时不需要加载 libc。not stripped 绝对是个好消息。

$ ./readme_revenge
aaaa
Hi, aaaa. Bye.
$ ./readme_revenge
%x.%d.%p
Hi, %x.%d.%p. Bye.
$ python -c 'print("A"*2000)' > crash_input
$ ./readme_revenge < crash_input
Segmentation fault (core dumped)

我们试着给它输入一些字符,结果被原样打印出来,而且看起来也不存在格式化字符串漏洞。但当我们输入大量字符时,触发了段错误,这倒是一个好消息。

接着又发现了这个:

看来 flag 是被隐藏在程序中的,地址在 0x006b4040,位于 .data 段上。结合题目的名字 readme,推测这题的目标应该是从程序中读取或者泄漏出 flag。

题目解析

因为 flag 在程序的 .data 段上,根据我们的经验,应该能想到利用 __stack_chk_fail() 将其打印出来(参考章节 4.12)。

main 函数如下:

很简单,从标准输入读取字符串到变量 name,地址在 0x6b73e0,且位于 .bss 段上,是一个全局变量。接下来程序调用 printf 将 name 打印出来。

在 gdb 里试试:

程序的漏洞很明显了,就是缓冲区溢出覆盖了 libc 静态编译到程序里的一些指针。再往下看会发现一些可能有用的:

再看一下栈回溯情况吧:

依次调用了 printf() => vfprintf() => printf_positional() => __parse_one_specmb()。那就看一下 glibc 源码,然后发现了这个:

这里就涉及到 glibc 的一个特性,它允许用户为 printf 的模板字符串(template strings)定义自己的转换函数,方法是使用函数 register_printf_function()

  • 该函数为指定的字符 __spec 定义一个转换规则。因此如果 __specY,它定义的转换规则就是 %Y。用户甚至可以重新定义已有的字符,例如 %s

  • __func 是一个函数,在对指定的 __spec 进行转换时由 printf 调用。

  • __arginfo 也是一个函数,在对指定的 __spec 进行转换时由 parse_printf_format 调用。

想一下,在程序的 main 函数中,使用 %s 调用了 printf,如果我们能重新定义一个转换规则,就能做利用 __func 做我们想做的事情。然而我们并不能直接调用 register_printf_function()。那么,如果利用溢出修改 __printf_function_table 呢,这当然是可以的。

register_printf_function() 其实也就是 __register_printf_specifier(),我们来看看它是怎么实现的:

然后发现 spec 被直接用做数组 __printf_function_table__printf_arginfo_table 的下标。s 也就是 0x73,这和我们在 gdb 里看到的相符:rdx=0x73[rax+rdx*8]正好是数组取值的方式,虽然这里的 rax 里保存的是 __printf_modifier_table

漏洞利用

有了上面的分析,下面我们来构造 exp。

回顾一下 __parse_one_specmb() 函数里的 if 判断语句,我们知道 C 语言对 || 的处理机制是如果第一个表达式为 True,就不再进行第二个表达式的判断,所以为了执行函数 *__printf_arginfo_table[spec->info.spec],需要前面的判断条件都为 False。我们可以在 .bss 段上伪造一个 printf_arginfo_size_function 结构体,在结构体偏移 0x73*8 的地方放上 __stack_chk_fail() 的地址,当该函数执行时,将打印出 argv[0] 指向的字符串,所以我们还需要将 argv[0] 覆盖为 flag 的地址。

Bingo!!!

exploit

完整的 exp 如下:

参考资料

Last updated

Was this helpful?