新竹县网站建设_网站建设公司_营销型网站_seo优化
2026/1/13 19:31:48 网站建设 项目流程

x64dbg实战:手把手教你实现函数追踪与参数解析

你有没有遇到过这样的场景?面对一个闭源的加密程序,你想搞清楚它是如何调用核心加密函数的,但没有源码、没有符号信息,甚至连入口点都找不到。这时候静态分析就像在黑暗中摸索——你能看到一堆指令,却不知道它们何时被执行、传了什么参数。

而动态分析,就是那束光。

今天,我们就以x64dbg为武器,带你从零开始完成一次完整的函数追踪实战:定位目标函数 → 设置断点 → 提取输入参数 → 监控返回值 → 自动化日志记录。整个过程不依赖任何外部工具(除了x64dbg),适合所有正在学习逆向工程或安全研究的同学。


为什么是 x64dbg?

市面上的调试器不少,比如老牌的 OllyDbg、微软官方的 WinDbg,还有 IDA Pro 的调试模块。但如果你要选一个现代、免费、功能强大又易于上手的 Windows 调试器,x64dbg 是目前最值得推荐的选择

它不只是“能用”,而是真正做到了:
- 支持 x86/x64 双架构
- 图形界面现代化,操作直观
- 内建脚本系统 + Python 插件扩展
- 社区活跃,GitHub 持续更新
- 完全开源,无后门风险

更重要的是,它的设计哲学非常贴近实际分析需求:让你把精力集中在逻辑推理上,而不是和工具较劲


我们要做什么?一个真实案例

假设我们拿到了一个名为crypto_tool.exe的程序,它会对用户输入进行 AES 加密,并输出结果。但我们不知道加密发生在哪个函数里,也不知道密钥是怎么处理的。

