☰
GEF 逆向实战:用 pattern 命令基于 De Bruijn 序列定位溢出偏移量
2026/9/25 7:32:49 网站建设 项目流程
  • 网络安全
  • 开发工具

【免费下载链接】gef

GEF (GDB Enhanced Features) - a modern experience for GDB with advanced debugging capabilities for exploit devs & reverse engineers on Linux

项目地址:https://gitcode.com/gh_mirrors/gef/gef
点击查看免费下载

在缓冲区溢出分析中,"被覆盖的寄存器里残留着哪段数据、溢出发生在第几个字节"是必须回答的问题。GEF 的pattern命令通过生成与搜索 De Bruijn(德布鲁恩)循环序列,把"猜偏移量"变成一次可复制的查表操作:先向目标程序灌入一段每个子串都唯一的填充串,崩溃后读取被破坏的寄存器值,再用搜索子命令反查出精确的字节偏移。本文以 pattern 命令文档 为核心,结合 gef.py 中的实现源码与 tests/commands/pattern.py 测试用例,完整讲解pattern create/pattern search的参数、底层算法与端到端使用流程。

读完本文,你将能够:独立生成与 pwntools 完全兼容的 De Bruijn 序列;理解 GEF 如何根据架构字长与字节序自动选择搜索方向;掌握从崩溃寄存器值反推返回地址、栈指针偏移的完整工作流。

1. De Bruijn 循环序列:为什么它能唯一定位偏移

pattern命令生成的字符串由大量互不重复的定长子串组成。以n=4为例,aaaabaaacaaadaaae...中任意连续 4 个字符都是唯一的——因此当这段字符串覆盖了某个缓冲区并在溢出后残留在寄存器中时,只要读出寄存器里的 4 个字符,就能在序列中反查出这 4 个字符起始位置的字节偏移,即溢出点距离缓冲区首地址的距离。

官方文档指出一个关键设计决策:GEF 实现的算法与 pwntools 完全一致,因此两侧工具生成的序列互为兼容,崩溃后即使脱离 GDB 也可以把泄漏值贴进 pwntools 脚本求偏移:

from pwn import * p = cyclic(128, n=8)

1.1 源码实现:与 pwnlib 兼容的递归生成器

生成逻辑位于 gef.py:

def de_bruijn(alphabet: bytes, n: int) -> Generator[int, None, None]: """De Bruijn sequence for alphabet and subsequences of length n (for compat. w/ pwnlib).""" k = len(alphabet) a = [0] * k * n def db(t: int, p: int) -> Generator[int, None, None]: if t > n: if n % p == 0: for j in range(1, p + 1): yield alphabet[a[j]] else: a[t] = a[t - p] yield from db(t + 1, p) for j in range(a[t - p] + 1, k): a[t] = j yield from db(t + 1, t) return db(1, 1) def generate_cyclic_pattern(length: int, cycle: int = 4) -> bytearray: """Create a `length` byte bytearray of a de Bruijn cyclic pattern.""" charset = bytearray(b"abcdefghijklmnopqrstuvwxyz") return bytearray(itertools.islice(de_bruijn(charset, cycle), length))

从源码可以看出几个实现细节:

  • 字母表固定为 26 个小写字母a-z,即charset = b"abcdefghijklmnopqrstuvwxyz",这也是为什么泄漏值通常是aaaab...这类纯字母序列;
  • de_bruijn是一个惰性生成器,按 DAWG(De Bruijn 序列标准构造法)递归产出字符,generate_cyclic_pattern再用itertools.islice截取所需的length字节,因此生成任意长度的序列都不会一次性构造完整超串;
  • docstring 中明确标注 "for compat. w/ pwnlib",印证了与 pwntoolscyclic()的算法级兼容。

2.pattern create:生成并暂存序列

2.1 语法与参数

pattern create [-h] [-n N] [length]
参数含义默认值
length生成的序列总字节数配置项pattern.length,默认1024
-n N唯一子串的定长(周期)当前架构指针宽度gef.arch.ptrsize(x86_64 为 8,32 位为 4)

默认长度来自命令自身注册的配置项——PatternCommand.__init__中self["length"] = (1024, "Default length of a cyclic buffer to generate")(见 gef.py),可通过 GEF 的config机制持久化修改,对应文档见 config 命令。

2.2 运行示例

gef➤ pattern create -n 4 128 [+] Generating a pattern of 128 bytes (n=4) aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaab [+] Saved as '$_gef0'

