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 * |