ciscn_2019_c_1
代码很简单,gets 一个东西,然后给你加密一下。gets 没保护,可以栈溢出,但是木有自带后门,需要自己找门。
首先解决一下加密函数破坏 payload 的问题,这个需要脑洞。由于加密函数使用 strlen 获得字符串长度,所以 payload 第一个字符直接 \x00 就可以阻止加密函数进行。
接下来的做法直接看 exp 。
1 | from pwn import * |
第 5-7 行是三个简化输入输出的函数,后面代码用的时候要能看懂。
第 14 行到第 21 行获取了一些指令的地址。
第 15 行的 ret 地址可以是任意 ret ,但是不能使用 encrypt 函数的 ret 地址 0x400AEE, 因为 \x0a 会被 gets 是为 \n 直接终止输入。
企鹅:“这也是常见问题,不同读入方式构造 exp 的时候需要留意一些特殊值。”
第 18 行的地址是连续的 pop rdi 和 ret ,但是去 ida 会发现没有 pop rdi 啊?0x400C82 是 pop r15 而 0x400C84 是 ret 。事实上 pop rdi 藏在 pop r15 里。pop r15 的机器码是 415f 而 pop rdi 是5f ,因此从 0x400C83 开始就是 pop rdi; ret; 。这个地址很常用,似乎多数 x64 的程序都有。
第 20 行是用 one_gadget 获取的 libc 里 system(’/bin/bash’) 的地址,20 和 21 行是从 ELF 里获取的 puts 函数在 got 表和 plt 表的地址。
接下来是一个 ret2libc 的过程。这个过程要 ret 两次,第一次获取 libc 的偏移量,第二次再 getshell 。
第 23 行的 payload 使得程序首先跳转到 pop rdi; ret; 的位置,然后把 puts 在 got 表中的地址 pop 的到 rdi 上,再 ret 到 puts 在 plt 表中的地址。在 plt 表中,puts 的值是 puts 的真实地址,因此这里会执行 puts 函数,而 puts 以 rdi 为参数,这样就可以输出 got 表中 puts 的值。
第 33 行利用 puts 在 libc 里的地址、system(’/bin/bash’) 在 libc 里的地址以及 pot 表中 puts 的值,就可以计算得到 system(’/bin/bash’) 在 pot 表中的值,ret 到这个值表示的地址就可以执行 shell 。
plt 表,pot 表,还有真实地址,这些是什么关系?
这是动态链接的知识,似乎比较复杂,慢慢学吧 =。=
由于服务跑在 ubuntu 18 上,还要多试几个原地 ret 的数量,让栈对齐才可以正常运行。此外 exp 会有一定概率挂,要多试几次。
企鹅:“确实会有玄学出现,所以稳定化 exp 利用也是一个难点。”
第 39 行到第 43 行是另一种写法,是把 system 和’/bin/bash’ 分开处理的。
上面的过程本质上是通过多个 ret 组成的链,把几个不相关的代码连接到一起,从而实现操纵寄存器、调用指定函数等操作。这,就是 ROP 。