Работа «Coding Machines» опубликованная Lawrence Kesteloot в 2009 г. является полностью вымышленной. Для тех, кому тяжело вникать, даю спойлер-подсказку: Это история фантазирует о том, как страхи из другой работы «Размышления о том, можно ли полагаться на доверие» Кен Томпсона могут влиять на реальный мир. Т.е. о том, что может произойти, если компилятор, компилирующий собственные релизы, сойдет с рельс.
Переведено вручную. Приветствуются поправки.
Патрик через минуту вернулся с небольшой пыльной коробкой. Мы с Дейвом смотрели, как Патрик ее открывает и достает сетевой свитч — такой староватый из тех времен, когда им еще делали железные корпуса. Он воткнул блок питания в розетку и аккуратно выпрямил шнур CAT-5, чтобы подключить этот свитч к нашей сети. Я хотел наорать на него за всю излишнюю осторожность в такой момент. Дейв сидел рядом со мной, нехарактерно тихо.
Я замер, пока у Патрика не получалось попасть шнуром в нужный порт. Я глядел на передние огоньки — Дейв, наверное, тоже. Мои глаза намокли. Патрик впихнул шнур. Сразу же огоньки загорелись и быстро замигали. Я почувствовал, как мои руки и лецо покраснели, а в углу глаза увидел как Дейв встал и открыл рот, будто пытаясь что-то сказать. Затем он нырнул лицом в сложенные руки, затем его вырвало.
Три недели назад мы бы так не реагировали. Три недели назад мы перетекали в тот тяжелый этап проекта, когда заканчивается веселье и тебе приходится реализовывать муторную часть продукта — то, что казалось простыми прямоугольниками в набросках, казалось легким в реализации, но стало замудренным по нелепым причинам, и как следствие неинтересным.
Патрик и Дейв все спроектировали, естественно, и ржали надо мной, когда я выдвигал различные идеи, которые в ретроспективе должны были быть очевидными. Было очень увлекательно слушать их споры о проектировании. Множество аргументов было основано на их интуиции, а не на обдуманной логике. За неделю с ними я познавал больше, чем за весь последний семестр в институте.
Я больше импонировал Дейву. Дейв Митчелл хорошо смеялся. Он был толстоват и часто ронял вещи. Мне нравились его доводы по поводу руководства и процесса разработки ПО, а он любил учить меня чему-то. Патрик и Дейв разделили свой проект пополам, а мне поручили писать тесты перед тем, как напишут сам код.
Одним тихим днем я услышал, как Дейв под себя шепнул «что за бред» и бросил руки в волосы. Я предчувствовал возможность чему-то научиться, и подошел к его столу. Он не заметил. Он быстро переключался в редактор, вставлял строчку отладочного вывода, компилировал, мотал головой и возвращался добавить еще одну строчку где-то.
— В чем дело?
Перед тем как ответить, он подождал пока программа завершится. — Я не понимаю этот баг. Я не понимаю откуда вылезает вот это число.
Он показал на строку в выводе. Дейв и Патрик предпочитали отлаживать выводя что-то в консоль, а не с помощью отладчика, как любил я.
— А что будет, если пройти здесь с отладчиком? — Это был стеб. Дейв в отладчике максимум мог отобразить стектрейс из дампа памяти.
Дейв замолчал, затем открыл свою программу в отладчике. Я в удивлении улыбнулся, а затем показал ему как ставить бреикпоинты. Мы еще несколько часов искали, где именно в этой каше из его кода, Буста и системных библиотек, образовывалось это значение.
— Патрик, можешь помочь? — Это меня удивило еще больше, чем дебаггер. Дейв почти никогда не просил помощи у Патрика.
Патрик подошел и Дейв объяснил ему всю ситуацию.
— Программа вылетает, потому что здесь неверный дереференс, но значение вот тут правильное. — Дейв показывал на какой-то код, пока Патрик глазами сканировал экран. Затем у меня появилась мысль. Дейв еще не закончил объяснять, а я не хотел перебивать. Мое сердце сильно билось, пока я ждал когда Дейву придется вдохнуть.
— Я думаю, что это ошибка компилятора, — уверенно сказал я, прищуриваясь на коде. Патрик и Дейв оба спешили сказать, что я не прав.
— Винить компилятор — как последняя надежда, — сказал Патрик, — также как и стд. либу. Шанс намного более велик, что ошибка в твоем свеженьком коде, а не в коде которым пользуются тысячи людей. — Я мудро кивал, но чувствовал, как мои щеки покраснели.
Патрик Карлссон часто меня обижал. Я уверен, что он не хотел, но он так прямо вел споры, что я чувстовал себя бездумно и молодо. Он был выше меня и Дейва, и стройным. Он был тихим всегда, когда не поправлял что-то что я сказал или код который я написал, но он мне все равно нравился. Иногда я думал, что его решения при проектировании были странные и наивные. Мы с Дейвом любили на него вместе нападать.
Патрик хрустнул костями. Он задавал вопросы о потоках и волатильном алиасинге — вопросы которые я не до конца понимал, но желал, что подумал бы о них сам. Дейв рассматривал каждый, и сказал, что ни одно из волнений Патрика не подходило к этому случаю. Патрик зажал губы вправо, так, как он делал когда сомневался в верности даных ему ответов, и сказал, — хммм… Не знаю. Странно, — и вернулся к своему столу.
Я был облегчен, когда Патрик не раскрушил нас каким-то очевидным решением, и подумал, что Дейв тоже так почувствовал, когда выпрямился из своего сутула и стал печатать.
— А можно как-то показать какой вот тут Ассемблер — где все уходит не туда?
Я показал ему, как это делается и подумал, что он может быть пробует мою гипотезу. Он ввел нужную команду — строки нашего Си были разделены десятками строками на Ассемблере.
— Что-то не так, — подметил Дейв, — это не та команда.
— Это точно та самая, — сказал я. Я хорошо знал дебаггер, и под «нами» он подразумевал меня.
— Да никак нет, тут слишком много Ассемблера на одну строчку кода. А здесь вообще ненастоящие инструкции.
— Такого не бывает, — сказал Патрик за своим столом.
Дейв медленно моргнул, — точнее я никогда их еще не видел... такое не выдает компилятор… да мы не там смотрим… — Он взволновался, а я чувствовал себя самым неопытным человеком в мире. Дейв встал и сказал, — я перегорел — давайте продолжим завтра.
Он ушел и я сел за его компьютер. За весь этот день я не помог ничем, кроме знания отладчика, а Дейв не думал, что я даже в нем разбирался. Я посмотрел на Ассемблер на его экране. Какие-то инструкции были очевидные, а другие мистикой. В интернете я нашел референс по Ассемблеру и стал ходить по коду, записывая в тетради значения всех регистров.
Дейв был прав — эти инструкции были полной белибердой. Они не только выполняли слишком много работы для соответствующего кода на Си. Они вообще не имели никакого смысла сами собой. Но я был убежден, что это как минимум нужное место, потому что несколько инструкций действительно соотносились с кодом на Си.
Я зафиксировался на одной определенной инструкции. Она вычитала регистр от самого себя. Само по себе, это не было ничем необычным — это могло быть коротким способом обнулить регистр. Но этот регистр потом задействовался в других математических инструкциях. Компилятор должен был знать, что в регистре будет ноль и убрать их оптимизациями. Я позвал Патрика. Я объяснил то, что увидел. Он хорошенько посмотрел на это все, и сказал, — это не просто инструкция вычитания.
Он был прав. Я дотошно перечитал референс, и понял что это было вариацией, которая также вычитала бит для переноса. Это было способом взять из регистра переносный бит. Я пошел обратно по коду, чтобы найти где этот перенос задается. Код по нарастающей становился все сложнее, и я постоянно делал неверные допущения, которые сбивали меня с пути.
Я повернулся, чтобы задать Патрику вопрос, но увидел пустой стол. На часах была полночь. Я поставил сигнализацию и поехал на велосипеде домой.
Утром я опоздал. Я не выспался, потому что несколько часов не мог уснуть. Каждый раз закрывая глаза, я представлял быстро пролетающие инструкции Ассемблера большими яркими буквами.
Я подошел к Дейву, чтобы обсудить вчерашнее расследование, но ему не было интересно.
— Ты был прав, — сказал он, — это баг в компиляторе. Я переделал код и он больше не провоцируется. Тот странный Ассемблер пропал.
Я находился в неловком положении, где мне пришлось возражать его комплименту. — Я не думал, что это был баг. Баг бы не сгенерировал такой код, который я видел.
— Это безусловно был баг. — Я достаточно долго проработал с Дейвом, чтобы знать, что чем увереннее он говорил, тем больше он сомневался. Но ему надоело простаивать из-за этой проблемы, поэтому я отстал.
Дейв подошел к моему столу с улыбкой на лице и чашкой кофе от Пита. Мне подходило вообще любое кофе, но он был привередливым, поэтому я тоже становился привередливым, чтобы говорить с ним по дороге к Питу. Меня немного обидело, когда он сходил туда без меня.
— Помнишь тот баг в компиляторе во вторник? — Он сильнее улыбнулся. Его зубы пожелтели от эспрессо.
— Ага.
— Я снова на него наткнулся. Тот же файл, та же проблема. Тот Ассемблер снова вернулся. А страннее всего — то, что я даже не менял этот файл. — Ему было удивительно весело это говорить.
— Может быть ты поменял какой-то хедер? — Я гений.
— А, наверное, — он нахмурился, — не, погоди. Дай я проверю SVN.
Он вернулся через две минуты. Никакие релевентные файлы не поменялись.
Подошел Патрик. — Это плохо. Здесь это краш, но могло быть что-то похитрее. Через четыре месяца наша система будет такая сложная, что мы будем выискивать такие проблемы неделями. Можешь прочитать release notes и решить, можем ли мы обновить компилятор?
Я быстро глянул и нашел то, что и требовалось доказать. Мы были на самой последней и стабильной версии рекомендуемого мажорного релиза. Стабильнее уже некуда. Я пошел в баг трекер искать пермутации слов «странный Ассемблер» и «баг в компиляторе», но ничего не нашел.
Я скомпилировал код Дейва на своем компьютере и прогнал через дизассемблер. Наш странный код там был — там же, где он был во вторник. Я узнал ту инструкцию вычитания-с-переносом и пару других которые выглядели странно. Также я прочитал весь остальной код. Поразительно, что эти инструкции нигде больше не появлялись. Весь остальной код пользовался типичными инструкциями. Вообще когда кому-либо надо было вычесть два числа и их существующие биты переноса?
Я скачал исходники компилятора. Я никогда еще не был так переполнен. Это было кучей компиляторных пассов, фреймворков для плагинов, встроенных языков описывающих архитектуры процессоров, и слоев абстракции. Я сразу полез в файлы, которые переводили синтаксическое дерево в Ассемблер. Я grep’нул вычитание-с-переносом. Его там не было. Я поискал другие. Какие-то были, но большинство отсутствовало.
Во время обеда я поделился с народом своими находками. У нашего небольшого офиса было свое патио, которое здесь в Маунтан Вью почти всегда было по погоде. Пятница была днем Того. Мне больше нравился Сабвей, но Дейв ненавидел их хлеб.
— Просто суперски странно, — сказал Патрик, ухмурившись в стол. Он часто говорил «суперски». Наверное так говорят в Калифорнии. — Каких еще инструкций не было в файле с трансляцией?
— Не помню, — я ему ответил, — еще несколько инструкций с битом переноса. Какие-то векторные инструкции, заодно.
Патрик поднял голову. — Этот компилятор не умеет вектозировать.
— Именно так.
— Либо инструкция сгенерировалась случайно, либо ты дизассемблишь не код. — Он еще был нахмуренный.
Дейв ворвался. — Это точно код. Он вызывает наш баг. Он исполняется.
— И он не генерируется случайно, — добавил я, — эта инструкация математическая, но используется тут не для математики. Это не бред.
— Интересно. — Мы наконец-то втянули Патрика. После обеда мы с ним сели за мой стол и прошлись по тому коду, который я лучше всего знал. Я показал ему, что несмотря на то, что эти инструкции редкие и странно используются, они все имеют смысл вместе. Тут был умышленный поток данных.
— Давай поищем инструкцию обратного перехода, — он попросил.
— А зачем?
— Назначение этого перехода может быть верхушкой цикла. Это будет хорошей точкой для начала анализа.
— Ага. — Он улыбнулся. Его глаза загорелись.
Весь оставшийся вечер мы ходили по этим замудренным переходам и декодировали следующие четыре инструкций. Оказывается, этот отрезок кода находил знак целого числа. Любой человек мог бы написать простое сравнение и переход, чтобы положить -1, 0 либо 1 в выходной регистр, но эти четыре инструкции были кучей кода который либо задавал бит переноса побочно, либо использовал его нестандартно.
— Знаешь, — сказал Патрик, — это даже не самое интересное. Я хочу знать как этот код сюда попал. Ты сказал этих инструкций не было в файле трансляции?
— Верно.
— Они где-то точно есть. Давай grep’нем и команду и оп-код исходниках.
Я сделал рекурсивный grep и ничего не нашел. Я не знал что делать дальше.
— Попробуй сам бинарник, — сказал Патрик.
— Какой бинарник?
— Компилятора.
Я grep’нул название команды — снова ничего. Но оп-код выдало сотни раз.
— Интересно, — сказал я, — они ничего не генерируют — они вставляют целые куски кода.
— Почему?
— Я не знаю
— Не, а почему ты так говоришь?
— Потому что все эти оп-коды лежат вместе. Это не таблица.
— Может быть это исполняемый код, — он сказал застыв на секунду.
Я засунул весь компилятор в дизассемблер и поискал остальные инструкции и нашел большие отрезки кода которые выглядяли точно так же как то, что мы разбирали весь день.
— Так это компилятор заражен! — сказал я, — вот почему мы не могли найти это в его исходниках.
— Окей, пересобери компилятор, — сказал Патрик, — я в интернете поищу что это такое. Когда соберешь компилятор, пересобери весь наш код и посмотри пропадет ли баг Дейва.
Компилятор компилировался два часа, не считая те часы, которые я потратил, чтобы понять их процесс сборки. Тем временем Патрик ничего не нашел. Затем я пересобрал наши исходники и запустил тесты. Они не прошли, на том же самом месте.
— Может быть баг все-таки был из-за чего-то еще? — я возразил когда Патрик подошел ко мне.
— Закинь этот компилятор в дизассемблер, — он сказал
Те оп-коды все еще там находились.
— Да не может такого быть, — я сказал, — это свежий билд официальных исходников.
— Тогда они тоже зараженные. Возможно кто-то взломал сайт для скачек и заменил их на другие исходники.
— Я бы очень хотел посмотреть на этот код, — я сказал и открыл в редакторе какой-то файл из компилятора.
— Это займет вечность, — сказал Патрик, — поставь бреикпоинт на write()
, с условием на строки которые содержат этот оп-код.
Слышу от человека, который делал вид, что не любит отладчики! Тяжело было поставить условие, и у нас было много ложных находок, но на следующий понедельник я поймал тот write()
, который вставлял подозрительный код. Я запрыгнул в ответственный код на Си. Там была обычная рутина для вывода в буфер. Я полез обратно по коду, чтобы понять откуда он заполняется, и находил только простые циклы основанные на трансляционной таблице, которая точно не была заражена.
Отчаявшись, я пошел по всем файлам в компиляторе в поисках кода, который мог за это отвечать. Большая часть манипулировала синтаксическим деревом. До меня дошло, что там хака точно нет, потому что трансляционная таблица на бекенде была чистая. Хак должен был находиться в самом бекенде. Точнее, он был бы где-то в установке регистров. Зная это, поиски сузились значительно, и я потратил час пролистывая эти файлы пока не наступил обед.
Понедельник был день супа фо. Мы всегда ходили в «Pho World», где было так дешево, что у них даже не было меню. Зато это было вкусным способом потратить четыре доллара.
— Eye of round, no tripe, no cilantro — Сказал Дейв маленькому мужчине, и мы все взяли то же самое. Я не знаю что такое «eye of round», но это точно было безопасным для нас выбором.
— В общем я не смог его найти, — сказал я, — ни выше от бреикпоинта, ни ниже по коду.
— Ты все еще это дебажишь? — спросил Дейв.
— Мы обязаны, — ответил Патрик, — мы не можем построить продукт с таким фундаментом.
— Просто так странно, что исходники чистые, а в Ассемблере все эти оп-коды, — сказал я.
— Я думал, что ты объявил это проблемой компилятора, — сказал Дейв.
— Да это не в вашем коде, а в коде самого компилятора.
— Компилятор тоже может за это отвечать.
— Не может, — я весь код перечитал.
— Но компилятор скомпилировал другой компилятор.
Я не понял к чему вел Дейв, но Патрик поднял голову и, кажется, был на грани эврики. Я ждал понятливого объяснения.
— То есть компилятор по неизвестным причинам распознает и изменяет твою программу, но также распознает и изменяет самого себя когда его компилируют.
— Ага, — сказал Дейв с улыбкой, выливая коричневый соус в свой суп и брызгая по столу.
— И как это должно работать? — спросил Патрик, теперь весь в деле. Пошел ответ. — Какой-то хакер засовывает этот код в компилятор и всем его раздает. Этот код понимает, что он компилирует компилятор и самого себя сует в бинарник. В следующей ревизии хакер убирает этот код из официального билда. Затем хак бесконечно самого себя распространяет, без каких-либо следов источника.
— И зачем все это? — спросил я. Я скептично относился к тому, что код мог узнавать и добавлять себя так безошибочно в нескольких версиях.
Я хотел, чтобы он не говорил, что такие вещи очевидные.
— Тогда давайте вернемся на старую версию компилятора, — сказал Дейв.
— То есть билд старой версии. Его исходники нам не помогут. Я сомневаюсь, что у нас есть старые билды. Мы не знаем, как глубоко это зарыто.
— Давай так, — сказал я, пытаясь чем-то пригодиться, — я напишу утилиту, которой можно скормить бинарник, и она распознает подозрительное использование этих оп-кодов.
— А, хорошая идея. — Мне нравился Патрик.
Эту утилиту я писал не долго. Она просто прогоняла бинарник в дизассемблере, а с помощью нескольких grep’ов находила инструкции, которые точно не были сгенерированы компилятором. Она нашла тот код из программы Дейва и из компилятора. Я дал ей пройтись по всем программам на моей системе.
— Вот целый список, — я сказал подходя к столу Патрика. Три страницы в распечатке. Он осмотрел его.
— Это не к добру. Рантайм Джавы, рантайм Питона, Перл, наш компилятор и еще куча других не особо важных програм.
— А почему нас интересуют эти рантаймы?
— Потому что если ты не доверяешь компилятору Си, то нам придется написать свой, и это не так сложно, но на каком языке мы его должны написать? Ты будешь доверять зараженному интерпретатору Питона со своим новым компилятором?
— Ты не будешь писать новый компилятор, — крикнул Дейв со своего стола, — не становись параноиком — это наверное просто баг.
Я отошел от Патрика смотря в свой список. У меня все еще не было ответа не вопрос, который я задал в обед: «зачем нужен этот хак?». Мне понравилось реверс-инженирить эти отрезки кода, и я честно боялся, что Патрик заставит меня написать компилятор на Ассемблере. Так что я надел наушники и открыл отладчик в той части компилятора, которая выглядела обфусцированно.
Тут тоже было насрано инструкциями с битом переноса, странными векторными инструкциями и запутанными (а иногда бесполезными) переходами. Это бы не сгенерировал никакой компилятор. Кто-то своими руками это написал, чтобы запутать читателя. Я собрался понять в чем был смысл этой страницы кода.
Через некоторое время наш охранник подошел вынести мой мусор. Я снял наушники. Дейва и Патрика не было. На часах было десять. Я обернулся к экрану со знакомыми мне инструкциями. Я смог собрать представление о том, что делал код, или хотя бы что делали некоторые его части. Я почувствовал прилив сил. Ответ был близко. Я остался до утра.
Патрик сидел рядом, пока я тыкал в экран. — Вот тут векторную инструкцию используют, чтобы посчитать сумму квадратов, что на самом деле — усложненный способ сравнить два массива байтов. — Это было развязкой десятиминутного разговора.
— Стоп, а что они в итоге делают? — он спросил.
— Это сравнение шаблонов.
— И зачем весь этот запутанный бред?
— Это нечеткий поиск, и видимо, он довольно быстрый.
— Да, но я никогда еще не видел более усложненного Голдберг-подобного способа что-то реализовать.
Мои плечи опустились, и я стал водить мышкой. Я проверил свою кучу заметок. Он не был даже отдаленно впечатлен.
— Что теперь? — я спросил после нескольких секунд тишины.
— Не знаю, дай я проверю почту. — Он ушел к своему столу. Я сдулся, а мои мышцы болели. Все оставшееся утро я читал блоги и обновлял страницу мероприятия MacWorld.
В обед Патрик пересказал Дейву мои находки. Он запомнил все детали. Дейв лыбился и мотал головой с каждым усложнением этого алгоритма. Я не думал, что он сможет так быстро в это вникнуть не поглядывая на сам код. Переслушивая это все, я понял что Патрик был прав. Это решение было слишком запутанно для такой простой задачи.
— А ты когда-то видел турниры по обфуцированному программированию? — спросил Дейв, когда Патрик почти закончил, — это то же самое.
— В институте я с другом соревновался, кто лучше обфусцирует программу, — сказал Патрик, — не знаю как он тогда к этому подошел, но я начал написав свою программу нормально, затем постепенно усложняя ее, переименовывая переменные, подменяя выражения и все такое. Но такой процесс бы никогда не привел к тому, что я увидел утром.
— Может кто-то делает это по-другому, — сказал Дейв.
— У меня странные ощущения от этого кода, — сказал Патрик, — я не знаю, как это объяснить. Он какой-то холодный.
Я и Дейв посмотрели на Патрика. Он растворял васаби в соевом соусе. Я не стал прерывать его собирание с мыслями.
— Это как в тех решалках для шахмат, — наконец он сказал, — у них нет никакой интуиции о ходах, или чувств о состоянии доски, или полезного опыта. Они просто пробуют все, что можно и выбирают самое лучшее. Это выглядит так же. Будто кто-то перепробовал все комбинации инструкций, пока одна наконец-то не делала то, что просят. Нет никакой красоты. Этот код уродливо функциональный. Наверное это для меня и значит «холод».
— А как оно знает, что от него просят? — я спросил.
— Ну… — ответил Патрик, — может кто-то определил все крайние случаи и запускал этот код, чтобы убедиться, что вывод правильный. Код, рабочий в крайних случаях, скорее всего сработает во всех случаях.
— О, а так можно? — спросил Дейв, улыбаясь, — я бы очень хотел просто сказать компилятору нужный результат, чтобы тот сам придумал за меня код.
— Нуу… Указать исход твоего кода достаточно точно ничем не отличается от написания самого кода, — ответил Патрик, и я почувствовал как моя улыбка затухла вместе с Дейвом. Я задумался, был бы угашенный Патрик чуть веселее. Или хотя бы не таким душнилой.
— Тогда зачем кому-то этим заниматься? — спросил я.
Патрик положил ролл себе в рот и уставился в середину нашего круглого стола — может этим никто и не занимается. Может быть это просто наши компьютеры.
Я не знал как на это ответить. Я не думал, что он шутит.
— Короче, есть такая штука — наазывается Бритва Оккама… — начал Дейв.
— А какое у тебя объяснение?
— Эм, ну точно не роботы с осознанным искусственным интеллектом пытающаяся поразить мой код для перекладывания объектов.
— Это не объяснение.
— А ты запускал антивирус? — дейв говорил “анти́вирус”, как будто он произносил имя римского императора.
— А это влияет на твое объяснение того, почему этот код так выглядит? — Патрик иногда был слегка жестоким.
Дейв сделал выдох и серьезно подошел к вопросу. Аргумент Патрика было тяжело отрицать. Другие версии ничего не объясняли. Мы никогда еще не видели такой код от компилятора, а человек, пишущий на Ассемблере руками не смог бы написать самореферентного, вставляющего оп-коды червя с расчетом на будущее даже без этих мистических инструкций. Такие инструкции вообще должны привлекать глаза к коду. Это упростило написание моего скрипта.
— Ладно, а какое у тебя объяснение? — наконец спросил Дейв.
— Не знаю, — сказал Патрик нахмурившись, — может какая-то программа искусственного интеллекта слетела с катушек. Или знаете, как вирусы прячутся от сканеров меняя собственный код? Может это пошло так — какой-то вирус был запрограммирован переписывать самого себя сохраняя поведение.
Мы затихли на пару минут. Я пытался понять, имело ли смысл его объяснение. Звучало маловероятно, но хватило бы всего одной экземпляра такой программы в таком направлении, чтобы она стала разростаться безостановочно. Мы находим полмиллиона новых вирусов в год. Наши мозги не обладают интуицией на такие числа.
Ко мне пришла мысль. — Погодите, это ведь началось не у нас. Мы получили бинарник с официального сайта. Кто-то точно встречался с этим до нас. Я не верю, что компилятор вставляет полурабочий код в билды и мы первые это заметили.
— О, хорошая идея, — сказал Патрик, — я после обеда напишу об этом.
Через час ко мне пришло письмо от Патрика с несколькими ссылками на форумы, где он расписал наши находки и спрашивал, встречался ли кто-то с этим. Дейв добавил ссылку на Ask Slashdot, где он попросил добрых слэшдоттеров объяснить что тут происходит, чтобы его коллега не заставил его писать компилятор на Ассемблере.
— Красава, Дейв, — сказал Патрик, и я не мог догадаться, шутит ли он или нет. Дейв выдал вялое «спасибо», от которого я понял, что он тоже неуверен.
Вторую половину дня я провел изучая больше странного кода и проверяя посты на форуме. В некоторых постах нас просто проигнорировали, а в других высмеяли. Стеб Слэшдота был невыносим. Все комментарии были «+5 funny». Мне не было смешно. Я почесал свой колючий подбородок и вспомнил, что совсем не спал. Я в трансе уехал домой и сразу уснул.
На следующее утро я встретил Патрика, глядящего на распечатанный мной зараженный код. Я подошел к его столу и встал рядом.
— Что ты пытаешься найти? — спросил я.
— Способ написать компилятор в чем-то кроме Ассемблера.
— А Ассемблер тоже заразили? — спросил Дейв со своего стола.
— Его нет в списке.
Я пошел к своему столу. — Я проверю еще раз.
— Что насчет браузера? — спросил Дейв, — может напишем его на Джаваскрипте? — Он так же сильно не хотел писать на Ассемблере.
— Не, он заражен, — сказал Патрик.
Дейв откинулся в кресле и руками закрыл лицо. — Да ты шутишь. Мы не напишем компилятор на Ассемблере. Не напишем.
— Это не так ужасно, — сказал Патрик, — мы день попишем всякие низкоуровные полезности, а потом это будет не сильно страшнее чем Си. Комплятор будет без оптимизаций и всего такого. Наверное нам даже не нужны числа с плавающей точкой. Нам просто им надо скомпилировать компилятор. То есть он должен сработать на всего одной программе.
— Эта программа вообще-то компилировалась два часа. Она довольно сложная.
— А компоновщик мы тоже напишем? — спросил Дейв.
Я даже не подумал о компоновщике. Видимо Патрик тоже: он повернулся к Дейву, а потом отвернулся и яростно перечитывал наши бумажки в поисках «ld».
— Тут его нет. Неудивительно, ведь проще вставлять код когда ты его компилируешь, — сказал Патрик. Я изначально не понял почему именно.
— Стоп, давайте оглядимся, — сказал Дейв, — на прошлой неделе я починил свой баг поменяв код достаточно, чтобы не вызывать этот… хак. — Он тоже не знал, как его назвать.
— Но он вернулся, а ты даже не менял код, — сказал Патрик.
— Да я знаю, но перед тем как мы напишем эту штуку, давайте попробуем подредактировать код самого компилятора — вдруг выйдет чистый билд.
— Патрик задумался. — Мы могли бы, наверное, но где ты поменяешь код? Учти, что по-моему это появилось несколько версий назад. Это значит, что их распознавание кода весьма сильное. И вообще, каждый тест у тебя будет по два часа.
— Могу компилировать в фоне, пока мы пишем эту штуку.
Так и договорились. Дейв скачал исходники компилятора, скомпилировал, нашел обфусцированные оп-коды, нашел их место в исходниках, и пробовал получить чистый билд. Я чувствовал, как он терял надежду, понимая, что обфусцированный код не просто в нескольких местах, а по всей программе.
Тем временем Патрик написал на Ассемблере простейшие рутины для манипуляции строк и файлов и разработал фильтр для текста, который не имел никакой пользы.
Я освежал свои знания Ассемблера. В институте нас ему учили на предмете «операционные системы», когда мы писали вытесняющую многозадачность. Мне почему-то очень зашло программировать так близко к железу. Я наслаждался чистотой и сыростью, когда манипулировал регистрами, точно зная, что происходит. Я испытываю ту близость с процессором, которую скрывали более высокоуровневые языки.
К обеду Патрик был готов дать мне первое задание — написать препроцессор. Сначала это казалось просто: включаешь файлы, подменяешь макросы и вставляешь условные блоки. Но потом я вспомнил о выражениях в условиях и о том, как не заменять макросы внутри строк, и я начал думать о простых решениях так же как Дейв.
Может мы возьмем зараженный? — я спросил. Я знал, что он был в списке, но не видел, как зараженная версия могла на нас повлиять. Мы могли бы прогнать весь компилятор и вручную найти измененный код. Я сомневался, что мы его увидим, но если увидим, то поправили бы препроцессор вручную.
Патрику понравилась эта идея. — Окей, ты начни делать это. Измени билд систему так, чтобы было два этапа: препроцессинг во временные файлы и их компиляция.
Наш план имел обратный эффект. Мое легкое решение не только стало сложным костылем посреди замудренной билд системы этого компилятора, а еще тратой времени, бездумно перелистывая выходной код и сравнивая с оригиналом в поисках изменений.
Патрик, тем временем, пописывал парсер рекурсивного спуска. Я внезапно услышал его смех. Он повернулся ко мне и сказал, — Я собирался аллоцировать свою первую синтаксическую ноду, как понял, что у меня нет malloc
.
Хорошо, что каждая часть этого проекта была настолько простой, насколько можно. Единственным требованием было, чтобы вывод компилятора был правильный. malloc
Патрика просто брал байты из кучи и никогда их не освобождал.
Мы так продолжали еще несколько дней. Патрик давал мне какое-то задание, типа реализовать функцию из стандартной библиотеки Си, и мы с Дейвом его решали пока ждали перекомпиляцию компилятора.
План Дейва не увенчался успехом. Он продолжал ходить по разным частям кода, пытаясь сделать, чтобы они генерировались без хаков, но каждый успех вызывал регрессию где-то еще.
Через две недели, наш компилятор, который справлялся с растущей частью оригинального компилятора, справился со всем. Я прогнал анализатор, и он выдал, что все было чисто. Мы скомпилировали оригинальный компилятор самим собой и он тоже стал чистый. На часах было 2:00 — в спешке закончить, мы совсем забыли пообедать.
— Давайте начнем пересборку всего проекта и поедим, — сказал Патрик.
Во время обеда мой мозг был слишком напряжен чтобы расслабиться и слишком усталый чтобы общаться. Дейв пытался пояснить о местной политике. Я просто ждал нашу еду, чтобы у меня уже появилась причина молчать. Наконец мне стало слишком больно думать о чем-либо кроме нашей задачи.
— А знаете, что меня бесит? — я неловко вставил когда Дейв остановился, — мы так и не приблизились к пониманию цели этих модификаций.
Патрик сразу ответил, тоже желая вылезти из политики, — если я прав, и это вызвано машинами, то тут не должно быть цели.
— Тогда зачем они это делают? — я спросил.
— Вирусы распространяются не потому что у них есть цель, — сказал Патрик, — они распространяются, потому что они хорошо это делают.
— То есть это вирус? — я спросил.
— Ну в плане того, как он распространяется.
— А откуда ты знаешь, что он распрострнаяется?
Патрик замер. — Ну оно сует что-то в наш код, — он наконец сказал, неуверенный в своем ответе.
— Оно не сует себя в наш код, — сказал Дейв, — это было бы бессмысленно — я же не пишу компилятор. Это просто был сетевой код.
— Ну а если ты хочешь распространяться, то сетевой код был бы хорошим местом для атаки.
Мне стало немного стыдно, что мы не подумали об этом перед тем, как уходить в двухнедельное написание нового компилятора. Нам так хотелось устаканить наш проект, что мы даже не изучили пришельца в нашей системе.
— Но мой сетевой код не обращался к компилятору. Ведь этот баг в компиляторе — это вирус в компиляторе. Я не понимаю, что оно по-твоему делает. — Дейв накалялся.
— Я не знаю что оно делает, — сказал Патрик, — мне просто интересно, отправляет ли оно что-то в сеть.
— Давай проверим в Wireshark, — сказал Дейв, имея в виду программу, которая собирала сетевой трафик с машины.
Внезапно, все чего я хотел — это было вернуться в офис и попробовать. Каждая мышца ныла страстью встать и уйти. Принесли наши бутерброды, и мы сразу их запаковали и увезли назад.
Я уже устанавливал Wireshark два месяца назад, чтобы помочь Патрику с проблемой связанной с TCP/IP, так что мы пошли к моему столу. Я запустил программу и записал одну минуту трафика.
— Тут много всего, — Дейв сказал, пока экран наполняелся пакетами.
— Ага, давай выберем какой-то распространенный, — сказал Патрик.
Я посмотрел на список и на глаз выбрал тот, что повторялся. Мы узнали, что это был SSH, и я вспомнил об окне которое tail’ило логи. Я закрыл его и записал еще одну минуту.
В этот раз было меньше пакетов. Мы проверили каждый по очереди. Синхронизация времени, ARP, Гугл почта, обновления всякого софта. Каждый мы исключали Wireshark'овым фильтром как только понимали, что они безвредные.
Затем я просканировал 10 минут. Было больше пакетов — опять же безвредные. Конечно, этого мы и ожидали. Дейв что-то пробурчал и пошел на кухню.
Затем я подумал. Холодок прошелся по моей руке, пока я искал ту самую бумажку на своем столе. Вот она — список зараженных програм. Мои глаза побежали по списку, матеря самого себя, что не отсортировал. Мой желуток сжался: вот и он, посреди второй страницы: Wireshark.
Патрик догадался, на что я смотрел, и увидел реакцию на моем лице когда я нашел его.
— Видимо ему мы не доверяем, — он сказал.
— Доверяем кому? — сказал Дейв, вернувшись с банкой колы.
— Wireshark'у, — сказал Патрик, — он тоже заражен.
Дейв закатал глаза. Он сел за свой стол и раскрыл бутерброд.
— Давай посмотрим на огоньки на свитче, — сказал я, — у каждого из нас был небольшой свитч на столе. Огонек на моем мигал раз в несколько секунд.
— Закрой все программы, которые мы выявили, — сказал Патрик.
Я закрыл браузер, чат, всякие демоны и утилиты. Я не мог закрыть все — всегда что-то висело в операционной системе, к примеру DHCP. Но огонек мигал так редко, что я мог соотнести его с пакетами в Wireshark. Это было хорошо. Wireshark не скрывал пакеты, ведь мы бы их увидели на огоньке свитча.
Патрик ушел за своим бутербродом, а я раскрыл свой. Я глянул на Дейва. Он лыбился над нами. Его улыбка меня пугала. Да, может мы и параноики, но это не значит, что это не правда. Я лично увидел достаточно кода за последние несколько недель, чтобы убедиться, что что-то большое и серьезное могло происходить у нас под носом.
Современный софт бесполезен без связи с сетью. Я не принимал идею, что этот аккуратно разработанный вирус ограничивался бы лишь локальной машиной, когда весь мир — всего лишь в одном пакете от него. Сотни програм на моем ПК были заражены. Я отказывался верить, что они были немые.
Патрик внезапно встал, почти уронив стул. Он пошел в коридор и через десять минут вернулся с аппаратом, размером с небольшой чемодан. Он подошел ко мне и сдвинул все с моего стола в угол, чтобы освободить место. Это был цифровой осциллоскоп, который он взял у инженеров этажом выше.
Он полез в корман и достал разводный кабель с головкой вида RJ-45. Он поставил его между моим ПК и свитчем. Он не умел пользоваться осциллоскопом. Я открыл терминал и нагенерировал для него кучу трафика. Он в итоге смог увидеть все мои пакеты. Я закрыл окно и мы подождали, меняя взгляд с осциллоскопа на огонек на передней панели свитча.
Не прошло и нескольких секунд, а трафик уже стал светиться на осциллоскопе. Я посмотрел, но не был уверен, показал ли что-то мой свитч. Я запустил Wireshark, чтобы иметь историю пакетов.
— Не надо, — сказал Патрик, — ты смотри на свитч и говори, когда увидишь пакет. Я буду говорить что вижу на осциллоскопе.
Дейв встал и расслабленно подошел к нам, стоя за моей спиной.
Я увидел огонек. — Сейчас, — я сказал, как Патрик одновременно повторил, — да.
Это произошло еще пару раз в другом порядке. Затем Патрик сказал «сейчас», а я ничего не получил. Это снова произошло через пару секунд. Холодок пошел по моей руке и ногам.
— Ого, — сказал Патрик. Я глянул, и на осциллоскопе была длинная серия трафика. А глянув на свитч, никаким огоньком и не пахло.
Дейв сказал, — вы же мне не скажете, что свитч тоже заражен и скрывает пакеты?
— Он точно скрывает пакеты, — сказал Патрик, глядя на трафик на осциллоскопе и вцепился руками в голову.
Я увидел Дейва. Его лицо побледнело. Его глаза метались между двумя аппаратами. Я был парализован.
Затем Патрик встал на стул, и посмотрел в стену. Эта поза продлилась всего две секунды, пока он не убежал обратно в коридор.
Вы сами знаете, что произошло дальше. Он вернулся со старым свитчем, намного старше этого, и воткнул его. Его огоньки светились вместе с осциллоскопом, на пакеты зацензуренные и Wireshark’ом и современным свитчем.
Я был рад, что меня вырвало. Так у меня появилось дело. Дейв ушел в ванную, а я убрался с Патриком салфетками с моего стола. Но той вони не хватало, чтобы заглушить мою панику, а мои слабые рученки тряслись в страхе.
Мы в тишине сидели за столом нашего патио, за нашими недоеденными бутербродами. Ничего, из того, что я хотел сказать, того не стоило. Я переменно убеждал себя, что мы ошиблись, и что мы обречены. Я очень надеялся, что Патрик что-то скажет.
Ворота в патио открыл почтальон. Он остановился у нашего стола, выложил письмо из своей стопки, и пошел дальше в здание.
Дейв взял его в руки. — Оно нам с тобой, — он сказал, поглядывая на Патрика. Он сорвал боковую сторону конверта, и достал письмо на отрывной бумаге. Он пару секунд путался в бумажках, а потом прочитал письмо вслух.
Господа,
Мы увидели ваши посты в интернете. Мы три года их ждали, прочесывая весь интернет и мониторя форумы.
Вы должны знать, что это происходило раньше, несколько раз. Первый раз был четыре года назад, у нашей команды в Вирджинии. Мы заметили измененные бинарники. Мы могли перекомпилировать код, чтобы они пропали, но изменения мистически возвращались через всего пару часов.
Следующий случай был всего через несколько месяцев у несвязанной команды в Сан Диего. И бинарники и исходники были изменены. Им приходилось переодически чистить исходники, потому что перекомпиляция уже не решала их проблему.
Только через год мы нашли третий случий, команду в Испании. Грязные бинарники, чистые исходники, но перекомпиляция ничего не решала. Исходный код компилятора был изменен, чтобы вставлять странные оп-коды.
Каждая команда обнаружила слабейшее звено червя и разработала решение. Слабейшее звено в следствии исчезало в последующей атаке. С каждым поколением червь погружался все глубже и глубже в систему.
Теперь ваш ход. Компилятор не только изменен, а теперь еще с чистыми исходниками. Он заражает сам себя при перекомпиляции. Наши машины уже девять месяцев этим заражены. Как и всего остального мира. А теперь возможно вам интересно, почему мы с таким нетерпением ждали ваш пост. Чтобы это объяснить, мы должны сделать два замечания.
Первое — это то, что любой мог угадать эти слабости. Лишь идиот станет подменивать бинарник от разработчиков, ожидая что его не перекомпилируют. Кто угодно бы пропустил этот шаг — нет смысла учиться этому уроку. Менять исходный код точно так же наивно. Эти модификации технически очень усложненные. Кто бы был настолько технически продвинут но так же социально наивен?
Машины. Только после третьей атаки мы додумались до этой гипотезы, и теперь мы убеждены. Если вы изучали подменные оп-коды, то вы тоже могли прийти к такому заключению. Эти оп-коды очевидно были сгенерированы методом тыка, генерируя случайную последовательность и проверяя, работает ли она правильно. Только машина так бы себя вела.
Это имеет смысл. Компьютеры не имеют эмпатии к людям. Они не могут предсказать, что мы сделаем, когда встретимся с проблемой. Машины должны были знать, что у их червя есть слабости, но они не знали какие они были. Они дали небольшной команде пострадать от их червя, чтобы команда нашла слабейшее звено и истребила червя. Машины затем исправляли эти слабости и пробовали снова.
Это крупномасштабная версия того, что они делают, генерируя оп-коды. Они пробуют разные вещи пока одна из них не сработает, вместо проектирования подобного человеку. Это обычно эффективный способ решить задачу, когда ты сделан из кремния.
Надо полагать, что вы сейчас уже написали свой компилятор, вероятно на Ассемблере, и скомпилировали все на нем начисто. Мы предполагаем, что еще через немного лет, мы увидим следующую команду на форумах. Мы уже можем представить их находки. Бинарник компилятора будет чистым. Либо переписать компилятор на ассемблере не поможет. Червь проберется глубже, вероятно в редактор текста, ассемблер, файловую систему, интерфейс жесткого диска либо даже сам процессор.
Дейв не смог закончить. Я не думаю, что там еще много оставалось. Он положил письмо на колени.
Мы утихли на несколько часов. Сначала ушел Патрик, затем Дейв. Ноябрьская ночь пришла рано и холодно, но я не мог сдвинуться.
Я переиграл наше приключение. Проанализировал каждый шаг, нашел каждое сделанное допущение. Большим прыжком было думать, что машины за все ответственны. Как говорил Карл Саган, «Выдающиеся заявления требуют выдающихся доказательств». У нас не было выдающихся доказательств. У нас вообще не было доказательств — лишь отсутствие иного объяснения.
Может это сделали люди? Люди постоянно разрабатывают вирусы. Вполне возможно, что мы просто встретились с обыкновенным вирусом и переволновались. А та команда из Вирджинии — может просто кучка психов параноиков. В это проще было поверить, чем в теорию машин.
Я чувствовал облегчение на душе, как думал об этом альтернативном объяснении. Может разработчики этого вируса написали эту программу для генерации случайных оп-кодов. Может они начали с простого, и все эти годы усложняли свою атаку — продвигая ее все глубже в систему. Может авторы — гении-аутисты.
Мой мозг опустел на много минут. Когда мысли вернулись, я считал эту теорию даже правдивее, чем ту, про машины. Конечно мы не знаем, чем промышляют машины, но я уверен, что ни один человек бы не написал такой червь. Если мы рассматриваем чей-то подход к шахматам, и видим, что они перепробовали все возможные пути, то единственной верной теорией будет игрок-машина.
Тем более это вообще не важно на практике. Мы должны были поделиться с миром нашими находками. Мы должны были остановить этих преступников, пока они не дошли до интерфейсов жестких дисков или процессоров. Нам бы ничего не осталось, если бы червь дошел до такого низкоуровнего элемента наших технологий.
Я подумал снова написать пост. Я вспомнил те «+5 funny» ответы на Слэшдоте. К кому мы должны обратиться? Интел? Правительство? Команда из Вирджинии наверное уже пыталась. Почему антивирусы это не находят? Я представлял разговоры с офицерами, которые бы ржали с наших теорий. Я представил как я бы орал.
Внезапно я встал со сжатыми кулаками шагая по закрытому патио. Я представлял как я орал на людей, потому что я не мог представить как я ору на машин. Я всегда антропомофрно обращался к компьютерам, но когда они наконец повели себя по-людски, их натура показалась эфемерной и бездушной.
Я взял свой бутерброд в обертке и ступил внутрь. Я выбросил бутерброд и бездумно открыл холодильник. Я смотрел на напитки. Ко мне пришла более счастливая мысль. Ничего в этом коде не выглядело вредоносно. За исключением редкого доставания такой команды как нашей, мы не видели ни одного знака вреда.
Патрик был прав, когда сказал, что вирус распространяется не из-за цели, а лишь чтобы распространяться. Этот червь может стать нашим вечным спутником, как митохондрия, создавшим симбиотический союз с нами. Если мы попытаемся его извлечь, то возможно он окажет опасное для нас сопротивление. Но это необязательно делать. Давайте просто оставим его. Это конечно тревожит, но подойдет.
Я закрыл холодильник, надел куртку и пошел к сигнализации. LCD экран на ее панели напомнил, что она тоже была компьютером. А зараженным? А знала ли она о нашем новом компиляторе? А даст ли она мне себя поставить? А выпустят ли магнитные замки меня из этого здания?
Я ввел цифры ее кода и услышал обратный отчет. Я вышел на патио к своему велосипеду. Они разрешили мне выйти. Я посмотрел на велосипед и улыбался его механической простоте. Я думал о светофорах и канализационных насосах. Я думал о своей кредитной карточке. Я думал об автомобилях и телефонах.
Я хотел присесть. Меня победили. Вернуть человечество обратно в до-компьютерную эру было невозможно. Мне резко стало стыдно, что я участвовал в нашей гибели. Я был программистом. Я был участником группы, которая создавала наследников.
Я заправил правую штанину в носок и открыл велосипед. Вместе со стыдом была и странная гордость. Не многие могут заявить, что создали вид, если это так можно назвать. Да, юристы с докторами очень важны. Но они всего лишь сохраняют текущее, а не создают будущих обитателей земли.
Я выехал из парковочного места на пустой улице. Я снова почувствовал тот запах цветов, что так сильно ассоциировал с Маунтан Вью. Я никогда не искал, как он называется. Нет ничего плохого в том, чтобы быть переходным видом. Практически все виды ими были, и когда-то мы тоже все равно им будем. Мы не забыли Люси. Мы не забыли динозавров. Мы полюбили динозавров. Машины нас не забудут.
От переводчика: Если вы знаете больше таких поистине увлекательных работ, то пожалуйста стучите мне в личку, чтобы я их тоже перевел.
Комментарии (10)
monoiddd Автор
07.11.2024 23:51Читателям в минуту публикации: Я уже истребил все опечатки, которые вы сейчас встречаете.
arteast
07.11.2024 23:51Не волнуйтесь, помощь уже едет: https://guix.gnu.org/en/blog/2023/the-full-source-bootstrap-building-from-source-all-the-way-down/
monoiddd Автор
07.11.2024 23:51Только если вирус не залезет в твою материнскую плату.
arteast
07.11.2024 23:51Не волнуйтесь, помощь уже в пути: https://hackaday.io/project/178826-pineapple-one
А если серьезно, то об этом можно было беспокоиться в 2014. Сейчас, когда ARM-системы приобретают популярность и им на пятки наступают RISC-V системы... От множества независимых производителей, с несколькими полностью независимыми наборами IP ядер. Остается только параноить, что, скажем, TSMC у себя при производстве умудряется привносить бекдоры во всё.
Arxitektor
07.11.2024 23:51TSMC у себя при производстве умудряется привносить бекдоры во всё.
Это конечно будет совсем жесть. Зараженный фотолитограф ). И аппарат по производству масок для чипов. И далее с какого-то времени все насквозь будет заражено. Все процессоры и все что когда-либо выпушено на них. И схемы ЦП по транзисторно не посмотреть. Софт электронных микроскопов тоже заражен. А про зараженный свич понравилось ). Пакеты есть а показаний что трафик идет нет ).
Denis_Chernyshev
07.11.2024 23:51И, кстати, LED индикаторы трафика до сих пор подключены к драйверу после PHY интерфейса. А драйвер - ASIC схема без программного управления. А это значит, маски фотолитографа уже заражены.
qw1
07.11.2024 23:51Страшилка, что зараженный компилятор будет воспроизводить себя, гуляет в американском гик-сообществе ещё с 80-х. У нас она как-то не прижилась.
Vad344
Слезились, источали слезы...
...отлаживать, выводя.
...стектрейс
"обсуждали"?
...
Больше читать не могу, так нельзя.
...GPT бот, кажется. Ну, судя по "переведенному'" тексту.
monoiddd Автор
Мокрые глаза, глаза намокли -- Так вроде говорят в речи. Тем более, это ближе к оригинальному "my eyes watered".
За "отлаживать" и "стектрейс" благодарю. Я бы писал "дебажить", но знаю, как тут любят.
"Сужать" значит сужать. Делать задачу у́же. В оригинале "to narrow down".
Видимо я бот :)
randomsimplenumber
Мои даже немного вытекли :/
Текст очень, очень затянут. Очень, очень много ненужных подробностей. Чехов достал бы своё ружье и перестрелял бы там всех ;)