Skip to content
KitploitKITPLOIT
工具博客
Log in
提交
工具博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

··订阅源·联系·隐私·© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
schrodingers-toctou — 检测编译器引入的内存加载,它们会将安全的C代码转变为TOCTOU漏洞。包括自动化源码审计、基于Unicorn的二进制分析,以及对100多个项目进行的编译器/架构/编译选项遍历扫描。 | Kitploit
工具/GitHubGitHub/xoreaxeaxeax/schrodingers-toctou
动态分析 (沙盒)静态代码分析 (SAST)漏洞分析漏洞利用二进制分析学习与教育
GitHubxoreaxeaxeax/schrodingers-toctou

schrodingers-toctou

检测编译器引入的内存加载,它们会将安全的C代码转变为TOCTOU漏洞。包括自动化源码审计、基于Unicorn的二进制分析,以及对100多个项目进行的编译器/架构/编译选项遍历扫描。

查看仓库
944281个月前Kitploit 审核通过

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享

Schrödinger's TOCTOU

"...'理智的编译器'的定义正变得越来越宽松。"

你运行的二进制文件并不是你编写的程序。编译器优化器会以你从未见过的方式重写 你的源代码——其中一些改动可以 悄无声息且合法地将看似安全的代码变成 存在漏洞的二进制文件。同一行代码可能 在一个编译器下安全,在另一个编译器下则可被利用, 而源码中没有任何线索告诉你究竟是哪种情况:一个处于叠加态的漏洞, 只有在构建时才会坍缩。Schrödinger's TOCTOU 探讨 **编译器发明的加载操作**及其对 检查时间到使用时间(TOCTOU)漏洞的广泛影响——这些问题 广泛存在于开源 内核, 虚拟机监控器, 安全区, 固件, 和库. 我们所见的每处地方,看似安全的代码都暴露在编译器的 反复无常之下。但这些只是样本,而非边界; 同样的 bug 也极有可能出现在你的代码中。

挑战

从简单的开始。

这个函数会加载多少次 *p?```c unsigned int g(unsigned short *p) { short t = p; / copy *p into a local for safekeeping */ return (unsigned short)t - t; }

提示:答案是 1 — 源代码将 `*p` 单次加载到 `t`。

将其粘贴到 [Compiler Explorer](https://godbolt.org/z/c5K9P4dPd)(`arm gcc 14.2.0`、
`-O2`)并统计从 `r0`(保存 `p`)的加载次数:```asm
g:
        ldrh    r2, [r0]     # load *p, once
        ldrsh   r0, [r0]     # load *p, twice
        subs    r0, r2, r0
        bx      lr

源代码中一次加载,二进制中有两次。第二次是一个虚构加载 — 一种编译器制造出的、你从未写过的读取。它在 C 抽象机下是合法的, 该抽象机假定两次读取之间内存不会改变。但当那块内存可被攻击者 写入时,这一假定就变成了一个漏洞:虚构加载可能落在安全检查 之后,悄悄重新打开一个程序员以为已经关闭的检查时到使用时 (TOCTOU)窗口。 你验证过的值与你在使用的值不再保证相同 — 即使你从未写过重新读取它的代码。

凭空出现的缓冲区溢出

该挑战证明了虚构加载确实存在;让我们看看这如何演变成 内存破坏。

在 TOCTOU 漏洞中,程序先检查某个值是否安全,然后使用该 值。 然而,如果攻击者能在两次读取之间的那一小段间隙内改变 该值,就会存在可利用的窗口 – 无害的值通过了检查, 而被使用的是危险的值:```c if (shared->len <= 20) // CHECK reads shared->len // ** attacker modifies shared->len ** memcpy(out, shared->data, shared->len); // USE reads it again: buffer overflow

教科书式的修复方法是**先做快照**:把攻击者可能篡改的任何数据
复制到攻击者无法触及的本地变量中,然后只信任该本地变量。
一旦 `len` 进入本地变量,它就被冻结了 — 攻击者即使与共享内存竞争
也无法再触碰它 — 因此检查与复制保证能看到相同的值。
这就是下面 `receive` 中的代码修复 TOCTOU 的方式:
它对消息进行快照、验证快照,并将验证后的副本发布到
`slot` 中供消费者转发:```c
#include <string.h>

struct message {
    int  len;          /* payload length */
    char data[20];     /* payload        */
};

struct message slot;   /* the most recently validated message */
char out[20];          /* fixed 20-byte destination           */

void receive(struct message *shared) {
    struct message local = *shared;    /* 1. snapshot untrusted input   */
    if (local.len <= 20)               /* 2. validate the snapshot      */
        slot = local;                  /* 3. publish the validated copy */
}

void forward(void) {                   /* the time of use, later        */
    memcpy(out, slot.data, slot.len);  /* slot.len was checked <= 20 ... right? */
}

从源码来看,这是正确的。len 只被读取一次——进入快照—— 所以清除 <= 20 检查的值就是发布到 slot 的值。 TOCTOU 窗口已关闭,代码是安全的。

