Qiling 框架核心功能与实战速查手册
Qiling 是一个基于 Unicorn 引擎构建的高级二进制模拟框架,专为逆向工程、恶意代码分析和漏洞研究设计。它不仅支持多架构(x86、ARM、MIPS 等)和多操作系统(Linux、Windows、UEFI 等),还提供了丰富的钩子机制和虚拟文件系统能力。
环境安装与初始化
通过 pip 安装:
pip install qiling
模拟 Linux x86_64 程序需准备 rootfs,可从官方仓库 examples/rootfs/x8664_linux 获取。
核心架构组件
- 加载器(Loader):识别二进制格式,加载段,设置栈、堆、环境变量及命令行参数。
- 架构层(Arch):处理 CPU 特性(如 VFP)、寄存器布局、TLS 初始化等。
- 操作系统层(OS):实现系统调用、API 映射、标准 I/O、线程管理等运行时行为。
典型实战:QilingLab 解题示例
以下代码展示了如何通过 Qiling 绕过多个反调试与验证逻辑:
from qiling import Qiling
from qiling.const import QL_INTERCEPT
from qiling.os.mapper import QlFsMappedObject
import struct
def setup_challenge1(ql):
ql.mem.map(0x1000, 0x1000)
ql.mem.write(0x1337, ql.pack16(1337))
def fake_uname(ql, *args):
rdi = ql.arch.regs.rdi
ql.mem.write(rdi, b'QilingOS\x00')
ql.mem.write(rdi + 195, b'ChallengeStart\x00') # 65*3=195
def setup_challenge2(ql):
ql.os.set_syscall('uname', fake_uname, QL_INTERCEPT.EXIT)
class FakeRandom(QlFsMappedObject):
def read(self, size):
return b'\x10' if size == 1 else b'\x00' * size
def close(self): return 0
def mock_getrandom(ql, buf, sz, flags):
ql.mem.write(buf, b'\x00' * sz)
ql.arch.regs.rax = 0
def setup_challenge3(ql):
ql.os.set_syscall('getrandom', mock_getrandom, QL_INTERCEPT.CALL)
ql.add_fs_mapper('/dev/urandom', FakeRandom())
def bypass_cmp(ql):
ql.arch.regs.eax = 1
def setup_challenge4(ql):
hook_addr = ql.loader.load_address + 0xE43
ql.hook_address(bypass_cmp, hook_addr)
def mock_rand(ql):
ql.arch.regs.rax = 0
def setup_challenge5(ql):
ql.os.set_api('rand', mock_rand, QL_INTERCEPT.CALL)
def skip_infinite_loop(ql):
ql.arch.regs.rax = 0
def setup_challenge6(ql):
ql.hook_address(skip_infinite_loop, ql.loader.load_address + 0xF16)
def noop_sleep(ql):
pass
def setup_challenge7(ql):
ql.os.set_api('sleep', noop_sleep, QL_INTERCEPT.CALL)
def patch_struct_check(ql):
rax = ql.arch.regs.rax
data = ql.mem.read(rax, 24)
str_ptr, magic, flag_ptr = struct.unpack("QQQ", data)
ql.mem.write(flag_ptr, b'\x01')
def setup_challenge8(ql):
ql.hook_address(patch_struct_check, ql.loader.load_address + 0xFB5)
def fix_byte_flag(ql):
addr = ql.arch.regs.rax
ql.mem.write(addr, b'\x01')
def setup_challenge9(ql):
ql.hook_address(fix_byte_flag, ql.loader.load_address + 0x1061)
def spoof_string(ql):
ptr = ql.arch.regs.rax
ql.mem.write(ptr, b'qilinglab\x00')
def setup_challenge10(ql):
ql.hook_address(spoof_string, ql.loader.load_address + 0x112D)
def fake_cpuid(ql, *args):
ql.arch.regs.ebx = 0x696C6951 # 'Qili'
ql.arch.regs.ecx = 0x614C676E # 'ngLa'
ql.arch.regs.edx = 0x20202062 # 'b '
def setup_challenge11(ql):
ql.hook_address(fake_cpuid, ql.loader.load_address + 0x1191)
if __name__ == '__main__':
binary = [r'./qilinglab-x86_64']
fs_root = r'./rootfs/x8664_linux'
emu = Qiling(binary, fs_root)
# 应用所有挑战补丁
setup_challenge1(emu)
setup_challenge2(emu)
setup_challenge3(emu)
setup_challenge4(emu)
setup_challenge5(emu)
setup_challenge6(emu)
setup_challenge7(emu)
setup_challenge8(emu)
setup_challenge9(emu)
setup_challenge10(emu)
setup_challenge11(emu)
emu.verbose = 0
emu.run()
关键操作速查表
寄存器访问
| 操作 | 示例 |
|---|---|
| 读取 EAX | val = ql.arch.regs.eax |
| 写入 RDI | ql.arch.regs.rdi = 0x4000 |
| 获取 PC | pc = ql.arch.regs.arch_pc |
| 获取 SP | sp = ql.arch.regs.arch_sp |
内存操作
ql.mem.map(addr, size):映射内存ql.mem.read(addr, n):读取 n 字节ql.mem.write(addr, data):写入字节数据ql.mem.string(addr):读取 C 字符串ql.mem.search(pattern):全局搜索字节序列
数据打包/解包
自动适配架构位数与字节序:
ql.pack32(0x12345678)→b'\x78\x56\x34\x12'(小端)ql.unpack64(data):解包为 64 位整数
钩子机制
- 地址钩子:
ql.hook_address(callback, 0x401000) - 系统调用钩子:
ql.os.set_syscall('read', my_read) - API 钩子:
ql.os.set_api('printf', fake_printf, QL_INTERCEPT.CALL) - 内存访问钩子:
ql.hook_mem_read(log_fn, 0x5000, 0x6000)
虚拟文件系统 (VFS)
通过 QlFsMappedObject 自定义文件行为:
class FakeProc(QlFsMappedObject):
def read(self, size): return b"fake content"
def close(self): return 0
ql.add_fs_mapper("/proc/cpuinfo", FakeProc())
远程调试支持
启用 GDB 兼容调试服务器:
ql.debugger = "gdb:127.0.0.1:9999"
或使用内置 Qdb 调试器:
ql.debugger = "qdb::rr" # 启用记录回放
扩展开发建议
对于未实现的系统调用或 API,推荐先通过 set_syscall 或 set_api 进行原型验证,再贡献至核心代码库。POSIX 系统调用实现在 qiling/os/linux/syscall/ 目录,Windows API 在 qiling/os/windows/dll/。