Featured image of post Можно ли доверять исполняемым файлам при чистом исходном коде: Атака Trusting Trust Кена Томпсона

Можно ли доверять исполняемым файлам при чистом исходном коде: Атака Trusting Trust Кена Томпсона

Почему аудит открытого исходного кода не спасает от скрытых закладок в компиляторах. Полный разбор лекции Кена Томпсона 1984 года, самовоспроизводящихся программ (Quine), бутстраппинга, разнообразной двойной компиляции (DDC) и воспроизводимых сборок.

Введение: Миф об аудите исходного кода и кризис доверия

В современной разработке программного обеспечения и кибербезопасности господствует непоколебимый догмат: «Если исходный код открыт и тщательно проверен сообществом разработчиков (Code Review), созданный исполняемый файл по определению заслуживает доверия».

Закон Линуса («при достаточном количестве глаз все ошибки лежат на поверхности»), сформулированный Эриком Рэймондом, стал духовным фундаментом Open Source. Мы изучаем коммиты в Git, используем статические анализаторы и свято верим: безупречный исходный код гарантирует создание безопасного бинарного файла.

Однако почти полвека назад один из титанов компьютерной эры, создатель UNIX и соавтор языка C Кен Томпсон (Ken Thompson), разрушил эту иллюзию. В 1984 году при вручении премии Тьюринга он произнес историческую речь: Reflections on Trusting Trust («Размышления о доверии к доверию»).

  flowchart TD
    subgraph TheMyth ["Общепринятая вера (Миф об аудите исходного кода)"]
        SourceAudit["Аудит исходного кода (100% чист)\nВ репозитории нет вредоносных строк!"] --> Compiler["Сборка обычным компилятором"]
        Compiler --> CleanBinary["Скомпилированный файл\n(Считается абсолютно надежным)"]
    end

    subgraph TheReality ["Реальность Томпсона (Атака Trusting Trust)"]
        CleanSource["Чистый исходный код (Идеальный Git)"] --> EvilCompiler["Зараженный бинарник компилятора\n[Хранит невидимого призрака]"]
        EvilCompiler --> PoisonedBinary["Целевая программа: бэкдор внедрен!\n(Пароль суперпользователя открыт)"]
        EvilCompiler --> ReplicatingCompiler["Самовоспроизведение в новый компилятор!\nЗаражение сохраняется навсегда"]
    end

    style TheMyth fill:#eef9ff,stroke:#007acc,stroke-width:2px
    style TheReality fill:#fff0f0,stroke:#d9534f,stroke-width:2px

Томпсон сформулировал пугающий вывод:

«Мораль очевидна: вы не можете доверять коду, который не создали полностью сами. Никакой аудит исходного кода на языке высокого уровня не защитит вас от использования недоверенного кода».

Томпсон продемонстрировал, что внедрил самовоспроизводящегося трояна прямо в исполняемый файл компилятора C для UNIX. Даже когда весь вредоносный код был удален из репозитория, зараженный компилятор при сборке системной утилиты login.c внедрял бэкдор для получения root-доступа, а при компиляции cc.c — копировал механизм трояна в новый исполняемый файл компилятора!


1. Анатомия идеального преступления: лекция 1984 года

1.1 Этап 1: Программы-квайны (Quine)

Фундаментом атаки служит квайн — программа, печатающая собственный исходный текст без использования внешнего ввода. Согласно теореме Клини о рекурсии, разделение программы на исполнительный алгоритм и матрицу данных позволяет коду точно воссоздавать свой генетический чертеж:

1
2
3
/* Классический квайн на C */
char *s = "char *s = %c%s%c; main() { printf(s, 34, s, 34); }";
main() { printf(s, 34, s, 34); }

1.2 Этап 2: Усвоение знаний компилятором (Пример \n)

Изначально компилятор C не знал, что '\n' означает символ перевода строки (ASCII 10). Сначала разработчик написал return 10;. После сборки нового компилятора код был заменен на return '\n';. В этот момент число 10 полностью исчезло из исходного кода, оставшись запечатленным исключительно в памяти бинарного файла.

1.3 Этап 3: Двойной невидимый троян

Томпсон закодировал логику внедрения бэкдора в login.c и логику саморепликации в cc.c. Затем он удалил эти строки из исходного кода. Репозиторий стал кристально чистым, но зараженный компилятор продолжал передавать троян из поколения в поколение.


2. Проблема раскрутки компилятора (Bootstrapping)

Компиляторы современных языков (GCC, Clang, rustc) являются самодостаточными (Self-hosting): они написаны на том же языке, который компилируют. Кто собрал первый компилятор? Цепочка сборки уходит в прошлое на десятилетия — к ассемблерам и перфокартам 1950-х годов. Любая скрытая закладка в этой исторической цепочке способна передаваться десятилетиями, не проявляясь в исходных текстах.


3. Математическое противодействие: Diverse Double-Compiling (DDC)

В 2005 году Дэвид А. Уилер доказал математическую возможность обнаружения и нейтрализации атаки Томпсона с помощью метода разнообразной двойной компиляции (DDC).

  flowchart TD
    subgraph DDCProtocol ["Протокол разнообразной двойной компиляции (DDC)"]
        SourceA["Проверенный исходный код $s_A$"]
        CompA["Подозреваемый компилятор $c_A$"]
        CompB["Независимый доверенный компилятор $c_B$"]

        SourceA --> |Шаг 1: сборка через $c_A$| BinA1["Бинарник $c_{A1}$"]
        SourceA --> |Шаг 2: сборка через $c_B$| BinB1["Бинарник $c_{B1}$"]
        SourceA --> |Шаг 3: пересборка через $c_{B1}$| BinA2["Бинарник $c_{A2}$"]

        BinA1 --> DiffCheck["Побитовое сравнение бинарников (cmp)"]
        BinA2 --> DiffCheck

        DiffCheck --> Result{"Совпадают побитово?"}
        Result -- "Бит-в-бит идентичны" --> Verified["[ДОКАЗАНО] В $c_A$ НЕТ скрытых закладок!"]
        Result -- "Обнаружено расхождение" --> Alert["[ТРЕВОГА] Обнаружена посторонняя модификация!"]
    end

    style DDCProtocol fill:#f8f9fa,stroke:#343a40,stroke-width:2px
    style Verified fill:#d4edda,stroke:#28a745,stroke-width:2px
    style Alert fill:#f8d7da,stroke:#dc3545,stroke-width:2px

Поскольку независимый компилятор $c_B$ не содержит кода атаки Томпсона, он создает чистый компилятор $c_{B1}$. Если $c_{A1}$ и $c_{A2}$ побитово совпадают, это служит математическим доказательством отсутствия недокументированных закладок в $c_A$.


4. Воспроизводимые сборки и 512-байтовое зерно

Проект Reproducible Builds исключает источники недетерминизма: метки времени (SOURCE_DATE_EPOCH), пути к файлам (-ffile-prefix-map), случайный порядок файлов. Инициатива Bootstrappable Builds и дистрибутив GNU Guix доказали, что всю операционную систему можно собрать, начиная с минимального шестнадцатеричного зерна размером всего 512 байт, полностью проверяемого человеком вручную.


5. Заключение: Эпистемология и восстановление доверия

Предупреждение Томпсона — не повод для фатализма, а напоминание о том, что безопасность строится не на слепой вере в исходный код, а на строгих математических и криптографических протоколах верификации цепочки доверия.

Licensed under CC BY-NC-SA 4.0
comments powered by Disqus