ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

pwndbg stepret 命令详解:逐步执行并停在下一条 return 类指令

pwndbg stepret 命令详解:逐步执行并停在下一条 return 类指令 pwndbg stepret 命令详解逐步执行并停在下一条 return 类指令【免费下载链接】pwndbgExploit Development and Reverse Engineering with GDB LLDB Made Easy项目地址: https://gitcode.com/GitHub_Trending/pw/pwndbgstepret是 pwndbg 的 NEXT步进/继续命令家族成员用于让被调试程序从当前 PC 开始“逐步执行”直到命中下一条 return-like返回类指令为止。它面向逆向工程与漏洞利用开发中的函数边界定位、ROP 链分析等场景让开发者不必手动敲几十次stepi就能精确停在函数返回点。阅读本文后你将掌握stepret的用法、其“通过 stepping 到达 return 指令”的底层实现原理、与同类命令nextret的差异以及它在仓库测试中的验证方式。命令速览stepret的命令声明位于 pwndbg/commands/next.pyusage: stepret [-h]官方帮助文本即命令 docstring也是 docs/commands/step_next_continue/stepret.md 的核心内容Breaks at next return-like instruction by stepping to it.通过逐步执行的方式停在下一条 return 类指令。可选参数短参数长参数说明-h--help显示本命令的帮助信息并退出stepret没有任何业务参数在 GDB/LLDB 中直接输入命令名即可执行例如pwndbg stepret核心语义什么是“return-like instruction”所谓 return-like返回类指令指的是执行后会从当前函数返回、并把控制流交还给调用者的指令例如 x86 的ret、AArch64 的ret、RISC-V 的ret/jalr x0别名等。pwndbg 对这类指令的判定并不依赖某个架构的助记符硬编码而是使用 Capstone 反汇编引擎的指令分组instruction groups标记。在 pwndbg/commands/next.py 中导入了from capstone6pwndbg import CS_GRP_RET然后在 pwndbg/aglib/next.py 中同样使用该常量。判断逻辑则位于 pwndbg/aglib/disasm/instruction.py每个反汇编出的PwndbgInstruction都会携带 Capstone 的分组集合self.groups: set[int] set(cs_insn.groups) # 适用于所有架构的分组CS_GRP_INVALID | CS_GRP_JUMP | CS_GRP_CALL | CS_GRP_RET | CS_GRP_INT | CS_GRP_IRET ...因此只要CS_GRP_RET in ins.groups成立该指令就被认定为 return-like。由于CS_GRP_RET是 Capstone 对所有受支持架构统一标注的通用分组stepret天然具备跨架构能力x86/x64、ARM、AArch64、MIPS、RISC-V、PowerPC、SPARC、s390x、LoongArch 等架构下的返回指令都能被识别无需逐架构写特例。实现原理_stepret执行控制器源码走读stepret的命令入口本身很薄真正的逻辑在异步执行控制器_stepret中位于 pwndbg/commands/next.pypwndbg.commands.Command( Breaks at next return-like instruction by stepping to it., categoryCommandCategory.NEXT ) pwndbg.commands.OnlyWhenRunning def stepret() - None: Breaks at next return-like instruction by stepping to it pwndbg.dbg.selected_inferior().dispatch_execution_controller(_stepret)async def _stepret(ec: pwndbg.dbg_mod.ExecutionController): # We may be starting on a ret. This prevents us from just sitting on the ret if pwndbg.aglib.proc.alive(): await ec.single_step() while pwndbg.aglib.proc.alive() and ( ins : await pwndbg.aglib.next.break_next_branch(ec, including_currentTrue) ): if CS_GRP_RET in ins.groups: break # Here we are e.g. on a CALL instruction (temporarily breakpointed by break_next_branch) # We need to step so that we take this branch instead of ignoring it await ec.single_step()第一步避免“卡在当前的 ret 上”循环之前有一段关键处理pwndbg/commands/next.py# We may be starting on a ret. This prevents us from just sitting on the ret if pwndbg.aglib.proc.alive(): await ec.single_step()如果程序此刻恰好已经停在一个ret指令上直接开始查找会让命令原地命中、什么也不做。所以stepret会先单步一次越过当前位置再开始搜索下一个 return-like 指令。第二步在分支之间“蹦跳”前进主循环调用的核心是 pwndbg/aglib/next.py 中的break_next_branchasync def break_next_branch(ec, addressNone, including_currentFalse): ins next_branch(address, including_currentincluding_current) if not ins: return None inf pwndbg.dbg.selected_inferior() # If the branch we found was not at the current program counter, we should step to it. if not including_current or ins.address ! pwndbg.aglib.regs.pc: with inf.break_at(BreakpointLocation(ins.address), internalTrue) as bp: await ec.cont(bp) return ins它调用同步函数next_branchpwndbg/aglib/next.py从当前 PC 开始向前反汇编找到程序流经的下一条分支类指令jump/call/ret 都算分支然后在其地址上设置一个内部临时断点并继续执行。注意including_currentTrue的语义如果当前 PC 本身就停在分支指令上就不再单步、直接把这条指令返回避免原地打转。循环体的判定逻辑很简洁pwndbg/commands/next.py若找到的指令分组包含CS_GRP_RET说明这就是目标 return 指令break退出循环命令结束PC 停在 ret 上否则例如命中的是一条call说明还需要继续前进此时调用await ec.single_step()真正“执行”这条分支进入被调用函数然后进入下一轮循环继续在函数内部寻找下一个分支。源码注释也明确解释了这一设计意图Here we are e.g. on a CALL instruction (temporarily breakpointed bybreak_next_branch). We need to step so that we take this branch instead of ignoring it.也就是说stepret会真实进入被调用的函数直到执行路径上遇到第一个返回指令为止——这正是它与nextret的语义分界点。stepret 与 nextret 的区别nextret见 docs/commands/step_next_continue/nextret.md的帮助文本是Breaks at next return-like instruction.其底层实现是 pwndbg/aglib/next.py 的break_next_ret循环调用break_next_branch(ec)不带including_current遇到CS_GRP_RET才返回async def break_next_ret(ec, including_currentFalse): if including_current: ins pwndbg.aglib.disasm.disassembly.one(pwndbg.aglib.regs.pc) ... if CS_GRP_RET in ins.groups: return ins while pwndbg.aglib.proc.alive(): if pwndbg.aglib.proc.stopped_with_signal(): return None # rets are assumed to be a subset of branches ins await break_next_branch(ec) if not ins: break if CS_GRP_RET in ins.groups: return ins两者的本质差异体现在两处对“当前就在 ret 上”的处理stepret会先主动single_step一步越过当前位置保证命令有实际效果而nextret若处于 ret 上只有传入including_currentTrue才会直接返回当前指令。对分支的“步进”方式stepret在命中非 ret 分支如 call时显式single_step以“踏入”该分支代码注释称之为 stepping to itnextret则更多依赖临时断点 cont在分支间跳跃。因此stepret的行为更贴近“一步步走过去”这也是它名称中step的由来。在实战中两者通常可以互换用于“停在下一个函数返回处”若你更关心“纯粹继续运行直到第一个 ret”优先使用nextret若希望语义更贴近逐步执行、并可靠处理起始点恰好是 ret 的情况则使用stepret。前置条件与行为边界stepret声明了pwndbg.commands.OnlyWhenRunning装饰器pwndbg/commands/next.py与 NEXT 家族的nextcall、nextjmp、nextret等命令保持一致仅在程序正在运行时可用。若程序尚未启动GDB/LLDB 会给出类似如下提示stepret: The program is not being run.这一点被仓库测试显式覆盖见 tests/library/gdb/tests/test_commands_next.pypytest.mark.parametrize(command, NEXT_COMMANDS) def test_next_commands_binary_not_running(command): out gdb.execute(command, to_stringTrue) assert out f{command}: The program is not being run.\n此外还有两点安全边界崩溃程序不冻结test_next_command_doesnt_freeze_crashed_binarytests/library/gdb/tests/test_commands_next.py 与 tests/library/dbg/tests/test_commands_next.py验证了对已崩溃crash程序执行stepret不会让调试会话卡死。原因在于next_branch在无法继续反汇编时会返回None循环随之正常退出。进程退出即停止循环条件显式包含pwndbg.aglib.proc.alive()进程结束后循环自动终止不会出现无限单步。测试验证与测试二进制stepret被纳入 NEXT 命令族的统一参数化测试测试列表定义于 tests/library/dbg/tests/test_commands_next.pyNEXT_COMMANDS ( pc, nextcall, nextjmp, nextproginstr, nextret, nextsyscall, stepret, stepsyscall, )该文件中的test_next_command_doesnt_freeze_crashed_binary会对列表内每个命令逐一执行崩溃程序测试tests/library/gdb/tests/test_commands_next.py 也有相同集合。这说明stepret与家族其他命令共享同一套回归保障。与 ret 语义直接相关的是test_nextret_looptests/library/dbg/tests/test_commands_next.py它针对nextret验证在包含循环的程序上执行命令后PC 最终停在X86_INS_RET上。对应的测试二进制为 tests/binaries/host/loop_instruction_ending_in_ret.x86-64.asm其结构是一段inc ecx; cmp ecx, 4; jl loop的小循环、若干nop填充、最后以一个ret结尾并在 ret 之后放置无法解码的字节db 0xFF, 0xFF以截断反汇编——这正好覆盖了“执行路径上存在循环分支、且终点是 ret”的典型场景与stepret的分支间蹦跳逻辑高度同源可作为理解stepret行为的上手指引。与 NEXT 命令家族的协同stepret属于CommandCategory.NEXT类别与它并列的“步进/继续”命令都定义在 pwndbg/commands/next.py 中完整列表及文档见 docs/commands/index.md 与 docs/commands/step_next_continue/命令作用文档stepret逐步执行并停在下一条 return 类指令stepret.mdnextret停在下一条 return 类指令nextret.mdnextcall停在下一条 call 指令可带符号正则nextcall.mdnextjmp停在下一条跳转指令nextjmp.mdnextproginstr停在属于目标程序的指令上nextproginstr.mdnextsyscall/stepsyscall停在下一次系统调用nextsyscall.md、stepsyscall.mdstepover在指定指令之后设置断点stepover.mdstepuntilasm停在匹配助记符/操作数的指令stepuntilasm.md它们共享同一个架构命令入口通过dispatch_execution_controller分发异步执行控制器控制器内部借助pwndbg.aglib.next提供的break_next_branch、break_next_call、break_next_ret等原语在分支/调用/返回指令上设置内部临时断点。理解了stepret就等于理解了整个 NEXT 家族的工作方式。小结stepret是 pwndbg 中一个简洁但设计考究的步进命令它以 Capstone 的CS_GRP_RET分组作为跨架构的 return 指令判定依据通过“内部断点蹦跳到分支 单步踏入分支”的循环把 PC 精确带到下一条返回指令并专门处理了“起始点恰为 ret”与“进程退出/崩溃”两类边界情况。对于函数边界确认、ROP gadget 定位、调用链回溯前的返回点抓取等逆向与利用开发工作它都是值得加入日常工具箱的高频命令。【免费下载链接】pwndbgExploit Development and Reverse Engineering with GDB LLDB Made Easy项目地址: https://gitcode.com/GitHub_Trending/pw/pwndbg创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表