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
提示没找到的时候得留个心眼,看看要找的指令是不是还有别的编码。