CTF笔记: 跳转ESP与PEDA
之前在Intro to Infosec的课上有一道CTF题,大致就是一个常规的用了gets()的32位Linux二进制执行文件,没开NX、Canary、PIE但机器本身开了ASLR。程序没有format string漏洞来泄露栈地址,但可以通过劫持执行流程来让puts()泄露任意内存地址的数据。有问题的函数是这样的:
void vuln(){
char buf[BUFSIZE];
gets(buf);
puts(buf);
fflush(stdout);
}乍一看不算很难,但仔细想想开了ASLR的时候栈的基地址就是随机的,就算没开NX也得先找到栈地址或者跳转到ESP才能运行栈溢出注入的Shellcode。问了问ChatGPT有没有通过固定地址里的数据(比如PLT/GOT表附近的数据)来泄露栈地址,回答是正常来说不会有,如果有的话那就是另外一个安全漏洞。那只能用ROP Chain来解决问题了,因为没开PIE所以.text里面指令地址都是固定的。然后用PEDA搜了 push esp 、 jmp esp 、 jump ebp 等等一系列可能的gadget,结果都是没找到,然后就尬住了。上网搜也没搜到怎么做。
只能说我思路是对的,但经验不够并且过于相信PEDA的 ropsearch 功能。最后我用的是return-to-libc的方式解决的。先劫持返回地址让它返回到 puts@plt 并且打印出GOT表,然后用GOT表里面的函数指针查询libc版本(依靠libc数据库),然后修改其中一个GOT指针指向 execve ,最后执行这个被修改了GOT表的函数来达成 cat flag.txt 。这样写下来脚本当然很长,写的时候就很疑惑了因为正常来说上课没教到return-to-libc而且这是入门课,应该不需要整的这么复杂。
然后交flag的时候看到旁边的Hint就傻眼了,旁边的Hint直接就告诉了我怎么去跳转到 esp ,之前读题的时候完全没看到。
大致是这样子:几乎每个ELF的程序入口都在 _start 例程,这个是gcc等编译器自动生成的。然后这个例程里面会有这么几个指令:
80491d1: e8 9a ff ff ff call 8049170 <__libc_start_main@plt>
80491d6: f4 hlt
80491d7: 8b 1c 24 mov (%esp),%ebx
80491da: c3 ret(还发现有一点,gcc的 --no-pie 是开PIE, -no-pie 才是关PIE,过于抽象,文档里也没说,我就说用 --no-pie 编译了半天怎么还是开着PIE的执行文件)
这几个指令乍一看没什么,但如果错位反汇编的话就能发现会变成这样:
80491d5: ff f4 push %esp
80491d7: 8b 1c 24 mov (%esp),%ebx
80491da: c3 ret假如从 call 最后一个字节开始反汇编,它就会跟后面 hlt 的 f4 粘一块变成 push esp ,这样我们就有了一个 push esp; ret 的gadget。push之后ret也是等同于直接jmp的。也就是说,只要把 vuln() 的返回地址改成 0x80491d5 ,程序就会跳转到这个gadget,然后跳转到esp。这时候esp指向的就是返回地址下面的4字节的位置,不需要弄return-to-libc这么复杂的执行链。
看到这个Hint整个人都傻了,因为我谷歌没搜到这篇文章而且我也确实是用PEDA的 ropsearch 搜索过了这个gadget,提示是没有。我改了一下PEDA的源文件让它打印出来实际检索的数据,发现它并不会因为一个指令能被编译成多种形式就添加多个search—— push esp 还有一种写法是 54 ,然后PEDA搜索的是 \x54.{0,24}\xc3 ,所以才没找到这个 ff f4 ** ** ** c3 的gadget。看样子以后碰到 asmsearch 或者 ropsearch 提示没找到的时候得留个心眼,看看要找的指令是不是还有别的编码。