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) — обнаруженных в проектах с открытым исходным кодом: ядрах, гипервизорах, анклавах, прошивках, и библиотеках. Куда ни посмотри, казалось бы, безопасный код остаётся во власти прихотей компилятора. Но это лишь выборка, а не граница; те же ошибки весьма вероятны и в вашем коде.

Задача

«Начните с чего-нибудь простого.»

Сколько раз эта функция загружает *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]

Проверка выполняется на ЧТЕНИИ №1; значение, которое попадает в `slot.len`, — это ЧТЕНИЕ №2. Атакующий, который изменяет `len` между ними, передаёт проверке `<= 20` безопасное значение, тогда как переразмеренное публикуется в `slot`, — и `forward` затем копирует это количество байтов в `out[20]`, порождая то самое переполнение, которое снимок должен был предотвратить и которое вернул оптимизатор.
Скачать инструмент