pwndbg patch-revert 命令详解:撤销运行时指令补丁与恢复原始内存
【免费下载链接】pwndbgExploit Development and Reverse Engineering with GDB & LLDB Made Easy项目地址: https://gitcode.com/GitHub_Trending/pw/pwndbg
patch-revert是 pwndbg 中用于撤销patch命令所打补丁的核心配套命令,它通过回写补丁前保存的原始字节,将目标地址的内存恢复到补丁前的状态。本文基于仓库文档与 patch.py 源码,完整讲解patch-revert的用法、参数、批量回滚机制与底层实现原理,帮助你在动态调试、绕过校验、指令级热修改等场景中安全地“反悔”任何已写入的内存修改。
命令概览
usage: patch-revert [-h] addresspatch-revert需要一个位置参数address(要回滚补丁的目标地址),执行后会把该地址的指令字节恢复为调用patch之前的内容。
| 位置参数 | 说明 |
|---|---|
address | 要回滚补丁的地址(Address to revert patch on) |
| 可选参数 | 说明 |
|---|---|
-h,--help | 显示本命令的帮助信息并退出 |
该命令的完整参数定义位于 patch.py 中的parser2:
parser2 = argparse.ArgumentParser(description="Revert patch at given address.") parser2.add_argument("address", type=int, help="Address to revert patch on")它与patch、patch-list同属于 MISC(杂项)命令类别,对应文档见 patch.md 与 patch-list.md。
补丁生命周期:patch → patch-list → patch-revert
patch-revert本身并不独立完成“修改”动作,它依赖patch命令建立的回滚记录。三者构成完整的补丁管理闭环:
patch <address> <ins>:将ins汇编为机器码,写入指定地址,并在内部记录“原始字节 → 新字节”的映射;patch-list:列出当前会话中所有尚未回滚的补丁及其前后指令;patch-revert <address>:按地址回滚单个补丁;传-1则一次回滚全部。
值得注意的是,补丁记录仅存在于当前 GDB/LLDB 会话内存中,进程重启或调试会话结束后即失效,因此patch-revert必须在同一会话内、对仍存活的调试目标使用。
源码实现:回滚是如何工作的
补丁记录的存储结构
在 patch.py 中,所有补丁被保存在模块级字典里:
# Keep old patches made so we can revert them patches: dict[int, tuple[bytes, bytes]] = {}键是补丁地址(int),值是(old_mem, new_mem)二元组:old_mem是打补丁前从目标地址读出的原始字节,new_mem是汇编后写入的新字节。当patch命令执行时(patch.py),它会依次完成汇编、读取原值、记录映射、写入内存四个步骤:
new_mem = pwndbg.aglib.asm.asm(ins) # 1. 汇编指令 old_mem = pwndbg.aglib.memory.read(address, len(new_mem)) # 2. 读取原字节 patches[address] = (old_mem, new_mem) # 3. 记录回滚信息 pwndbg.aglib.memory.write(address, new_mem) # 4. 写入新字节 pwndbg.lib.cache.clear_caches() # 清理缓存其中汇编通过 asm.py 委托给pwndbg.lib.zig.asm,由 Zig 工具链根据当前调试目标的架构(如 x86-64、aarch64、mips 等)完成交叉汇编,相关架构映射见 zig.py。
patch-revert 的三种分支
patch_revert的完整逻辑位于 patch.py,共覆盖四种情况:
def patch_revert(address: int) -> None: if not patches: print(message.notice("No patches to revert")) return if address == -1: for addr, (old, _new) in patches.items(): pwndbg.aglib.memory.write(addr, old) print(message.notice(f"Reverted patch at {addr:#x}")) patches.clear() elif address in patches: old, _new = patches.pop(address) pwndbg.aglib.memory.write(address, old) print(message.notice(f"Reverted patch at {address:#x}")) else: print(message.error(f"Address {address:#x} not found in patch list")) pwndbg.lib.cache.clear_caches()- 无补丁记录:若
patches为空,直接输出提示No patches to revert并返回,不做任何内存写入; address == -1(批量回滚):遍历字典中所有地址,逐一写回old原始字节并打印Reverted patch at 0x...,随后patches.clear()清空全部记录。这是“一次撤销所有补丁”的快捷入口,常用于结束调试会话前的整体清理;- 命中单个地址:从字典中
pop出该地址的记录,写回原始字节,实现精准回滚; - 地址不存在:输出
Address 0x... not found in patch list错误提示,不做任何修改。
无论成功与否(在无记录提前返回的路径之外),函数末尾都会调用pwndbg.lib.cache.clear_caches()清空 pwndbg 的各类缓存,确保后续disasm、telescope等命令读取到的是回滚后的真实内存,而不是缓存中的旧数据。这一点在 test_context_commands.py 中有对应的验证逻辑——该测试通过patch $rip+5 nop;...修改指令后断言反汇编输出确实变为nop,印证了补丁写入与缓存清理的实际效果。
关于“连续补丁同一地址”的注意事项
从数据结构上看,patches[address] = (old_mem, new_mem)是覆盖式赋值:若你在同一地址上先后打了两次补丁,第二次执行patch时读取到的old_mem是第一次补丁后的字节,因此记录被覆盖为“第一次补丁后的状态 → 第二次补丁后的状态”。此时执行patch-revert恢复到的是最近一次补丁前的状态,而非最初的原始指令。若需要精确回退多步,请为不同的地址分别打补丁,或及时patch-revert后再进行下一次修改。
实战示例
假设正在调试一个 x86-64 程序,需要临时绕过某处分支判断,然后恢复原状:
pwndbg> patch $rip+5 nop;nop;nop;nop;nop Patched 5 bytes at 0x5555555551c5 pwndbg> patch-list ────────────────────────────────────[ Patches: ]──────────────────────────────────── 0x5555555551c5 (5 bytes) from: mov edi, 0x1337; mov esi, 0xdeadbeef to : nop; nop; nop; nop; nop pwndbg> patch-revert 0x5555555551c5 Reverted patch at 0x5555555551c5批量回滚所有补丁:
pwndbg> patch-revert -1 Reverted patch at 0x5555555551c5 Reverted patch at 0x555555555200对未打补丁的地址执行回滚则会得到错误提示:
pwndbg> patch-revert 0x555555555300 Address 0x555555555300 not found in patch list适用前提与限制
patch-revert带有OnlyWhenRunning装饰器(见 patch.py),仅在调试会话正在运行时可用,未附加/启动目标时无法执行;- 回滚依赖会话内的
patches记录,切换目标、重启调试会话或目标进程退出后记录即丢失; address参数使用 Pythonint解析(支持0x...十六进制与$reg寄存器表达式),-1被保留为“全部回滚”的特殊值,因此无法用于回滚地址恰为0xFFFFFFFFFFFFFFFF的单条补丁;- 写入依赖调试器对目标内存的可写能力,例如在 qemu-user 的某些旧版本下可能受限(参见 test_aarch64.py 中关于 qemu-user 8.2.2 不支持 GDB 写内存的说明)。
延伸阅读
- patch.md:
patch命令用法,含-q/--quiet静默参数 - patch-list.md:
patch-list命令用法,查看当前全部补丁 - patch.py:
patch、patch-revert、patch-list三个命令的完整源码 - asm.py 与 zig.py:指令汇编与多架构支持实现
- memory.py:
memory.write底层写内存实现
【免费下载链接】pwndbgExploit Development and Reverse Engineering with GDB & LLDB Made Easy项目地址: https://gitcode.com/GitHub_Trending/pw/pwndbg
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考