MIPS ROP-system parameter issue

MIPS 架构下的栈溢出安全缺陷 exploit - system 函数参数覆盖问题。
常见的 MIPS 下安全缺陷利用方式
MIPS 架构下的栈溢出 exploit 通常会使用 ret2shellcode、ret2text、ret2libc 三种方式。
ret2shellcode
MIPS 指令集中存在一类特殊的 gadget,它们可以通过 $sp 寄存器寻址,将栈地址($sp + offset)加载到某寄存器中,然后跳转到该寄存器执行。另外,MIPS 架构下仅有受限的 no-execute 保护[1],根据 MIPS 官方文档[2]描述,只有在 MIPS Release 3 以及 SmartMIPS ASE 中包含类似 no-execute 的 XI(Execute Inhibit) 保护,而大量采用 MIPS CPU 的设备中,stack 内存区域通常包含 rwx 权限。
基于以上两点,我们可以将 shellcode 布置于 stack,然后利用此类 gadget 跳转到 shellcode 执行。
ret2text
某些情况下包含安全缺陷的目标程序中含有比较有用的代码片段,例如程序中包含调用 system 函数的逻辑,此时可通过 ret2text 返回到程序内部的 system 函数执行命令。
ret2libc
以上两种方式基本都需要多次控制程序执行流程,然而在实际情况下,很多包含安全缺陷的程序对于用户提交的数据有限制,例如 \x00 在 web 服务中会截断其后的数据,导致请求不完整。此类情况下会考虑将程序执行流劫持到 libc 中,因为 libc 地址较高,不会包含 \x00 等坏字节。ret2libc 的一种经典思路是将命令保存到 stack,然后通过 ROP 将 stack 地址加载至 $a0 寄存器,最后跳转到 libc 中的 system 函数执行命令。
以上是 MIPS 架构中比较常用的安全缺陷利用方式,最终达到执行系统命令的目的(详细思路可参考互联网文章)。
system 参数覆盖问题
下面简单说明什么是 system 参数覆盖以及这个问题是如何出现的。
假设一网络设备的 web 服务中包含一个栈溢出安全缺陷,当用户发送的请求中 URI 部分过长,会导致程序向栈缓冲区拷贝过多的数据,使程序崩溃。
按照经典的 exploit 思路,这里可以采用 ret2libc 方式,先用 mipsrop 工具从 libc 中查找合适的 gadget
1 | | 0x000518D0 | addiu $a0,$sp,24 ; jr 68($sp) |
我们看到位于 0x518D0 位置的 gadget 比较合适,它将 $sp + 24 地址加载到 $a0,然后跳转到 $sp + 68 指向的地址继续执行。
所以可进行如下内存布局:
padding 部分填充栈空间数据,gadget_address(libc_address + 0x000518D0)覆盖了返回地址,当程序从包含安全缺陷的函数返回时,由于此函数为非叶子函数,所以会从栈上获取返回地址加载到 $ra,而返回地址已经被我们修改为 gadget,所以程序跳转到 gadget 继续执行。
这段 gadget 具体的汇编代码序列如下
1 | addiu $a0, $sp, 24 |
执行时,$sp + 24 地址被加载到 $a0,$sp + 68 地址的值被加载到 $ra,然后以 4 字节为单位将 $sp + 64 ~ $sp + 52 的数据加载到 $s3 ~ $s0,最后用 jr 指令跳转到 $ra 即 $sp + 68 指向的内存区域,由于 MIPS 的流水线效应,最后一句 addiu $sp,72 在跳转之后已经执行完毕。
此时几个寄存器状态:
1 | $a0 = command |
由于最后的 addiu $sp, 72,$sp 指向我们布局的内存数据之下(即 data 部分):
$pc 已经指向 system 函数,程序继续执行 system 对应代码,查看 system 函数开头的部分汇编代码
1 | la $gp, locret_1E4F0 # Alternative name is '__libc_system' |
其中第三条指令为 system 函数分配栈空间,把 $sp 减去 40,得到 system 函数的栈顶,后续 system 以及 system 所调用的子函数,都会使用这个栈顶及以上的内存来存取临时变量,如图
实际上小于 $sp + 72 的栈空间都可能会被其它变量的数据所覆盖。
如果恰巧有某个临时变量的数据被保存到 $sp + 24 附近的内存,本应是正常的系统命令就会被破坏,导致 exploit 失败。
如何避免此类问题
方法1:寻找包含 jr $sx 的 gadget 或利用其它寄存器转移参数
mipsrop 中可能会找到如下 gadget
1 | | 0x00016BC8 | addiu $s0,$sp,16 ; jalr $s1 |
这个 gadget 对应的指令序列如下
1 | addiu $s0, $sp, 16 |
先将 $sp + 16 地址加载到 $s0,然后 $s0 被赋值给 $a0,最后跳转到 $s1,期间不存在对 $sp 的修改。这种 gadget 很多情况下可以防止 command 被覆盖。
方法2:利用 popen 部分代码
popen 中调用了 execl 函数来执行系统命令,其中参数加载过程通常位于同一个汇编代码块中
1 | la $a0, loc_50000 |
$a0、$a1、$a2 在代码中都已完成加载,我们只需要控制 $s7 为 command 地址,这样在跳转到 execl 执行前,move $a3,$s7 就会把 command 地址放到 $a3 中,完成参数传递。
通过 mipsrop 找到将 stack 地址加载到 $s7 的 gadget
1 | addiu $s7, $sp, 24 |
加载后跳转到 $s4 继续执行,但如果直接跳回 popen,会导致参数异常,这是因为 MIPS 中对全局变量寻址有特殊的策略,MIPS 为了方便对全局变量数据的寻址,引入一个叫做 $gp 的寄存器,通过此寄存器,一条 4 字节的指令即可实现在 $gp +- 32K 内存中的寻址过程[3]。
$gp 是在函数开头就设置好的,由于我们是直接跳转到 popen 函数的中间部分,这就导致 $gp 错误,无法正确找到相关参数,所以需要引入另一段 gadget 来修正 $gp。
$gp 寄存器具体的值可通过调试目标函数开头确定,这个值相对 libc 基址偏移固定。确定好 $gp 值后即可寻找相关 gadget 修正。
1 | lw $gp, 16($sp) |
以上 gadget 会将 $sp + 16 的值加载到 $gp,然后跳转到 $s1,恰好可以满足需求。
综合上述流程,gadget 执行顺序如下
1 | 将 $sp + 24 填充为 command,跳转到第一段 gadget ($ra) |
这样执行 popen 部分时,各参数可正确寻址,并且 command 也不会被覆盖,可以成功执行系统命令。
实际上,除了 MIPS 外其他架构也可能存在类似问题,主要原因就是需要使用的数据位于下一个函数的栈帧内,比较通用的思路,一是可以调整数据位置,将其移动到不会被覆盖或修改的位置,或者可以利用对应指令集的特性来避免数据被覆盖。
参考文章
[1] operating system - MIPS memory execution prevention - Stack Overflow
- Title: MIPS ROP-system parameter issue
- Author: Catalpa
- Created at : 2021-09-17 00:00:00
- Updated at : 2024-10-17 08:51:27
- Link: https://wzt.ac.cn/2021/09/17/mipsrop/
- License: This work is licensed under CC BY-SA 4.0.