wustctf2020_getshell_2
准备

32 位,开了 NX 保护
分析
main函数
1 | int __cdecl main(int argc, const char **argv, const char **envp) |
有一个 vulnerable 函数
vulnerable函数
1 | ssize_t vulnerable() |
读取输入最大 36(0x24) 个字节到 buf ,但 buf 大小为 24,所以存在缓冲区溢出
shell函数(后门函数)
1 | int shell() |
有 system 函数,但连接路径有问题,不过知道了有 sh
思路:
这题有栈溢出和 system 函数和 sh ,所以直接通过溢出打 system('/sh')
但是需要考虑的是这里的溢出位数
这里光看读取最大输入是 36 , buf 大小为 24 ,有足够的可写空间
但这样子是错的,这里具体的偏移量是 0x18+4(28) ,才到返回地址我们写入 system 地址
这样子我们能写入的只有8字节,用以往正常溢出的方法,payload 需要12字节是不够的payload=b'a'*28+p32(system)+p32(0)+p32(sh)
( p32(0) 是占位符,承接 system 函数的返回地址,当 system 执行完毕后,程序会尝试跳转到地址 0x00000000,这会触发错误,但在此之前,shell 已经被成功弹出,攻击已达成目标。)
通过 ROPgadget 获取 sh 地址
得到 sh 函数的地址为 0x8048670
知识点:call 指令
概念:call 指令是汇编语言中用于实现子程序(函数)调用的核心指令,主要功能是将程序控制权转移到子程序,并在子程序执行完毕后返回原调用点继续执行。
流程:
- 保存返回地址:
call指令会先将当前指令的下一条指令地址(返回地址)压入栈中 - 跳转到子程序:将子程序的起始地址加载到指令指针寄存器(如
EIP/IP) - 执行子程序:
CPU开始顺序执行子程序中的指令 - 返回原程序:子程序通过 RET 指令弹出栈中保存的返回地址到指令指针寄存器
这里我们直接用 shell 函数中汇编 call system 语句的地址
(调用 system() 函数执行命令)
(利用 call 指令自动压栈的特性:call 会自动将当前指令的下一条指令地址(返回地址,这里的 sh 地址)压入栈中)
( call 指令直接把 sh 地址压入栈中, system 从栈中获取参数(即 sh),当 system 执行完后,会执行 ret 指令,ret 会弹出栈顶的值(即 sh),并跳转到该地址执行虽然会报错,但已经执行了 shell)
(所以这里我们不用加 p32(0) 这个值,给的 p32(sh) 既是 system 返回地址,在 call 指令下还是 system 的参数)
这个地址作为返回地址,调用 system 函数,然后我们直接把sh的地址当参数给他就能获得连接
1 | sh=0x8048670 |
脚本
1 | from pwn import * |