执行流程对应 PatternCreateCommand.do_invoke:解析length(缺省读gef.config["pattern.length"])与n(缺省读gef.arch.ptrsize)→ 调用generate_cyclic_pattern(length, n)生成字节串 → 打印明文 → 通过gef_convenience(pattern_str)存入一个GDB 便捷变量。

2.3 便捷变量$_gef0:溢出 payload 的"搬运工"

gef_convenience(gef.py)按$_gef0、$_gef1…… 的递增序号命名,字符串值会被定义为形如$_gef0 = "aaaabaaac..."的便捷变量。这个设计让 payload 的传递非常自然:

gef➤ pattern create 256 [+] Generating a pattern of 256 bytes (n=8) ... [+] Saved as '$_gef0' gef➤ set args $_gef0 gef➤ run

在命令行直接引用$_gef0即可把整段循环序列作为目标程序的输入参数,无需手动拷贝长字符串。

3.pattern search:从泄漏值反查偏移量

3.1 语法与参数

pattern search [-h] [-n N] [--max-length MAX_LENGTH] [pattern]

search子命令在 De Bruijn 序列中查找给定pattern,返回其偏移。参数说明:

参数含义默认值
pattern待搜索的值:GDB 符号(如寄存器名)、字符串或十六进制数无(必填)
-n N子串定长,应与pattern长度一致gef.arch.ptrsize
--max-length搜索所覆盖的序列最大长度,即"假设溢出最多发生在前 N 字节内"pattern.length(默认 1024)

pattern search还有一个别名pattern offset(源码中_aliases_ = ["pattern offset"],见 gef.py),语义上更直白地表达"求偏移"。

源码中的参数校验值得注意(PatternSearchCommand.do_invoke):

max_length = args.max_length or gef.config["pattern.length"] n = args.period or gef.arch.ptrsize if n not in (2, 4, 8) or n > gef.arch.ptrsize: err("Incorrect value for period")

即-n只接受 2/4/8,且不能超过当前架构指针宽度——这是为了保证"子串长度 ≥ 泄漏值宽度"时查表结果不产生歧义。

3.2 三种输入形式与官方示例

文档给出的三组示例覆盖了全部输入形式:

gef➤ pattern search 0x6161616161616167 [+] Searching '0x6161616161616167' [+] Found at offset 48 (little-endian search) likely [+] Found at offset 41 (big-endian search) gef➤ pattern search $rbp [+] Searching '$rbp' [+] Found at offset 32 (little-endian search) likely [+] Found at offset 25 (big-endian search) gef➤ pattern search aaaaaaac [+] Searching for 'aaaaaaac' [+] Found at offset 16 (little-endian search) likely [+] Found at offset 9 (big-endian search)

三种形式分别对应:

  1. 十六进制值:寄存器里读出的原始机器码;
  2. GDB 符号:直接传$rbp这类寄存器名,GEF 会先gdb.parse_and_eval取值;
  3. 明文字符串:直接粘贴泄漏的字符。

4. 搜索原理:小端/大端双向查找与 "likely" 标记

PatternSearchCommand.search 的实现揭示了输出中"little-endian / big-endian / likely"三个词的由来:

def search(self, pattern: str, size: int, period: int) -> None: # 1. check if it's a symbol (like "$sp" or "0x1337") symbol = safe_parse_and_eval(pattern) if symbol: addr = int(abs(to_unsigned_long(symbol))) ... mask = (1 << (8 * period)) - 1 addr &= mask pattern_le = addr.to_bytes(period, "little") pattern_be = addr.to_bytes(period, "big") else: # 2. assume it's a plain string pattern_be = gef_pybytes(pattern) pattern_le = gef_pybytes(pattern[::-1]) cyclic_pattern = generate_cyclic_pattern(size, period) off = cyclic_pattern.find(pattern_le) if off >= 0: ok(f"Found at offset {off:d} (little-endian search) " f"{Color.colorify('likely', 'bold red') if gef.arch.endianness == Endianness.LITTLE_ENDIAN else ''}") return off = cyclic_pattern.find(pattern_be) ...

