Qiling
参考文献
官方网站:https://qiling.io/
文档:https://docs.qiling.io/en/latest/
相关信息集合:https://github.com/qilingframework/qiling/issues/134
练习用:https://www.shielder.com/blog/2021/07/qilinglab-release/
原理
基于Unicorn实现的,但是更多添加了逆向分析的风味。
环境搭建
安装
pip install qiling
模拟Arm架构
在Windows上模拟Linux
要用上Linux的Rootfs,可以去官方仓库的examples文件夹中下。
Qiling 框架组件
Qiling 框架包含三大核心组件:
加载器(Loader)
- 功能:识别文件(操作系统、架构)、加载文件、映射 shellcode、设置栈 / 内存 / 堆、配置环境变量(ENV)和命令行参数(ARGV)。
- 相关文件:
qiling/``utils.py、qiling/``const.py、qiling/loader/*.py。
架构(Arch)
- 功能:配置 CPU 特性(如 VFP)、架构特定函数(如 GS/FS 段)、初始化 TLS(线程局部存储)。
- 相关文件:
qiling/arch/*.py。
操作系统(Operating System)
- 初始化阶段:设置 CPU、初始化 OS 组件(输出、标准 IO、注册表、线程管理)、映射 API 或系统调用。
- 运行阶段:执行模拟逻辑。
- 相关文件:
qiling/os/*.py。
Qilinglabs
这是一个练习题目,上面已经放过链接了。下面是writeup
参考:Qiling入门与QilingLab
来自:qiling-labs练习
from qiling import *
from qiling.const import *
from qiling.os.const import *
from qiling.os.mapper import QlFsMappedObject
import structdef challenge1(ql: Qiling):ql.mem.map(0x1000, 0x1000)ql.mem.write(0x1337, ql.pack16(1337))def hook_rdi(ql: Qiling,*args):rdi = ql.arch.regs.rdiql.mem.write(rdi, b'QilingOS\x00')ql.mem.write(rdi + 65 * 3, b'ChallengeStart\x00')def challenge2(ql: Qiling):ql.os.set_syscall('uname', hook_rdi, QL_INTERCEPT.EXIT)class my_uradom(QlFsMappedObject):def read(self, size):if size == 1:return b'\x10' else:return b'\x00' * size def close(self):return 0def hook_getrandom(ql:Qiling, buf, size, flags):ql.mem.write(buf, b'\x00' * size) ql.arch.regs.rax = 0def challenge3(ql:Qiling):ql.os.set_syscall('getrandom', hook_getrandom, QL_INTERCEPT.CALL)ql.add_fs_mapper('/dev/urandom', my_uradom())def hook_eax(ql:Qiling):ql.arch.regs.eax = 1def challenge4(ql:Qiling):base_address = ql.loader.load_address# 计算目标钩子地址(0xE43 是 cmp 指令的位置)hook_addr = base_address + 0xE43print(f"设置钩子地址: {hex(hook_addr)}")ql.hook_address(hook_eax, hook_addr)def hook_rand(ql:Qiling):ql.arch.regs.rax = 0def challenge5(ql:Qiling):ql.os.set_api('rand', hook_rand, QL_INTERCEPT.CALL)def hook_while_true(ql: Qiling):ql.arch.regs.rax = 0def challenge6(ql: Qiling):libc_base = ql.loader.load_addressql.hook_address(hook_while_true, libc_base + 0xF16)def hook_sleep(ql: Qiling):returndef challenge7(ql: Qiling):ql.os.set_api('sleep', hook_sleep, QL_INTERCEPT.CALL)def hook_sleep(ql: Qiling):returndef challenge7(ql: Qiling):ql.os.set_api('sleep', hook_sleep, QL_INTERCEPT.CALL)def hook_mem(ql:Qiling):rax = ql.arch.regs.rax # 结构体首地址ql.log.info(f"\u001b[31m[+] rax: {hex(rax)}\u001b[0m")data = ql.mem.read(rax, 24) # 读取结构体数据ql.log.info(f"\u001b[31m[+] data: {data.hex()}\u001b[0m")str_addr, magic_num, check_addr = struct.unpack("QQQ", data) # 解析结构体数据ql.log.info(f"\u001b[31m[+] str_addr: {hex(str_addr)}\u001b[0m")ql.mem.read(str_addr, 0x10) # 读取字符串数据ql.log.info(f"\u001b[31m[+] str: {ql.mem.string(str_addr)}\u001b[0m")ql.log.info(f"\u001b[31m[+] magic_num: {hex(magic_num)}\u001b[0m")ql.log.info(f"\u001b[31m[+] check_addr: {hex(check_addr)}\u001b[0m")ql.mem.write(check_addr, b'\x01') # 将check_addr的值设置为1check = ql.mem.read(check_addr, 8) # 读取check_addr的值ql.log.info(f"\u001b[31m[+] check: {check.hex()}\u001b[0m")def challenge8(ql: Qiling):libc_base = ql.loader.load_addresshook_addr = libc_base + 0x00FB5ql.log.info(f"\u001b[31m[+] hook_addr: {hook_addr}\u001b[0m")ql.hook_address(hook_mem, hook_addr) def hook_data(ql:Qiling):print("challenge9")rax = ql.arch.regs.raxprint("data",ql.mem.read(rax,8))ql.mem.write(rax,b'\x01')print("has change")def challenge9(ql: Qiling):libc_base = ql.loader.load_addresshook_addr = libc_base + 0x1061 ql.log.info(f"\u001b[31m[+] hook_addr: {hook_addr}\u001b[0m")ql.hook_address(hook_data, hook_addr) def hook_strcmp(ql: Qiling):print("start challenge 10 hook")data_address = ql.arch.regs.raxql.log.info(f"\u001b[31m[+] data_address: {hex(data_address)}\u001b[0m")ql.mem.read(data_address, 0x10) # 读取字符串数据ql.log.info(f"\u001b[31m[+] str: {ql.mem.string(data_address)}\u001b[0m") ql.mem.write(data_address, b'qilinglab\x00') # 将check_addr的值设置为1ql.log.info(f"\u001b[31m[+] str: {ql.mem.string(data_address)}\u001b[0m")def challenge10(ql: Qiling):print("start challenge 10")libc_base = ql.loader.load_addresshook_addr = libc_base + 0x112D ql.log.info(f"\u001b[31m[+] hook_addr: {hook_addr}\u001b[0m")ql.hook_address(hook_strcmp, hook_addr) def challenge10(ql: Qiling):print("start challenge 10")libc_base = ql.loader.load_addresshook_addr = libc_base + 0x112D ql.log.info(f"\u001b[31m[+] hook_addr: {hook_addr}\u001b[0m")ql.hook_address(hook_strcmp, hook_addr) def hook_cpuid(ql: Qiling, *args):ql.arch.regs.ebx = 0x696C6951ql.arch.regs.ecx = 0x614C676Eql.arch.regs.edx = 0x20202062def challenge11(ql: Qiling):libc_base = ql.loader.load_addresshook_addr = libc_base + 0x1191ql.hook_address(hook_cpuid, hook_addr)if __name__ == '__main__':path = [r'D:\qil\qilinglab-x86_64']rootfs = r"D:\qil\qiling\examples\rootfs\x8664_linux"ql = Qiling(path, rootfs)challenge1(ql)challenge2(ql)challenge3(ql)challenge4(ql)challenge5(ql)challenge6(ql)challenge7(ql)challenge8(ql)challenge9(ql)challenge10(ql)challenge11(ql)ql.verbose = 0ql.run()
速记
寄存器
| 操作 | 示例 | 说明 |
|---|---|---|
| 读取(通过寄存器名) | ql.arch.regs.read("EAX") |
支持大小写(如 "eax" 或 "EAX") |
| 读取(通过 Unicorn 常量) | ql.arch.regs.read(UC_X86_REG_EAX) |
需导入对应架构的 Unicorn 常量 |
| 读取(直接属性) | eax = ql.arch.regs.eax |
最简洁的方式 |
| 写入(通过寄存器名) | ql.arch.regs.write("EAX", 0xFF) |
- |
| 写入(通过 Unicorn 常量) | ql.arch.regs.write(UC_X86_REG_EAX, 0xFF) |
- |
| 写入(直接属性) | ql.arch.regs.eax = 0xFF |
- |
| 获取寄存器表 | ql.arch.regs.register_mapping() |
返回当前架构的寄存器映射表 |
| 获取寄存器位数 | ql.arch.reg_bits("rax") |
64 位环境下返回 64,ql.arch.reg_bits("eax")返回 32 |
| 当前架构的程序计数器(PC),自动适配架构(如 x86 的 eip、ARM 的 pc) | ql.arch.regs.arch_pc |
- |
| 当前架构的栈指针(SP),自动适配架构(如 x86 的 esp、ARM 的 sp) | ql.arch.regs.arch_sp |
- |
栈操作
| 操作 | 示例 | 说明 |
|---|---|---|
| 出栈 | value = ql.arch.stack_pop() |
从栈顶弹出一个值 |
| 入栈 | ql.arch.stack_push(value) |
向栈顶压入一个值 |
| 栈内读取(不修改 SP) | value = ql.arch.stack_read(offset) |
读取栈顶偏移offset处的值(偏移可正 / 负 / 零) |
| 栈内写入(不修改 SP) | ql.arch.stack_write(offset, value) |
向栈顶偏移offset处写入值 |
内存子系统
内存管理方法
| 方法 | 描述 |
|---|---|
map |
在指定位置映射内存区域,使其可访问 |
unmap |
回收已映射的内存区域 |
unmap_all |
回收所有已映射的内存区域 |
map_anywhere |
在未指定位置映射内存区域 |
protect |
修改已映射区域的访问权限(读 / 写 / 执行) |
内存读写与搜索
| 操作 | 示例 | 说明 |
|---|---|---|
| 搜索字节模式 | addr = ql.mem.search(b"\xFF\xFE\xFD") |
搜索整个内存;可指定范围begin/end |
| 读取内存 | data = ql.mem.read(addr, size) |
从addr读取size字节 |
| 写入内存 | ql.mem.write(addr, data) |
向addr写入data(字节类型) |
| 读取字符串 | s = ql.mem.string(addr) |
从addr读取以 null 结尾的字符串 |
| 写入字符串 | ql.mem.string(addr, "hello") |
向addr写入字符串(自动添加 null 结尾) |
内存状态查询
| 操作 | 示例 | 说明 |
|---|---|---|
| 显示所有映射区域 | for line in ql.mem.get_formatted_mapinfo(): print(line) |
打印内存映射信息 |
| 查找空闲空间 | free_addr = ql.mem.find_free_space(size) |
查找大小为size的空闲区域 |
| 检查可分配性 | ql.mem.is_available(addr, size) |
若addr开始的size字节可分配,返回 True |
| 检查是否已映射 | ql.mem.is_mapped(addr, size) |
若addr开始的size字节已映射,返回 True |
is_available和is_mapped并非完全对立;若内存区域部分已映射,两者均返回 False。
打包与解包
Qiling 提供了架构相关的打包 / 解包函数,自动适配位数和字节序。
也可以直接用struct。
| 函数 | 说明 | 对应 C 类型 | 大小(字节) |
|---|---|---|---|
ql.pack() |
自动按架构位数打包(64 位用pack64,32 位用pack32) |
- | 随架构变化 |
ql.pack64(data) |
64 位无符号打包 | unsigned long long |
8 |
ql.pack32(data) |
32 位无符号打包(含字节序检查) | unsigned int |
4 |
ql.pack16(data) |
16 位无符号打包(含字节序检查) | unsigned short |
2 |
ql.unpack(data) |
自动按架构位数解包 | - | 随架构变化 |
ql.unpack64(data) |
64 位无符号解包 | unsigned long long |
8 |
ql.unpack32(data) |
32 位无符号解包(含字节序检查) | unsigned int |
4 |
ql.unpack16(data) |
16 位无符号解包(含字节序检查) | unsigned short |
2 |
ql.packs() |
自动按架构位数打包有符号数据 | - | 随架构变化 |
ql.pack64s(data) |
64 位有符号打包 | long |
8 |
ql.pack32s(data) |
32 位有符号打包(含字节序检查) | int |
4 |
ql.pack16s(data) |
16 位有符号打包(含字节序检查) | short |
2 |
ql.unpacks(data) |
自动按架构位数解包有符号数据 | - | 随架构变化 |
ql.unpack64s(data) |
64 位有符号解包 | long |
8 |
ql.unpack32s(data) |
32 位有符号解包(含字节序检查) | int |
4 |
ql.unpack16s(data) |
16 位有符号解包(含字节序检查) | short |
2 |
钩子函数
Qiling 提供多种钩子函数,可用于拦截 指令执行、内存访问、系统调用 等事件,实现动态分析与调试。
所有 hook_* 方法注册成功后都会返回一个句柄,可用于后续删除或管理。
不同类型的钩子具有不同的回调参数,例如:
- 指令钩子:回调参数通常为
(ql, addr, size, md) - 内存钩子:回调参数通常为
(ql, access, addr, size, value)
常用钩子类型
| 钩子函数 | 作用 | 示例 |
|---|---|---|
ql.hook_address(callback, address) |
在执行到指定地址时调用回调函数 | python\n def stop(ql):\n ql.emu_stop()\n ql.hook_address(stop, 0x40819a)\n |
ql.hook_code(callback, user_data) |
每执行一条指令触发回调,可用于打印反汇编信息 | python\n def disasm(ql, addr, size, md):\n buf = ql.mem.read(addr, size)\n for insn in md.disasm(buf, addr):\n print(f\"{insn.address:#x}: {insn.mnemonic} {insn.op_str}\")\n ql.hook_code(disasm, user_data=ql.arch.disassembler)\n |
ql.hook_block(callback) |
每执行一个基本块前触发回调 | python\n def log_block(ql, addr, size):\n print(f\"Basic block at {addr:#x}\")\n ql.hook_block(log_block)\n |
ql.hook_intno(callback, intno) |
当触发指定中断号时执行回调 | python\n ql.hook_intno(hook_syscall, 0x80) # 钩住 Linux 的 0x80 系统调用中断\n |
ql.hook_insn(callback, insn_type) |
钩住某种特定指令(如 UC_X86_INS_SYSCALL) |
python\n def handle_syscall(ql):\n print(\"Syscall triggered\")\n ql.hook_insn(handle_syscall, UC_X86_INS_SYSCALL)\n |
内存访问钩子
| 钩子函数 | 作用 | 示例 |
|---|---|---|
ql.hook_mem_unmapped(callback) |
拦截对未映射内存的访问 | 用于检测非法内存访问或动态建页 |
ql.hook_mem_read(callback, begin, end) |
监控 [begin, end) 区间的内存读取 |
python\n def log_read(ql, access, addr, size, val):\n print(f\"Read from {addr:#x}, size {size}\")\n ql.hook_mem_read(log_read, 0x1000, 0x2000)\n |
ql.hook_mem_write(callback, begin, end) |
监控 [begin, end) 区间的内存写入 |
python\n def log_write(ql, access, addr, size, val):\n print(f\"Write to {addr:#x}, value {val:#x}\")\n ql.hook_mem_write(log_write, 0x3000, 0x4000)\n |
ql.hook_mem_invalid(callback) |
拦截未映射或权限错误等无效访问 | 可用于捕获段错误或异常 |
钩子管理
| 操作 | 示例 | 说明 |
|---|---|---|
| 删除单个钩子 | python\n ql.hook_del(hook_handle)\n |
hook_handle 是注册钩子时返回的句柄 |
| 清除所有钩子 | python\n ql.clear_hooks()\n |
一次性移除所有已注册的钩子 |
Qiling 劫持功能汇总表(Hijacking Summary)
| 分类 | 功能 | 方法 / 接口 | 说明 | 示例 / 备注 |
|---|---|---|---|---|
| 标准流劫持 | 劫持程序标准输入(stdin) | ql.os.stdin = pipe.SimpleInStream(fd) |
使用管道模拟标准输入流,从宿主机或自定义内容提供输入数据 | python\nql.os.stdin = pipe.SimpleInStream(0)\nql.os.stdin.write(b'Ea5yR3versing\\n')\n |
| 交互式输入流 | ql.os.stdin = pipe.InteractiveInStream() |
启用交互式输入,可人工输入数据(类似 pwntools.interactive) | python\nql.os.stdin = pipe.InteractiveInStream() # 等待人工输入\n |
|
| 自定义输出流 / 错误流 | ql.os.stdout、ql.os.stderr |
可自定义将输出重定向到文件、socket 或自定义类 | 输出流接口同 stdin,可继承扩展 | |
| 虚拟文件系统 (VFS) 映射 | 映射虚拟路径到宿主机文件 | ql.add_fs_mapper('/dev/urandom', '/dev/urandom') |
当模拟程序访问 /dev/urandom 时,实际读取宿主系统的同名文件 |
路径可为 Linux、Windows 等格式 |
| 映射虚拟路径到自定义对象 | ql.add_fs_mapper('/dev/urandom', FakeUrandom()) |
映射到继承自 QlFsMappedObject 的自定义文件类,可自定义 read()、write() 等行为 |
python\nclass FakeUrandom(QlFsMappedObject):\n def read(self, size): return b'\\x04'\n |
|
| 映射磁盘镜像文件 | ql.add_fs_mapper(0x80, QlDisk(path, 0x80)) |
将磁盘号(如 0x80)映射为镜像文件,实现磁盘访问模拟 | QlDisk 继承自 QlFsMappedObject |
|
| 映射宿主目录 | ql.add_fs_mapper('/proc', '/proc') |
将宿主机目录直接映射用于 /proc、/sys 等虚拟路径 | 常用于嵌入式或 Linux rootfs 模拟 | |
| 系统调用劫持 (POSIX Syscalls) | 注册系统调用 Hook(按名称) | ql.os.set_syscall("write", callback) |
替换或修改指定系统调用,可定义参数与返回值 | python\ndef my_syscall_write(ql, fd, buf, cnt):\n data = ql.mem.read(buf, cnt)\n return cnt\n |
| 注册系统调用 Hook(按编号) | ql.os.set_syscall(callno, callback) |
通过编号绑定 syscall,例如 4 表示 write |
POSIX 系统依据表格定义 | |
| 拦截阶段(可选) | QL_INTERCEPT.CALL / ENTER / EXIT |
CALL:替换实现;ENTER:修改参数;EXIT:修改返回值 | 默认使用 CALL |
|
| POSIX API 劫持(Libc函数) | 设置 C 标准库函数 Hook | ql.os.set_api('puts', callback, QL_INTERCEPT.CALL) |
拦截 puts、printf 等函数调用,常用于分析或篡改行为 |
python\ndef my_puts(ql):\n p = ql.os.resolve_fcall_params({'s': STRING})\n print(p['s'])\n |
| 解析函数参数 | ql.os.resolve_fcall_params({'param': TYPE}) |
自动从模拟栈和寄存器中提取指定类型参数(STRING、POINTER 等) | Qiling 内部实现自动解析 | |
| Windows API 劫持 | 注册 Windows API Hook | ql.os.set_api("memcpy", my_memcpy) |
Hook Windows 系统 API,拦截时可访问参数与返回值 | python\n@winsdkapi(cc=CDECL, params={'dest': POINTER, 'src': POINTER, 'count': UINT})\ndef my_memcpy(ql, addr, params):\n ql.mem.write(params['dest'], ql.mem.read(params['src'], params['count']))\n |
| 装饰器 | @winsdkapi(cc, params={...}) |
指定调用约定(CDECL / MS64)和参数类型 | 在 64 位系统上自动使用 MS64 | |
| Hook 阶段 | QL_INTERCEPT.CALL / ENTER / EXIT |
CALL: 拦截调用;ENTER: 修改参数;EXIT: 修改返回值 | 可组合使用 | |
| UEFI API 劫持 | 注册 UEFI API Hook | ql.os.set_api('SetVariable', hook_func) |
拦截 EFI 变量管理 API 等系统接口 | python\n@dxeapi(params={'VariableName': WSTRING, 'DataSize': UINT, 'Data': POINTER})\ndef hook_SetVariable(ql, addr, params):\n data = ql.mem.read(params['Data'], params['DataSize'])\n ql.env[params['VariableName']] = data\n return EFI_SUCCESS\n |
| 装饰器定义 | @dxeapi(params={...}) |
指定 EFI 参数解析方式(WSTRING、GUID、POINTER 等) | 兼容 DXE 与 SMM 阶段 | |
| 内存管理 Hook 示例(调试工具) | 检测内存泄漏 (malloc/free) |
set_api('malloc', on_exit)、set_api('free', on_enter) |
在 malloc EXIT 时记录返回值地址,在 free ENTER 时检查释放合法性 | 用于检测 double-free 或未释放内存 |
| 参数覆盖机制 | Hook 函数可返回 (address, params) |
修改被调用 API 的参数或调用地址以避免崩溃 | 常用于调整错误调用 | |
| 输入输出管理扩展 (pipe模块) | 简单输入输出流 | pipe.SimpleInStream(fd) / pipe.SimpleOutStream(fd) |
标准输入/输出模拟,用于测试固定输入或静态输出捕获 | 直接写入或读取缓冲 |
| 交互式流 | pipe.InteractiveInStream() |
允许交互输入(命令行键入) | 类似 pwntools 的 interactive() |
|
| 文件流组合 | 可组合 pipe 与 QlFsMappedObject |
用于复杂场景,如网卡或虚拟设备模拟 | 灵活扩展 |
QlFsMappedObject是自定义虚拟文件对象的基类,常需实现:
read()、write()、fstat()、close()等方法。QlDisk继承自QlFsMappedObject,在 BIOS/DOS 环境中用于磁盘仿真。- 所有 Hook 注册函数(
set_syscall、set_api等)都可指定 拦截阶段 (QL_INTERCEPT)。
通过不同阶段可以分别修改系统调用的参数或返回结果。 - Hook 函数一般第一个参数始终为
ql: Qiling对象,可访问内存、寄存器、日志等系统信息。
远程调试
Qiling支持远程调试,可与兼容gdbserver的客户端(如IDA Pro)配合使用。目前Qiling框架仅支持gdbserver,欢迎扩展更多调试服务器。
调试启动
默认情况下,调试服务器监听localhost:9999,且模拟代码会在入口点暂停。
from qiling import *
from qiling.const import QL_VERBOSEdef test_gdb(path, rootfs):ql = Qiling(path, rootfs, verbose=QL_VERBOSE.OFF)# 启用调试器,默认监听本地地址,端口9999ql.debugger = True# 也可自定义地址、端口或调试服务器类型# ql.debugger = ":9999" # GDB服务器监听0.0.0.0:9999# ql.debugger = "127.0.0.1:9999" # GDB服务器监听127.0.0.1:9999# ql.debugger = "gdb:127.0.0.1:9999" # GDB服务器监听127.0.0.1:9999# ql.debugger = "idapro:127.0.0.1:9999" # IDA Pro服务器监听127.0.0.1:9999ql.run()if __name__ == "__main__":test_gdb(["../examples/rootfs/x8664_linux/bin/x8664_hello_static"], "../examples/rootfs/x8664_linux")
与 IDA 配合调试
仅在 IDA Pro 7.4 上测试通过:
启动后配置 IDA Pro 连接到调试服务器(具体步骤参考 IDA 的 GDB 远程调试设置)。
与 GDB 配合调试
(gdb) set architecture i386:x86-64 # 设置架构
(gdb) target remote localhost:9999 # 连接调试服务器
Qdb 调试器
Qdb 是 Qiling 的命令行调试器插件,基于 Qdb 修改而来。
功能
-
命令行交互界面
-
单步执行(
step或s) -
断点设置(
breakpoint或b),继续执行(continue或c) -
内存动态查看(
examine或x) -
记录与回放(
backward或p,需启用rr模式)
使用方法
通过ql.debugger启用 Qdb,支持以下选项:
ql.debugger = "qdb":启用 Qdbql.debugger = "qdb::rr":启用 Qdb 并开启记录回放ql.debugger = "qdb:0x1030c":启用 Qdb 并在0x1030c设置断点
示例:
from qiling import Qilingfrom qiling.const import QL\_VERBOSEif __name__ == "__main__":ql = Qiling([r'rootfs/arm_linux/bin/arm_hello'], r'rootfs/arm_linux', verbose=QL_VERBOSE.DEBUG)ql.debugger = "qdb" # 启用Qdbql.run()
系统调用与 API 实现
由于 Qiling 的设计特性,操作系统 API 和 POSIX 系统调用的覆盖度相比真实内核总有不足(目前覆盖约 40% 的 Windows API、Linux 系统调用,UEFI 覆盖度未知)。社区贡献对完善这些接口至关重要。
9.1 POSIX 系统调用
系统调用按头文件拆分到posix/syscall目录下,例如:
-
syscall/``resource.py包含setpriority(定义于resource.h) -
syscall/``time.py包含clock_gettime(定义于time.h)
添加系统调用后,需在qiling/os/linux/<架构>.py中映射系统调用号与函数。
9.2 操作系统 API(Windows/UEFI)
API 按头文件拆分,例如 Windows 的CreateMutexW和OpenMutexA。对于成对的A(ANSI)和W(宽字符)API,可通过functools.wraps复用实现:
@winsdkapi(cc=STDCALL, dllname=dllname)def hook_CreateMutexW(ql, address, params):# 实现CreateMutexW逻辑try:_type, name = params["lpName"].split("\\\\")except ValueError:name = params["lpName"]_type = ""owning = params["bInitialOwner"]handle = ql.os.handle_manager.search(name)if handle is not None:# ql.os.last_error = ERROR_ALREADY_EXISTSreturn 0else:mutex = Mutex(name, _type)if owning:mutex.lock()handle = Handle(obj=mutex, name=name)ql.os.handle_manager.append(handle)return handle.id@winsdkapi(cc=STDCALL, dllname=dllname)def hook_OpenMutexA(ql, address, params):# 复用W版本的实现return hook_OpenMutexW.__wrapped__(ql, address, params)
9.3 开发流程
-
先用
ql.os.set_api或ql.os.set_syscall测试自定义实现(参考 “钩子函数” 部分); -
测试通过后,将实现合并到核心代码中。