但事实并非如此。在 x86-64 gcc -O2 下,receive 会从 原始共享内存中读取两次:一次 作为标量用于门控检查,另一次作为批量 拷贝的一部分,该拷贝被发布到 slot:```nasm receive: cmp DWORD PTR [rdi], 20 ; READ #1: the CHECK reads shared->len directly movdqu xmm0, XMMWORD PTR [rdi] ; READ #2: the bulk copy re-reads it (len is byte 0) mov rax, QWORD PTR [rdi+16] ; (the bulk copy's tail: struct bytes 16-23) jg .L1 ; len > 20? skip the publish mov QWORD PTR slot[rip+16], rax ; (publish that tail) movaps XMMWORD PTR slot[rip], xmm0 ; and publish the TOCTOU-vulnerable snapshot .L1: ret forward: movsx rdx, DWORD PTR slot[rip] ; copy size = slot.len, the unchecked READ #2 value mov esi, OFFSET FLAT:slot+4 ; src = slot.data mov edi, OFFSET FLAT:out ; dst = out[20] jmp memcpy ; copies slot.len bytes into out[20]

检查运行于 READ #1;落入 `slot.len` 的值是 READ #2。攻击者若在两者之间翻转 `len`,就能让 `<= 20` 检查看到一个安全值,同时一个超大的值被发布到 `slot` 中——`forward` 随后将那么多字节复制进 `out[20]`,这正是快照本应防止的溢出,却被优化器重新引入。

在 [`poc/example.c`](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/poc/example.c) 中,这被转化为一个完整的概念验证,代码采用了经典的 TOCTOU 加固方式:不可信的 `message` 结构体被快照到 `local` 中,从而无法被修改;快照的 `local.len` 根据缓冲区容量进行校验;只有经过校验的副本才会被发布到 `slot`;之后消费者将 `slot.len` 个载荷字节复制到固定缓冲区。与此同时,攻击者与 `shared->len` 进行竞争。编译器一次意料之外的发明加载(invented load)在批量发布时重新读取了 `shared->len`,因此即使检查通过了,`slot.len` 携带的仍是攻击者的超大值——程序员试图防御的 TOCTOU 被重新引入,凭空制造出一个看似不可能的缓冲区溢出。

## Cause

> *当 C 代码到达机器码阶段时,它已经经历了前端降级(frontend lowering)、IR 优化、寄存器分配和后端代码生成的重塑——这是一个深层的多级流水线,在你看不见的地方做着决策。不能归咎于任何单一阶段。发明加载是整个流水线的一种涌现属性,而非其中任何一部分的缺陷。*

目前的情况是:编译器*确实可能*发出发明加载,而本意用于防止该缺陷的习惯用法——快照、校验、使用——恰恰就是重新引入它的原因。下一步(要弄清我们是否真的易受攻击)是刻画它究竟在*何时*发生。事实证明这很困难。

在 [`cat-states/`](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/cat-states) 中,我们寻找能够证明它是真实存在的概念验证——而且它无处不在:

| Mechanism | Toolchains | Targets |
|---|---|---|
| [**Rematerialization**](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/cat-states/README.md#rematerialization-class-1) | GCC, Clang, ICX, ICC, MSVC | x86-64, i386, m68k, VAX, MSP430 |
| [**Width-mismatch reload**](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/cat-states/README.md#width-mismatch-reload-class-2) | GCC | ARM, MIPS, MIPS64, RV64, s390x |
| [**Bulk-vs-scalar overlap**](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/cat-states/README.md#bulk-vs-scalar-overlap-class-3) | GCC, Clang, ICX, MSVC | x86-64, ARM, AArch64, AVR, Xtensa, SPARC, PPC64, s390x, MIPS64, RV64, m68k, MSP430, VAX, HPPA |
| [**Cross-class reload**](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/cat-states/README.md#cross-class-reload-class-4) | GCC | x86-64, s390x |
| [**CISC mem-op fold**](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/cat-states/README.md#cisc-alu-mem-op-fold-class-7) | GCC, Clang | m68k, MSP430, s390x, VAX, 6502 |
| [**Byte-order reload**](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/cat-states/README.md#byte-order-divergent-reload-class-8) | GCC | s390x |

上面的每个 PoC 都锁定了一个加载*可能*出现的具体位置;[`alpha-lab/`](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/alpha-lab) 围绕它绘制空间图,以找出边界落在何处——这是一个由单个 `.c` 文件驱动的三阶段流水线。[矩阵运行器](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/alpha-lab/matrix_runner.py)在 [Compiler Explorer](https://godbolt.org) 上扫描编译器 × 架构 × 标志位矩阵;[加载检测器](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/alpha-lab/detect.py)在 [Unicorn](https://www.unicorn-engine.org/) 下运行每个生成的二进制文件,并捕获任何被读取两次的字节;[标志位最小化器](https://github.com/xoreaxeaxeax/schrodingers-toctou/blob/main/alpha-lab/flag_search.py)对每个命中进行增量调试,将其缩减为能将安全构建翻转成双重读取 TOCTOU 的最小标志位集合。
下载工具