可以归纳出四条机制:

  • 符号优先解析:先用safe_parse_and_eval尝试按 GDB 表达式求值。成功则按数值处理,并用(1 << 8*period) - 1掩码只保留低n字节——因为 8 字节的$rbp值里可能混入非 pattern 的高位(如 0x7ffd... 的栈地址),只有低n字节落在循环序列上;
  • 明文字符串则构造双字节序:把字符串本身当大端字节串、反转后当小端字节串,各自在序列中find;
  • 先小端、后大端:命中哪一端就报告哪一端,并只报告第一个命中的方向,所以输出里同时出现两行(如 48 与 41)是文档示例在不同场景下的组合展示,实际单次运行按小端优先短路返回;
  • "likely" 由架构字节序决定:当前架构是小端时,小端命中行加粗红色标注likely;大端架构则反之(gef.py)。这个提示告诉用户"该结果与目标机器字节序一致,可信度更高",在跨字节序分析(如远程调试)时另一端的偏移同样有用。

搜索所用的序列是按--max-length(默认 1024)与-n现场重新生成的——由于生成器确定性与 pwntools 兼容,无需与pattern create时保存的$_gefN严格一致,但两者参数保持一致才能覆盖真实 payload 长度:如果实际 payload 有 2048 字节,而搜索时max_length仍是默认 1024,超过 1024 的偏移将显示 "not found"。

5. 端到端工作流:以 strcpy 溢出为例

仓库自带的测试二进制 tests/binaries/pattern.c 就是一个标准的栈溢出靶场:

void greetz(char* buf) { char name[8] = {0,}; strcpy(name, buf); // 8 字节缓冲,无边界检查 printf("Hello %s\n", name); }

tests/commands/pattern.py 中的自动化用例完整演示了标准工作流,可直接照搬为手动调试步骤:

gef➤ file tests/binaries/pattern gef➤ pattern create 64 -n 4 [+] Generating a pattern of 64 bytes (n=4) aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaab [+] Saved as '$_gef0' gef➤ set args aaaabaaacaaadaaaeaaafaaagaaahaaa gef➤ run # 在 strcpy 崩溃点断住后查看 $rbp gef➤ pattern search -n 4 $rbp [+] Found at offset 8 (little-endian search) likely

测试用例 test_cmd_pattern_search 在不同架构下验证了相同偏移结论:x86_64 上-n 4搜索$rbp期望得到 offset 8(即 8 字节缓冲name之后恰好是保存的帧指针),-n 8搜索同样期望 8;32 位 i686 因栈布局不同期望 16。用例最后还断言了搜索不存在模式JUNK时输出 "not found" 的分支(tests/commands/pattern.py)。

得到 offset 后的典型利用动作是:偏移 + 4/8 = 覆盖saved rbp后ret地址的起始位置,从而精确控制返回地址。

5.1 与 pwntools 联合使用的注意事项

由于算法兼容,同一泄漏值可以离线复算:

from pwn import * offset = cyclic_find(b"aaaag\x00\x00\x00", n=4) # 注意字节序与 n 必须与 GEF 端一致

需要保持两侧n(周期)一致:GEF 端-n 4对应 pwntools 端n=4,若一侧用 8 另一侧用 4,偏移结果会完全不同。文档示例p = cyclic(128, n=8)即与pattern create -n 8 128一一对应。

6. 小结

命令作用关键默认值
pattern create [-n N] [length]生成 De Bruijn 序列并存入$_gefNlength=1024(pattern.length),n=架构指针宽度
pattern search [-n N] [--max-length L] [pattern]在序列中反查偏移(别名pattern offset)n=架构指针宽度,max-length=1024

GEF 的pattern命令把缓冲区溢出分析中"偏移计算"这一环节封装成了两步操作,且其 De Bruijn 生成器与 pwntools 保持算法级兼容(见 gef.py 的de_bruijn与generate_cyclic_pattern)。配合$_gefN便捷变量传参、寄存器直接作为搜索参数、小/大端双向查找与字节序 "likely" 提示,它构成了从 payload 注入到偏移反查的闭环。理解-n与max-length两个参数如何共同决定搜索空间,是避免 "not found" 假阴性的关键。

  • 网络安全
  • 开发工具

【免费下载链接】gef

GEF (GDB Enhanced Features) - a modern experience for GDB with advanced debugging capabilities for exploit devs & reverse engineers on Linux

项目地址:https://gitcode.com/gh_mirrors/gef/gef
点击查看免费下载
上一篇:【免费下载】 探索Tikhonov正则化与L曲线:MATLAB代码资源推荐【matlab下载】
下一篇:Matrix-Game-3.0开发者指南:自定义动作控制与模型微调教程

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

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

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

立即咨询