6.1.14 pwn 32C3CTF2015 readme
题目复现
$ file readme.bin
readme.bin: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=7d3dcaa17ebe1662eec1900f735765bd990742f9, stripped
$ checksec -f readme.bin
RELRO STACK CANARY NX PIE RPATH RUNPATH FORTIFY Fortified Fortifiable FILE
No RELRO Canary found NX enabled No PIE No RPATH No RUNPATH Yes 1 2 readme.bin开启了 Canary。
flag 就藏在二进制文件中的 .data 段上:
$ rabin2 -z readme.bin | grep 32C3
000 0x00000d20 0x00600d20 31 32 (.data) ascii 32C3_TheServerHasTheFlagHere...程序接收两次输入,并打印出第一次输入的字符串(看起来并没有格式化字符串漏洞):
$ ./readme.bin
Hello!
What's your name? %p.%p.%p.%p
Nice to meet you, %p.%p.%p.%p.
Please overwrite the flag: %d.%d.%d.%d
Thank you, bye!
$ python -c 'print "A"*300 + "\n" + "B"' > crash_input
$ ./readme.bin < crash_input
Hello!
What's your name? Nice to meet you, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA.
Please overwrite the flag: Thank you, bye!
*** stack smashing detected ***: ./readme.bin terminated
Aborted (core dumped)
$ python -c 'print "A" + "\n" + "B"*300' | ./readme.bin
Hello!
What's your name? Nice to meet you, A.
Please overwrite the flag: Thank you, bye!第一次输入的字符串过多会导致栈冲突的问题,第二次的输入似乎就没有什么影响。
感觉和 6.1.13 那题一样,都是需要利用 __stack_chk_fail() 打印 flag(参考章节 4.12)。但这一题是动态链接程序,因为 libc-2.25 版本的更新,使 __stack_chk_fail() 不能用了。所以为了复现,我们选择Ubuntu 16.04,版本是 libc-2.23。
题目解析
来看一下程序的逻辑:
看注释已经很明显了,第一次的输入需要我们触发栈溢出,使程序调用 __stack_chk_fail(),并打印出 argv[0]。第二次的输入将覆盖掉位于 0x00600d20 的 flag。
漏洞利用
那么问题来了,如果 flag 被覆盖掉了,那还怎样将其打印出来。这就涉及到了 ELF 文件的映射问题,我们知道 x86-64 程序的映射是从 0x400000 开始的:
在调试时我们又发现 readme.bin 被映射到下面的两个地址中:
所以只要在二进制文件 0x00000000~0x00001000 范围内的内容都会被映射到内存中,分别以 0x600000 和 0x400000 作为起始地址 。flag 在 0x00000d20,所以会在内存中出现两次,分别位于 0x00600d20 和 0x00400d20:
所以即使 0x00600d20 的 flag 被覆盖了,0x00400d20 的 flag 依然存在。
让我们来找出 argv[0] 距离栈的距离:
536=0x218 个字节。第一次尝试:
在第一个终端里执行下面的命令,相当于远程服务器,并且将 stderr 重定向到 stdout:
然后在第二个终端里执行 exp:
咦,flag 并没有在我们执行 exp 的终端里打印出来,反而是打印在了执行程序的终端里:
所以我们需要做点事情,让远程服务器上的错误信息通过网络传到我们的终端里。即利用第二次的输入,将 LIBC_FATAL_STDERR_=1 写入到环境变量中。结果如下:
函数 __GI___libc_secure_getenv 成功获取到了环境变量 LIBC_FATAL_STDERR_ 的值 1:
Bingo!!!
exploit
最终的 exp 如下:
参考资料
Last updated
Was this helpful?