如何对 setjmp/longjmp 序列进行逆向工程?

逆向工程 拆卸 部件 x86 记忆 操作系统
2021-06-30 03:21:27

我正在尝试对 GNU libc x86(32 位)setjmp / longjmp(一个漏洞,它可能允许任意覆盖jmp_buf env.

一篇关于 musl setjmp 的精彩文章,但我在网上几乎找不到关于 GNU 的任何内容。我试图浏览源代码,但它是一个宏的意大利面条球,可能是因为如此依赖系统。asm 是不寻常的,使用以下内容:

CALL       dword ptr GS :[0x10 ]

我不完全理解(我认为段用于 16 位 8088 代码!是什么GS:?)。

先验地,我希望 setjmp 会简单地保存一些寄存器,但它似乎要复杂得多。我发现一些帖子声称 GNU 故意混淆它,要么是为了防止程序员依赖内部结构,要么是出于某些安全目的,我对这两者都持怀疑态度。

使用调试器进行试验表明一件事:jmp_buf env每次调用变化,例如即使是相同的程序,具有相同的参数和相同的堆栈指针,如果您使用调试器将jmp_buf一个调用加载到另一个调用中得到一个SIGV。内容显然不是程序和堆栈的纯函数,而是以某种方式(随机?)随着每次调用而改变。

这里的任何裂纹 RE 是否能够穿透setjmp

1个回答

我将从回答一些基本问题开始,其中一些您甚至没有问过!

段寄存器在现代代码中做什么?

我们需要额外的寄存器来寻址内存区域已经有一段时间了。32 位,尤其是 64 位,已经绰绰有余。操作系统开发人员利用了那些未使用的寄存器,现在大多数现代操作系统至少使用一些寄存器来保存与操作系统相关的数据。正如评论中提到的,在 amd64 处理器上,段寄存器不能用于分段,但操作系统也在 32 位处理器上这样做。

您可以在此处阅读有关 linux 的更多信息此处此处有关 windows 等。

为什么我不能从以前执行的程序中恢复数据

尽管您可以控制程序执行的一些变量(参数、堆栈地址、进程加载地址和堆位置),但您仍然无法控制所有变量(特定分配的位置、从“外部”源(例如内核和作为)返回的值我们很快就会看到,反利用缓解措施也可能会干扰这类事情)。

一般来说,如果不进行必要的调整,你永远不应该期望这样的事情会起作用。更不用说像 setjmp/longjmp 这样低级和微妙的东西了。

为什么没有记录 setjmp/longjmp 实现?

首先,我们在逆向工程社区,避免文档并不能保证机密性。其次,文档在代码中:)

我想文档对于这种可能经常更改并且非常特定于体系结构的低级细节来说是相当困难的这就引出了你的下一个问题——

为什么依赖于 setump/longjmp 架构?

显然,这是不言而喻的,但为了完整起见,我认为最好在这里明确。以下是必须在每个架构级别上完成的一些原因:

  1. 由于这些函数涉及 CPU 的某些 ABI(特别是调用约定),因此代码必须遵循不同的约定。
  2. 为特定目的按名称访问寄存器是在 C 中抽象出来的。
  3. C 是一种过程语言,因此其核心的 setjmp/longjmp 与 C 的本质直接矛盾,因为它打破了过程(函数)的界限。
  4. 其他特定于体系结构的功能(实现方式不同,影子堆栈指针保护就是这样的例子)可能会改变 setjmp/longjmp 处理特定情况的方式。

从现在开始我将讨论 amd64。

setjump 是如何实现的

现在,虽然这不是 C(也不是最易读的程序集),但 amd64 上的 setjmp 代码可以在setjmp.S 中找到,longjmp 是__longjmp.S它甚至得到了很好的评论,代码也很简单!

您可以清楚地看到保存在结构中的寄存器(例如,movq %r12, (JB_R12*8)(%rdi))。PTR_MANGLE如果启用了 aformentinoed 指针保护功能,您可以看到被调用。

因为您的问题主要围绕查找代码而不是阅读代码,而且由于代码非常简单,所以我现在将阅读函数作为练习留给读者。稍后我会回来添加更多详细信息,因此请随时提出后续问题。

jmp_buf结构是如何定义的

由于我们正在处理组装,因此我们没有结构。相反,有几个#define预处理器指令来定义jmp_buf结构。这些位于专用标题中:jmpbuf-offsets.h

这些文件在哪里?我在哪里可以找到不同的架构实现?

这些文件位于sysdep模块中,该模块包含每个受支持的特定于体系结构的组件的子目录。aarch64代表 arm 64 位,x86代表 32 位英特尔 8086 兼容处理器,86_64代表 64 位英特尔 8086 CPU,等等。