我们的目标是:
1. 找到负责加密的核心函数(暂且叫它EncryptData
2. 追踪每次调用时传入的明文和密钥
3. 观察返回后的密文是否写入正确位置
4. 实现自动化日志记录,避免手动重复操作

听起来复杂?别急,我们一步步来。


第一步:启动并附加目标程序

打开 x64dbg,点击 “File” → “Open”,选择你的目标程序。如果程序已经运行(例如服务进程),也可以通过 “Attach to Process” 方式附加。

加载完成后,你会看到几个关键窗口:
-CPU 窗口:显示反汇编代码、寄存器、堆栈、内存等
-模块列表(Module Map):查看程序加载了哪些 DLL
-字符串窗口(Strings):提取程序中出现的所有可读文本

先别急着运行,我们要找线索。


第二步:通过字符串定位关键函数

很多程序会在错误提示、日志输出中留下蛛丝马迹。我们不妨看看有没有和加密相关的字符串。

点击菜单栏 “Search” → “Current Module” → “String references”,或者直接按快捷键Alt+S

很快你会发现类似这样的字符串:

"Encryption started" "Key length invalid" "AES encryption completed"

太好了!右键点击"AES encryption completed"→ “Find references”,跳转到引用它的代码位置:

call sub_140001A00 test eax, eax jz short loc_140001B20 mov rcx, offset aAesEncryptionCompleted ; "AES encryption completed" call printf

看到call sub_140001A00了吗?这个函数很可能就是我们要找的EncryptData

双击进入该函数,你会发现它做了以下几件事:
- 检查参数合法性
- 初始化 AES 上下文
- 调用底层加密例程
- 返回状态码

现在我们确认了:0x140001A00 就是我们要追踪的目标函数地址


第三步:理解 x64 调用约定 —— 参数从哪来?

在 x86 时代,不同编译器使用不同的调用约定(__cdecl、__stdcall、__fastcall),让人头疼。但在x64 下,Windows 统一采用 Microsoft x64 calling convention,规则清晰明确:

参数序号整型/指针浮点
第1个RCXXMM0
第2个RDXXMM1
第3个R8XMM2
第4个R9XMM3
第5+个压栈(从右至左)压栈

返回值放在RAX中。

所以,如果我们想获取EncryptData的前两个参数(比如明文指针和密钥指针),只需要看RCX 和 RDX的值即可。

📌 提示:结构体指针也很常见。如果 RCX 指向一块内存,记得去数据转储窗口查看其内容,可能是一个包含多个字段的上下文结构。


第四步:设置断点并观察参数

回到 CPU 窗口,在地址0x140001A00处按下F2,设置一个软件断点(你会看到地址背景变红)。

然后按F9运行程序,触发加密操作。

程序中断后,观察右侧寄存器面板:

RCX: 000001A8D3F40000 ← 明文缓冲区 RDX: 000001A8D3F40100 ← 密钥缓冲区 R8: 0000000000000010 ← 明文长度 = 16 字节 R9: 0000000000000020 ← 密钥长度 = 32 字节

接下来,我们可以验证这些地址的内容。

右键点击 RCX 的值 → “Follow in Dump”,在下方“数据转储”窗口中切换编码方式(ASCII/UTF-8),看到明文确实是"HelloWorld123456"

同理查看 RDX,发现密钥是"603DEB1015CA71BE..."—— 典型的 AES-256 密钥格式。

这说明我们成功捕获了函数调用的真实输入!


第五步:监控函数返回与输出结果

仅仅知道输入还不够,我们还想知道加密后的密文写到了哪里。

有两种方法可以做到:

方法一:在调用方下一条指令设断点

返回到原来的call EncryptData处,找到下一条指令地址(比如0x140001A50),在那里再设一个断点(F2)。当函数执行完返回时,程序会再次中断。

此时你可以检查:
- RAX 是否为 0(表示成功)
- 输出缓冲区是否有新数据写入
- 是否调用了printf或文件写入 API

方法二:使用栈上的返回地址设内存断点

更高级的做法是利用栈机制自动监听函数返回。

我们知道,函数调用时会把返回地址压入栈顶([RSP])。因此可以在[RSP]处设置内存访问断点,当函数ret时弹出该地址就会触发中断。

操作步骤:
1. 在函数入口中断后,记下[RSP]的值(比如0x140001A50
2. 右键 → “Breakpoint” → “Memory on access”
3. 地址填rsp, 大小8, 类型Read/Write

这样当函数执行ret指令时,就会命中这个断点,无需手动寻找调用者。


第六步:自动化!用脚本批量记录调用日志

如果函数被频繁调用(比如每秒几十次),手动检查显然不可行。我们必须借助自动化。

x64dbg 支持两种脚本方式:内置脚本语言 和 Python 插件(x64dbgpy)。

方案一:使用内建脚本(轻量级)

新建一个文件trace_encrypt.dbg

; trace_encrypt.dbg ; 功能:自动记录 EncryptData 的参数与返回值 entry: msg "Setting up trace on EncryptData..." bp 0x140001A00, "on_entry" ret on_entry: log "=== ENCRYPT CALL ===" log "Time: %t" log "Input Buffer (RCX): 0x%I64X", rcx log "Key Buffer (RDX): 0x%I64X", rdx log "Plaintext: '%s'", string(rcx) log "Key: '%s'", string(rdx) ; 设置返回监控 bpr esp, 8, 2, "on_return" ret on_return: log "Return Value (RAX): 0x%I64X", rax log "Output likely at: 0x%I64X", rcx ; 假设原地加密 log "" bc esp ; 清除内存断点 go ; 继续运行

保存后,在 x64dbg 命令行输入:

script load "C:\path\to\trace_encrypt.dbg" script run entry

从此以后,每一次EncryptData被调用,都会在日志窗口自动生成一条完整记录。

💡 技巧:%t可打印时间戳;string(addr)尝试解析 C 风格字符串;log ""输出空行便于阅读。


方案二:使用 Python 插件(灵活强大)

如果你启用了x64dbgpy插件,可以用 Python 写更复杂的逻辑。

示例脚本encrypt_tracer.py

from x64dbg import * def on_function_enter(): print(f"[+] Entering EncryptData @ {GetEIP():X}") plaintext_addr = Register("rcx") key_addr = Register("rdx") # 读取前16字节作为样本 plain_data = ReadByteArray(plaintext_addr, 16) key_data = ReadByteArray(key_addr, 32) print(f" Plaintext: {bytes(plain_data).decode('latin1', 'ignore')!r}") print(f" Key: {key_data.hex()}") # 绑定断点 AddBreakpoint(0x140001A00, BPS_NORMAL, on_function_enter)

这种方式更适合做数据分析、网络上报、甚至集成 fuzzing 框架。


常见问题与避坑指南

❌ 问题一:函数没符号怎么办?

不是所有函数都有名字。解决办法:
- 用字符串交叉引用定位
- 查找特征指令序列(如mov rax, 0x41414141
- 使用插件 Scylla 扫描已知库特征

❌ 问题二:断点被检测或绕过?

某些程序会检测 INT3 指令(软件断点的本质)来反调试。

应对策略:
- 改用硬件断点(最多4个,不修改内存)
- 使用内存断点监控代码页变化
- 启用 TitanHide 插件隐藏调试器痕迹

❌ 问题三:参数是指针嵌套结构怎么办?

比如RCX -> struct { char* data; int len; void* ctx; }

解决方案:
- 在数据转储窗口跟随指针(Follow in Dump)
- 手动计算偏移(如RCX+8是 ctx 地址)
- 使用 “Analyze Structure” 插件辅助解析


最佳实践建议

  1. 善用标签与注释
    - 右键地址 → “Label” 给关键函数命名
    - 使用 “Comment” 添加分析备注,方便后续回顾

  2. 结合 IDA 预分析
    - 先用 IDA 分析控制流、识别函数边界
    - 导出.sig文件供 x64dbg 加载,提升识别率

  3. 控制日志频率
    - 高频调用函数启用条件断点:bp EncryptData if rcx == 0x12345678
    - 或在脚本中加入过滤逻辑,只记录特定情况

  4. 保护原始程序
    - 调试前备份原文件,防止误改造成崩溃
    - 如需打补丁,使用“Copy Patch to Executable”功能生成新文件


结语:掌握这项技能意味着什么?

当你能在几分钟内定位一个未知函数、还原其调用参数、监控执行流程时,你就不再只是一个“看代码的人”,而是变成了一个程序行为的掌控者

无论是分析恶意软件的通信协议,还是破解某个 DRM 机制,亦或是复现已知漏洞的触发路径,这套基于 x64dbg 的动态追踪方法都能成为你最可靠的起点。

而这套能力的核心,并不是记住多少快捷键,而是建立一种思维方式:

程序的行为是可以被观测的,只要你在正确的时机、正确的地点,放上一只“眼睛”

那只眼睛,可以是一个断点,一行脚本,或一段 Python 回调。

现在,轮到你动手试试了。

如果你在实践中遇到了其他挑战 —— 比如无法解析结构体、断点失效、多线程干扰 —— 欢迎在评论区留言讨论,我们一起拆解每一个技术难题。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询