МЕТОДОЛОГИЯ НЕДЕТЕКТИРУЕМОГО ВИРУСА Я постараюсь поменьше вдаваться в детали и побольше говорить о самой сути дела. А суть в том, что у меня появилась уверенность: я знаю, как выглядит недетектируемый вирус; а если в чем-то здесь и ошибаюсь, то, все равно, это начало, фундамент, база -- то, на чем будет построен настоящий недетектируемый вирус. Рассмотрим некоторую юзерскую аппликуху (просто программу), которая (обычно) делает всякую хуйню, но в день X время Y производит НСД с вероятностью 1/100. Выявить эти действия, до самого последнего момента, в общем случае, невозможно. Я говорю о том, что если программа именно такой и была задумана, и код, производящий НСД, был написан и откомпилирован вместе с самой программой, то такая программа не определяется обычной эвристикой. Вы уверены в своем мастдае? фотошопе? ворде? Та самая фишка, которая отсылает информацию о вас билгейцу где-то в интернете, не то ли это НСД, о котором я говорю? Это именно оно. Это то, что, не зная где/как/зачем искать, практически невозможно обнаружить. Это и есть идеал; то, к чему следует стремиться при заражении исполняемых файлов. Другими словами, нужно при заражении максимально интегрировать код вируса с кодом программы, то есть сделать так, как если бы он в ней присутствовал изначально. В первом приближении это UEP. То есть вместо замены точки входа, вставка перехода на вирус в случайное место программы. Дальше идет всовывание вируса внутрь кода программы вместо каких-то процедур; возможно по кускам; однако, все это реализуется крайне просто и так же просто детектируется. Следущий шаг, о котором и идет речь, суть реверсинг программы, то есть ее декомпиляция, затем изменение где-то на уровне линкера, то есть на уровне кусков кода и инструкций, и сбор обратно в работающий троянизированный файл. Доказательство возможности автоматического реверсирования файлов таково: существует IDA, 100%-корректно дизассассемблирующая некоторые файлы, совершенно без посторонней помощи; существует TASM, который полученные IDой .asm-файлы успешно компилирует; а значит, этот процесс возможно автоматизировать. Более того, мы не дизассемблируем инструкции до уровня мнемоник, а только до уровня их длин; и при ассемблировании у нас также все намного проще. Это и было сделано; и это работает; это вирус z10 и движок mistfall. И с детектилкой, судя по всему, большие проблемы. Но не об этом здесь речь. Здесь я говорю о методологии: то есть о методах и средствах, структуре и логике написания недетектируемого вируса в общем виде, независимо от операционки, процессора, формата файла и т.п. Основная мысль в том, что невозможно качественно изменять уже откомпилированные файлы. А я говорю, конечно же, исключительно о бинарных, исполняемых файлах. Несложно догадаться, почему при вставке (а не замене, т.е. insert а не patch) нескольких байт между двумя половинами файла, это приводит к потере его работоспособности. Ибо нужно нечто больше: нужно после этой вставки заново пересчитать все указатели, смещения и т.п. Как вы будете чинить/апгрейтить незнакомый вам, скажем, телевизор? Вы его сначала осмотрите, и скажете: да, есть резон этим заниматься. Можно было бы, конечно, пару винтиков впихнуть в щель, скажем, между панелями, или насыпать железной стружки внутрь, но это не красиво. Работать это не будет. Поэтому телевизор разбирается на части. При этом, мы знаем что это телевизор такой-то там марки/модели (PE EXE), то есть, именно по этому мы им и занимаемся. Но мы не знаем его устройства (нет исходников). Но вообще, что-то нам о деталях и связях между ними известно (PE формат и инструкции x86). Поэтому все вытащенные детальки откладываются в стороны, а в голове (или на бумажке, или в памяти компа) рисуются связи между ними, чтобы потом можно было собрать все обратно. Когда все окончательно разобрано, можно, например, пинцетом вынести на помойку горстку полуобгоревших тараканов, случайно заползших в телек. Пусть их аналогией будет алигнмент между процедурами. Затем в телек добавляется некое устройство, новые детали (инструкции), скажем жучок, или бомба, или что-то еще. Чем более пыльными и некузявыми они будут, тем меньше вероятность что их заметят. То есть новые детали маскируются под уже существующие. Другими словами, инструкции надо сделать такими же, какие они в файле, в зависимости, например, от компилятора. Желательно сделать так, чтобы без этих деталей телек уже не мог бы работать; т.е. чтобы они стали неотемлемой его частью. Так будет сложнее их выявить. После всего этого телек собирается, уже с использованием той информации о его структуре, которую мы получили при разборе. Аккуратный человек принесет тараканов обратно, и присыпет внутренности пылью. Короче говоря, это и был реверсинг. С программами, да еще в формате PE EXE все обстоит куда проще, потому как любую деталь можно произвести самому, тут же, и в любых количествах, да и все это делается автоматически. Ведь вы же не будете лично так извращаться с каждой программой? Вы напишете специальную программу для этого, т.е. движок. И он будет уже будет заниматься реверсингом. А вы только будете ему говорить: "так, типа, разобрал? вот те два проводка перекинь, вот этот кондер выпаяй, а вот эту коробочку вкрути вон туда; ну все, типа, можешь собирать обратно". Вот радиолюбитель так с телеком сделать не может, потому как он не на том уровне, чтобы делать роботов; а мы, программеры, запросто делаем все что хотим; и в этом наше преимущество. Хотя все сидим за мониторами. Впрочем, я отвлекся. Здесь есть один важный момент: до какой степени разбирался этот телек. Снимали ли с него только корпус, разбирали на панели, блоки, на отдельные детали. Ясно, что нет, например, смысла разбирать трансформаторы на части, или вскрывать трубку ;-) -- потом можно и не собрать; точно также и не всегда резонно при реверсинге софта заходить дальше, чем, скажем, длины инструкций. Таким образом, общая схема реверсирования такова: 1. получение полного доступа к объекту 2. получение необходимой информации о составляющих и связях между ними 3. разбор объекта на части, восполнение недостающей информации о связях 4. модификация частей/связей 5. сбор измененного объекта, с учетом всей имеющейся информации Очевидно, что возможно проводить реверсирование не всего объекта, а только некоторой его части. Но объект, каким бы он ни был, всегда является частью еще более сложной системы, так же, как его части принадлежат ему самому; поэтому не имеет смысла говорить о частях объекта, единственная рекомендация -- это учитывать взаимодействие с системами более высокого уровня. Например PE EXE файл может вызывать функции системных DLL'ек по фиксированным адресам, без использования фиксапов. Так что можно его было бы рассматривать как часть всей операционки, но все таки мы говорим о файле. Теперь рассмотрим вопрос о противодействии детектированию изменений. Прежде всего, как будут искать эти изменения? Естественно, сначала начнут с качества элементов. Например, в кодовой секции PE EXE файлов крайне редко бывает много псевдо-случайных байт, т.е. блоков с равновероятным распределением элементов. Но мы уже говорили, что наши троянские, вставляемые в объект части мы максимально возможно маскируем под уже существующие; т.е. желательно вирусу быть незакриптованным. После того, как будут проверены все элементы, начнут проверять связи между ними. Т.е. если мы вставляем в программу большой блок кода, то он, наверное, будет обладать малым числом внешних связей и большим числом связей внутренних. Рекомендация -- увеличить это число связей до среднестатистического уровня. После того, как будут рассмотрены все элементы и связи, антивир будет пытаться выявить закономерности более высокого порядка, характерные для всех инфицированных файлов. Наилучшим образом это может быть сделано при помощи самообучающейся нейросетки; вот только аверы еще не доросли до таких мудростей. Важным свойством недетектируемого вируса будет являться маскировка под нечто существующее; например под какой-нибудь компилятор или упаковщик. Отдельно это было рассмотрено в одном из RVM'ов, когда ассемблерный вирус генерил для себя декриптор, ничем не отличающийся от файла скомпилированного турбопаскалем. Хорошая недетектируемость может быть достигнута также с применением модульной структуры, когда нет никакой определенности в наборе инструкций, из которых вирус состоит. Сам вирус может быть модульным, написанным на некотором макро-языке, который будет с использованием специального компилирующего модуля преобразовываться в ассемблерный код при каждой генерации рабочей вирусной копии. Таким образом, апдейт этого компилирующего модуля или его интеграция (взаимодействие) с другим таким модулем будут отражаться сразу на всем вирусном коде. Дальше, мы можем использовать инструкции программы в участках своего собственного кода; это тоже относится к маскировке. Пусть, например, программа работает только с регистром AX, а вирус -- только с регистром BX. Таким образом возможно навести статистику и выявить участки вирусного кода. Очевидно, в этом случае эффективно будет копии некоторых своих инструкций (работающих с регистром BX) накидать в случайные места среди кода программы, а некоторые инструкции программы размешать со своим собственным кодом. Теперь поговорим об универсальной промежуточной сущности, которая позволяет нам с такой наглостью заявлять: "удалим из программы такие-то инструкции", или "инвертируем все условные переходы", и т.п. Нет никаких сомнений, что это -- список элементов программы, также я буду говорить о списке инструкций, что то же самое. Движок RPME работает только с инструкциями, и ориентирован на пермутацию вирусного кода. А движок MISTFALL -- это то же самое, но для работы со всеми элементами PE файлов. Список инструкций хранится, естественно, в памяти; это связанный одно- или двунаправленный список, и каждая его запись описывает один элемент программы, как то: инструкцию, блок данных, фиксап, rva-адрес, метку; а также содержит указатель на следующую запись, и на запись о следующей инструкции. Кусок кода: E8 xxxxxxxx call main ... main: 90 nop C3 retn Часть списка: ,-#3--------. ,-#7--. ,-#8--. ,-#9--. root-->...-->| call main |-->...-->|main:|-->| nop |-->| ret |-->...-->NULL `-----------' `-----' `-----' `-----' size=5 size=0 size=1 size=1 next=#4 next=#8 next=#9 next=#10 nextopc=#4 nextopc=#8 nextopc=#9 nextopc=NULL Создается такой список в два шага: сначала файл грузится в память и разбирается его структура: формат, точки входа и т.п.; а затем, начиная с некоторых помеченных мест, производится дизассемблирование (разбор кода на инструкции). На чем может быть написан реверсирующий движок? Все зависит от мастерства автора. Я считаю, что следует разумно сочетать C++ и ассемблер, т.е. разбить задачу на части и каждую часть писать на том, на чем это наиболее эффективно. В принципе, возможно написать реверсирующий движок исключительно на ассемблере. Реверсинг файлов с целью их заражения -- это новое слово в вирусах; на реверсинге основывается интеграция кода вируса с кодом программы; а это уже идеальный, практически недетектируемый метод заражения. Для осуществления реверсинга необходимы следующие вещи: знание ассемблера, знание формата реверсируемых файлов, и дизассемблер длин инструкций. Интеграция кода вируса с кодом программы -- это работа на уровне списка элементов файла; а именно -- вставка вирусного кода и/или декриптора в самые разные места кодовой секции; все это, естественно, на основе реверсирования файла. Реверсинг и интеграция позволяют также использовать и старые добрые поли- и метаморфизм; полиморфным может быть вирусный декриптор, разбросанный по всему файлу, спрятанный в файле; а метаморфным/пермутирующим может быть сам вирусный код. При этом, возможно сочетать все это с модульной структурой вируса, когда новые модули постоянно выкачиваются из инета; а замена ассемблера собственным макро-языком позволит еще более увеличить сложность и, как следствие, возможности вируса. Таким образом, в этом тексте вам был показан путь: то, к чему идет вирмэйкинг; то, к чему мы все стремимся и то, что рано или поздно будет сделано. Z0MBiE Март-2001