Qiling使用速记

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.pyqiling/``const.pyqiling/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 struct

def 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.rdi
    ql.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 0

def hook_getrandom(ql:Qiling, buf, size, flags):
    ql.mem.write(buf, b'\x00' * size) 
    ql.arch.regs.rax = 0

def 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 = 1

def challenge4(ql:Qiling):
    base_address = ql.loader.load_address
    # 计算目标钩子地址(0xE43 是 cmp 指令的位置)
    hook_addr = base_address + 0xE43
    print(f"设置钩子地址: {hex(hook_addr)}")
    ql.hook_address(hook_eax, hook_addr)

def hook_rand(ql:Qiling):
    ql.arch.regs.rax = 0


def challenge5(ql:Qiling):
    ql.os.set_api('rand', hook_rand, QL_INTERCEPT.CALL)
    
def hook_while_true(ql: Qiling):
    ql.arch.regs.rax = 0

def challenge6(ql: Qiling):
    libc_base = ql.loader.load_address
    ql.hook_address(hook_while_true, libc_base + 0xF16)


def hook_sleep(ql: Qiling):
    return

def challenge7(ql: Qiling):
    ql.os.set_api('sleep', hook_sleep, QL_INTERCEPT.CALL)

def hook_sleep(ql: Qiling):
    return

def 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的值设置为1
    check = 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_address
    hook_addr = libc_base + 0x00FB5
    ql.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.rax
     print("data",ql.mem.read(rax,8))
     ql.mem.write(rax,b'\x01')
     print("has change")
     

def challenge9(ql: Qiling):
    libc_base =  ql.loader.load_address
    hook_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.rax
    ql.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的值设置为1
    ql.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_address
    hook_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_address
    hook_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 = 0x696C6951
    ql.arch.regs.ecx = 0x614C676E
    ql.arch.regs.edx = 0x20202062

def challenge11(ql: Qiling):
    libc_base = ql.loader.load_address
    hook_addr = libc_base + 0x1191
    ql.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 = 0
    ql.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_availableis_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.stdoutql.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) 拦截 putsprintf 等函数调用,常用于分析或篡改行为 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()
文件流组合 可组合 pipeQlFsMappedObject 用于复杂场景,如网卡或虚拟设备模拟 灵活扩展
  • QlFsMappedObject 是自定义虚拟文件对象的基类,常需实现:
    read()write()fstat()close() 等方法。
  • QlDisk 继承自 QlFsMappedObject,在 BIOS/DOS 环境中用于磁盘仿真。
  • 所有 Hook 注册函数(set_syscallset_api 等)都可指定 拦截阶段 (QL_INTERCEPT)
    通过不同阶段可以分别修改系统调用的参数或返回结果。
  • Hook 函数一般第一个参数始终为 ql: Qiling 对象,可访问内存、寄存器、日志等系统信息。

远程调试

Qiling支持远程调试,可与兼容gdbserver的客户端(如IDA Pro)配合使用。目前Qiling框架仅支持gdbserver,欢迎扩展更多调试服务器。

调试启动

默认情况下,调试服务器监听localhost:9999,且模拟代码会在入口点暂停。


from qiling import *
from qiling.const import QL_VERBOSE

def test_gdb(path, rootfs):

  ql = Qiling(path, rootfs, verbose=QL_VERBOSE.OFF)

  # 启用调试器,默认监听本地地址,端口9999
  ql.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:9999

  ql.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 修改而来。

功能

  • 命令行交互界面

  • 单步执行(steps

  • 断点设置(breakpointb),继续执行(continuec

  • 内存动态查看(examinex

  • 记录与回放(backwardp,需启用rr模式)

使用方法

通过ql.debugger启用 Qdb,支持以下选项:

  • ql.debugger = "qdb":启用 Qdb
  • ql.debugger = "qdb::rr":启用 Qdb 并开启记录回放
  • ql.debugger = "qdb:0x1030c":启用 Qdb 并在0x1030c设置断点

示例:

from qiling import Qiling

from qiling.const import QL\_VERBOSE

if __name__ == "__main__":

  ql = Qiling([r'rootfs/arm_linux/bin/arm_hello'], r'rootfs/arm_linux', verbose=QL_VERBOSE.DEBUG)
  ql.debugger = "qdb"  # 启用Qdb
  ql.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 的CreateMutexWOpenMutexA。对于成对的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_EXISTS

      return 0

  else:

      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 开发流程

  1. 先用ql.os.set_apiql.os.set_syscall测试自定义实现(参考 “钩子函数” 部分);

  2. 测试通过后,将实现合并到核心代码中。

posted @ 2025-11-20 16:11  T0fV404  阅读(0)  评论(0)    收藏  举报