- 网络安全
- 开发工具
【免费下载链接】gef
GEF (GDB Enhanced Features) - a modern experience for GDB with advanced debugging capabilities for exploit devs & reverse engineers on Linux
本文基于 GEF 官方文档 docs/api.md 与核心实现 gef.py,系统讲解如何把 GEF 当作库来使用:在几分钟内编写并加载自定义 GDB 命令、注册随断点事件刷新的上下文面板(Context Pane)、用parse_arguments装饰器规范地解析命令行参数,以及通过继承Architecture类为新目标架构添加支持。读完本文,你可以脱离 GEF 内置命令集,独立扩展出贴合自身逆向工作流的调试工具。
GEF 的扩展定位:一个"电池齐备"的调试环境 + 命令开发框架
GEF(GDB Enhanced Features)的定位是构建在 GDB 之上的现代化调试环境,面向 Linux 平台的漏洞利用开发者与逆向工程师。但正如 docs/api.md 开篇所述,它最重要的价值之一是提供了一整套原语(primitives),让开发者能够快速创建自己的 GDB 命令——"This page intends to summarize how to create advanced GDB commands in moments usingGEFas a library."
从源码结构看,这一设计体现在 gef.py 的全局命令注册表__registered_commands__(gef.py#L163)以及Gef.load()的加载逻辑中:每次加载时,GEF 会将注册表中的类实例化并挂载到 GDB,同时自动为其创建别名(gef.py#L11443-L11478)。此外,社区还有一个官方托管外部脚本的仓库gef-extras(用于存放可贡献的扩展脚本与自定义架构实现),本文不展开,但其目录结构(scripts/、archs/)与本文各节的扩展方式一一对应。
快速开始:最小可用的自定义命令骨架
docs/api.md 给出了创建名为newcmd的 GEF 命令的最基本骨架:
@register class NewCommand(GenericCommand): """Dummy new command.""" _cmdline_ = "newcmd" _syntax_ = f"{_cmdline_}" # optionally # _examples_ = [f"{_cmdline_} arg1 ...", ] # _aliases_ = ["alias_to_cmdline", ] @only_if_gdb_running # not required, ensures that the debug session is started def do_invoke(self, argv): # let's say we want to print some info about the architecture of the current binary print(f"gef.arch={gef.arch}") # or showing the current $pc print(f"gef.arch.pc={gef.arch.pc:#x}")加载与调用同样简单:
gef➤ source /path/to/newcmd.py [+] Loading 'NewCommand' gef➤ newcmd gef.arch=<__main__.X86_64 object at 0x7fd5583571c0> gef.arch.pc=0x55555555a7d0源码级解析:register与GenericCommand到底做了什么
骨架中每个部分都能在 gef.py 中找到对应实现,理解它们能帮助你写出更健壮的命令:
@register装饰器(gef.py#L5294-L5314):对GenericCommand子类执行三项检查——必须具有_cmdline_属性、必须实现do_invoke方法、且_cmdline_不能与已注册命令重名(重名会抛出AlreadyRegisteredException),通过检查后加入全局注册表。因此文档中"命令名冲突"这类问题在注册阶段就会被拦截。GenericCommand基类(gef.py#L5317-L5383):它继承自gdb.Command,并在__init_subclass__中强制要求子类定义_cmdline_与_syntax_(缺失时直接抛NotImplementedError)。_syntax_不只是文档:构造器会将其拼进命令的__doc__,供 GDB 的 help 菜单自动展示。- 调用链与异常兜底:
invoke()方法负责把原始参数字符串经gdb.string_to_argv()拆分后交给你的do_invoke(argv);它还会用bufferize包装输出,并统一捕获异常——非 debug 模式下输出Command '<name>' failed to execute properly的警告,debug 模式下则通过show_last_exception()展示完整堆栈,甚至可通过gef.propagate_debug_exception设置让异常向上抛出(gef.py#L5360-L5376)。注释中特别建议自定义命令捕获具体异常而非泛型Exception,以便 GEF 的异常处理机制正常工作。 - 命令级配置项:
GenericCommand还提供settings机制——通过self["setting_name"]读取以"{cmdline}."为前缀的配置键(gef.py#L5391-L5411),这意味着你的自定义命令也可以拥有自己的gef配置项,与内置命令享受同一套配置体系。
让新命令"常驻":CLI 加载与~/.gdbinit
自定义命令文件既可以在 GDB 提示符下临时加载:
gef➤ source /path/to/newcmd.py也可以写入~/.gdbinit使其随 GDB 启动自动加载:
echo source /path/to/newcmd.py >> ~/.gdbinit一个值得注意的细节:旧版文档推荐的register_external_command()在源码中已被标记为@deprecated,并明确提示 "Useregister(), and inherit fromGenericCommandinstead"(gef.py#L5271-L5274)。新写的扩展请一律使用@register。
自定义上下文面板(Context Pane)
当你希望某段逻辑"像命令一样执行,但在每次断点、单步等 break 类事件后自动刷新,并显示为 GEF context 输出的一部分"时,官方提供了register_external_context_pane()函数。docs/api.md 中的示例是一个"调试计时器"面板:
__start_time__ = int(time.time()) def wasted_time_debugging(): gef_print("You have wasted {} seconds!".format(int(time.time()) - __start_time__)) def wasted_time_debugging_title(): return "wasted:time:debugging:{}".format(int(time.time()) - __start_time__) register_external_context_pane("wasted_time_debugging", wasted_time_debugging, wasted_time_debugging_title)加载方式与命令相同:gef➤ source /path/to/custom_context_pane.py。之后每次停下时,你会在 context 输出的底部附近看到新面板;面板之间的顺序可以通过 GEF 的 context 配置调整。同一个文件中也可以同时注册 Command 和 Context Pane。
Context Pane API 参数说明
该 API 对参数类型要求严格,docs/api.md 给出的签名为:
def register_external_context_pane( name: str, context_callback: Callable[None,[]], context_callback_title: Callable[str, []], condition_callback: Optional[Callable[bool, []]] = None ) -> None各参数含义:
| 参数 | 说明 |
|---|---|
name | 作为面板设置名(settings name)使用的字符串 |
context_callback | 使用gef_print()输出面板内容的函数 |
context_callback_title | 返回标题字符串的函数;返回None则不显示标题 |
condition_callback(可选) | 返回布尔值,决定本次是否显示该面板 |
对照源码(gef.py#L5227-L5253),其实际形参名为pane_name/display_pane_function/pane_title_function/condition,文档 docstring 还给出了一个更贴近实战的用法示例:仅在当前指令是系统调用时显示一个面板——
def only_syscall(): return gef_current_instruction(gef.arch.pc).is_syscall() def display_pane(): gef_print("Wow, I am a context pane!") def pane_title(): return "example:pane" register_external_context_pane("example_pane", display_pane, pane_title, only_syscall)从源码结构看,注册过程比"注册一个回调"要多做一件事:register_external_context_pane会委托给gef.gdb.add_context_pane()(gef.py#L11419-L11441),后者会自动把面板名(空格替换为下划线)追加到context.layout配置项的末尾——这正是文档所说"面板顺序可在 GEF context 配置中修改"的机制:context.layout是一个空格分隔的面板名序列,调整它的顺序与取舍即可控制面板布局。若面板名已存在,注册会被跳过并给出重复告警。
Context Layout Mapping API
另一个更底层的 API 是为已存在的面板注册新的布局映射,用于"接管"现有面板的显示逻辑:
def register_external_context_layout_mapping( current_pane_name: str, display_pane_function: Callable[[], None], pane_title_function: Callable[[], Optional[str]], condition: Optional[Callable[[], bool]] = None ) -> Nonecurrent_pane_name:必须是布局配置中已注册的面板名;display_pane_function:使用gef_print()输出面板内容;pane_title_function:返回标题字符串,返回None则不显示标题;condition(可选):谓词函数,返回True时才显示面板内容与标题,返回False时整个面板被跳过。
从源码看,register_external_context_layout_mapping(gef.py#L5256-L5265)是add_context_pane内部调用的底层入口(后者先改写context.layout,再转入该函数完成映射注册),因此它不会像register_external_context_pane那样自动追加新的 layout 项——它只负责"已有面板名的显示逻辑替换"。
GEF API 核心原语:比 GDB 原生接口更 Pythonic 的写法
GEF API 的设计目标是提供比原生 GDB Python API 更简洁的接口。查看任意函数的完整帮助,可以直接在 GDB/GEF 中用嵌入式 Python 解释器的help:
gef➤ pi help(Architecture)甚至在 GDB 外部:
gdb -q -ex 'pi help(hexdump)' -ex quitdocs/api.md 给出两个基本示例:
读取内存并十六进制转储(对应源码hexdump位于 gef.py#L2213,Memory.read位于 gef.py#L12209,parse_address位于 gef.py#L4516,后者支持$pc、*0x4000、符号名等多种 GDB 地址表达式形式):
gef ➤ pi print(hexdump( gef.memory.read(parse_address("$pc"), length=0x20 ))) 0x0000000000000000 f3 0f 1e fa 31 ed 49 89 d1 5e 48 89 e2 48 83 e4 ....1.I..^H..H.. 0x0000000000000010 f0 50 54 4c 8d 05 66 0d 01 00 48 8d 0d ef 0c 01 .PTL..f...H.....获取内存布局(gef.memory.maps返回Section对象列表,实现见 gef.py#L12274):
gef ➤ pi print('\n'.join([ f"{x.page_start:#x} -> {x.page_end:#x}" for x in gef.memory.maps])) 0x555555554000 -> 0x555555558000 0x555555558000 -> 0x55555555c000 0x55555555c000 -> 0x5555555575000 0x5555555576000 -> 0x5555555577000 0x5555555577000 -> 0x5555555578000 0x5555555578000 -> 0x555555559a000 0x7ffff7cd8000 -> 0x7ffff7cda000 [...]简化命令编写的装饰器
API 还提供了一批装饰器来简化命令的编写(实现集中在 gef.py#L400-L469 的 "Decorators" 区段):
@only_if_gdb_running:仅在存在活动调试会话时执行,否则输出 "No debugging session active" 警告(gef.py#L403-L413);@only_if_gdb_target_local:仅在本地目标(非 GDBremote远程调试)时执行,远程会话下输出 "This command cannot work for remote sessions." 警告(gef.py#L416-L426);- 此外还有
@deprecated(为即将移除的命令附加弃用警告)、@experimental_feature(提示功能不稳定)、@only_if_events_supported(检查 GDB 事件支持情况)等。
输出方面,面板与命令中应统一使用gef_print()(gef.py#L236)而非裸print,以保证 GEF 的样式与缓冲机制一致。
使用parse_arguments规范解析命令参数
当命令需要结构化参数时,@parse_arguments装饰器是基于argparse的官方方案:
@parse_arguments( {"required_argument_1": DefaultValue1, ...}, {"--optional-argument-1": DefaultValue1, ...} )它会在调用do_invoke前完成参数解析,并把结果存入kwargs["arguments"]——因此被装饰函数的签名必须包含*args, **kwargs。参数类型直接从默认值推断,唯一的例外是布尔值:默认值True对应argparse的store_true动作。
几个关键行为细节均可在 gef.py#L495-L566 的实现中得到确认:
- 整型默认值会启用
int_wrapper(int(x, 0)),意味着0x7fff这样的十六进制输入也能被正确解析; - 可变数量参数:把默认值写成
tuple或list即启用nargs="*",元素类型取可迭代对象第一个元素的类型(注意默认值列表不能为空,否则无法推断类型); - 可选参数同样支持列表值:此时会注册为
action="append",可多次出现累积; - 短/长选项组合:参数名可以是
("--long-argument", "-l")这样的元组。
# 可变数量参数 + 选项示例 @parse_arguments( {"instructions": ["nop", "int3", "hlt"], }, {"--arch": "x64", } ) # 组合选项标志示例 @parse_arguments( {}, {("--long-argument", "-l"): value, } )文档中的完整示例:
class MyCommand(GenericCommand): [...] @parse_arguments({"foo": [1,]}, {"--bleh": "", ("--blah", "-l): False}) def do_invoke(self, argv, *args, **kwargs): args = kwargs["arguments"] if args.foo == 1: ... if args.blah == True: ...当用户输入:
gef➤ mycommand --blah 3 14 159 2653函数内即可使用:
args.foo == [3, 14, 159, 2653] # a List(int) from user input args.bleh == "" # the default value args.blah == True # set to True because user input declared the option (would have been False otherwise)注意源码中一个容易踩坑的细节:对可选参数而言,布尔默认值的语义与"出现即置位"相关——store_true/store_false的选择由默认值决定(默认False→store_true),因此"用户声明了该选项"才表现为True(gef.py#L548-L551)。
为新架构添加支持
为 GEF 添加新架构支持的官方路径是继承Architecture类(基类实现见 gef.py#L2624,社区各架构的参考实现集中在gef-extras仓库的archs/目录)。
更精确的架构匹配可以通过静态方法supports_gdb_arch()完成(基类声明于 gef.py#L2672):它接收唯一参数gdb_str(GDB 报告的目标架构名,str类型),返回值语义为——
True:当前Architecture类支持该目标二进制;False:明确不支持;None:跳过本次判定,让 GEF 继续用默认方式推断架构。
文档给出的典型例子是 ARM Cortex-M——某些目标(如armv7m)应当优先使用 Cortex-M 实现而非通用 ARM 实现:
@staticmethod def supports_gdb_arch(gdb_arch: str) -> Optional[bool]: return bool(re.search("^armv.*-m$", gdb_arch))进一步参考
- 完整 API 参考:GEF 提供的全部类与函数的自动生成文档位于 docs/api/gef.md,配合仓库中的 API 测试(如
tests/api/gef_arch.py、tests/api/gef_memory.py、tests/api/gef_session.py等)可以验证各原语的实际行为与边界条件; - 命令与函数式扩展:除
GenericCommand外,register同样支持注册GenericFunction(需_function_属性与invoke方法,见 gef.py#L5306-L5312),适合封装不面向命令行的公共逻辑,供其他命令复用; - 调试扩展本身:自定义命令抛出的异常可借助 GEF 的
gef.show_last_exception等机制排查,仓库的 docs/deprecated.md 记录了已废弃 API 的迁移建议,写扩展前建议先确认所用 API 未进入弃用清单。
按"骨架 → 注册 → 参数解析 → 上下文面板 → 架构扩展"的顺序实践下来,你就拥有了与 GEF 内置命令同等地位的扩展能力:可source加载、可~/.gdbinit常驻、拥有自动生成的 help 语法与独立的命令级配置项。
- 网络安全
- 开发工具
【免费下载链接】gef
GEF (GDB Enhanced Features) - a modern experience for GDB with advanced debugging capabilities for exploit devs & reverse engineers on Linux
相关推荐
GEF扩展开发指南:打造你的专属调试命令
GEF扩展开发指南:打造你的专属调试命令 前言 GEF作为GDB的增强工具,不仅提供了丰富的调试功能,更重要的是它允许开发者快速创建自定义命令。本文将深入讲解如
网络安全开发工具股票K线预测准确率85%?Kronos开源模型把行情翻译成机器语言,5分钟带你跑通第一个预测
股票K线预测准确率85%?Kronos开源模型把行情翻译成机器语言,5分钟带你跑通第一个预测 凌晨一点,你盯着满屏红绿K线,又一次被自己的判断打脸:说突破,它阴
人工智能大模型基础模型预训练金融科技「生化环材四天王」的由来:张雪峰.skill天坑专业论的完整逻辑
「生化环材四天王」的由来:张雪峰.skill天坑专业论的完整逻辑 张雪峰.skill(zhangxuefeng skill)是一个开源的认知操作系统,把高考志愿
AI 技能交互助手
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考