Техники выживания vx.exe в реальной среде0. ВступлениеДанная статья является больше размышлением, моим взглядом, чем практическим руководством.
Так что, если что-то будет не устраивать, советую все списать на приступы моей наркомании.
Весь материал рассчитан в основном для начинающих, чтобы привлечь их внимание к этому езину.
Что же касается остальных, то все, кто занимаются темой vx, давно уже имеют свои наработки. Примечание: все примеры кода на asm; все ссылки на какие-либо упоминания в статье находятся в самом низу. 1. НачалоВ статье мы обсудим стандартные приемы, техники, нужные для того, чтобы наша прога, занимающаяся своими делами, оставляла антивирусное ПО в стороне. Возьмем пример простого инжекта dll в нужный процесс. Весь код приводить не буду, а напишу лишь кратко последовательность вызываемых API: --------------------------------------------------------------------------- Invoke, OpenProcess .. Invoke, VirtualAllocEx .. Invoke, WriteProcessMemory .. Invoke, CreateRemoteThread .. --------------------------------------------------------------------------- * Вместо .. аргументы функций Таблица импорта будет выглядеть примерно так: kernel32.dll - OpenProccess - Process32NextA - VirtualAllocEx - WriteProcessMemory - CreateRemoteThread - CreateToolhelp32Snapshot - ... - ExitProccess Такой файл никуда не годится, т.к. сразу же будет наказан из-за последовательности API и такого же палевного импорта (в случае с эвристикой). Но, видимо, хороший человек с ником dx с http://kaimi.ru выложил на всеобщее обозрение макросы для вызова API не через IAT (Import Address Table), а другим хитро вы*банным и, опять же, довольно четким (по-пацански так) способом:
* Данная функция ищется при помощи сравнения имен со строкой, поэтому советую усовершенствовать макрос, модифицируя этот способ на сравнение хеша, или просто зашифровать строку (и то, и то нужно обязательно сделать с уникальным ключом, который будет истинным для каждой новой копии, иначе толка в этом не будет). Теперь мы можем подгружать и юзать любые API, которые нам потребуются. Используем вышеупомянутый макрос для всех функций, которые могут выдать нашу основную задачу. Оставим, пожалуй, только ExitProcess для стандартного Invoke. Хоть единственная данная API в импорте, при наличии кода, не самое лучшие решение, поэтому попробуем сделать его более "реальным". 2. Генерация таблицы импортаЕсть 2 метода создания и использования:
Что касается первого пункта, он является полностью упрощенной версией второго варианта. Заключается он в том, чтобы просто заполнять импорт функциями, которые нигде в коде не вызываются. А служит для того, чтобы создавать видимость какого-то реального приложения (импорта). Некоторые люди считают, что этого достаточно, но мы будем обсуждать и использовать второй вариант. Суть в том, что мы генерируем тот же импорт, но теперь все функции из него будут вызываться в нашем коде. Приставка "псевдо" означает, что API, которые мы будем генерить, не будут нести никакой полезной нагрузки для нашего кода. А просто так говоря, "отыгрывать в пустую" , что позволит быть больше похожим на реальное приложение, и разбавить нашу последовательность API для инжекта. О том, как собрать новую таблицу импорта, мы разговаривать не будем, т.к. инфу по этому можно найти в инете, особенно полно информации на tuts4you.com. Лучше поговорим о том, какие API мы будем использовать в нашем псевдо-импорте. Речь пойдет о так называемых "Безопасных API", о которых написал kaze в 2008 году, в своей статье "Stealth API-based decryptor", очень рекомендуется для чтения, т.к. имеет большое отношение к этой теме!! Само понятие Safe API означает, что если функция будет вызвана со случайными параметрами каждый раз, наша прога не упадет, а максимум вернет код ошибки. Для поиска таких функций используется алгоритм (из перевода статьи на http://coru.ws):
После того, как мы собрали или нашли список таких API, можно создавать нашу таблицу импорта. Рекомендуется перед этим собрать какую-нибудь статистику частоты использования этих функций в реальных приложениях, возможно, повысить или понизить частоту появления определенных функций. Посмотреть, как аверы реагируют на те или иные изменения. Это нужно будет долго тестировать, настраивать, но результаты будут о себе показывать. Это очень важная и интересная тема на самом деле!! Дальше, для использования этих функций мы можем поступить так:
Первый способ является более универсальным, ну а второй для параноиков, которые остерегаются того, что аверы будут смотреть, лезет ли наше приложение в свой импорт, накидывая баллы, или нет. Что выбрать - решать тебе. Вызов Safe API происходит по тому же способу, что и при поиске, только теперь мы точно знаем, что не упадем. Алгоритм генерации случайного числа для аргумента выбираем себе по душе. Mov _ESP, Esp ; сохраняем указатель на верхушку стека Mov Ecx, 16 ; количество аргументов @@: Call Get_Random_Dword push Eax loop @F Call SafeAPI; ; вызываем API Mov Eax, Esp ; текущий адрес верхушки стека в Eax Mov Ebx, _ESP ; прежний адрес в Ebx Sub Ebx, Eax ; выравниваем то, что осталось Add Esp, Ebx ; восстанавливаем Собственно, разговор об импорте получился сравнительно затянутым, но пригодный для того, чтобы дать толчок, развивать и совершенствовать эту тему дальше. В итоге мы решили пару проблем: скрыли нашу последовательность API для инжекта dll в процесс и сделали норм импорт. 3. Изменение кодаКогда мы говорили о принципе действия макроса для вызова API не через IAT, обсуждался момент, что имя функции статично, и что это должно быть изменено. Хотя все это, конечно, пока полная ерунда, т.к. код макроса и наш код являются точно такими же. Это же простой сигнатурный рай для аверов!! Существуют следующие техники для изменения кода:
Об этом всем в этой статье мы говорить пока не будем. Любой желающий сможет найти кучу инфы на любую тему из списка. Внизу небольшие советы лишь о том, как начать. Что касается полиморфа, то советую начинающим не включать его в свои "основные" проекты, т.к. в начале это может только испортить картину. Юзайте и тестите свои полиморфные движки только на своих виртуалках. Основная проблема может быть в том, что мусорный код будет очень низкого качества:
Тем не менее, полиморф нужен везде, в каждом участке программы, т.к. каждый сэмпл для максимальной долгожительности должен быть уникальным. Что же касается метаморфинга, то здесь, в принципе, ничего сложного нету, ИМХО. Т.к. все, что нам нужно - составить список правил, по которым будем изменять наши команды. Для начала, просто составим таблицу для любого компилятора на основе того, как он генерит код в двух режимах - скорость выполнения, минимальный обьем. Но, увы, все равно придерживаться этих правил хотя бы на 90% будет ошибкой, т.к. код в таком случае будет в большинстве сэмплов одинаковыми. Также, не стоит заменять каждую команду подряд, выведите сами процент или еще лучше, пусть это значение будет передаваться в качестве параметра вашей функции, которая занимается этим. Все познается в практике. Почему я сказал, что тут ничего сложного нету, то, в отличие от того же полиморфа, например, для соблюдения логики, нам достаточно будет смотреть на наши "правила", не учитывая при этом предыдущие и последующие конструкции. Что хочу еще сказать, то не стоит ни в коем случае !!! смешивать полиморф и метаморф в одну кашу, в том плане, что "метоморфить" то, что сгенериил нам наш полиморфный движок - это просто напросто все испортит. Является исключением то, когда всем этим занимается один двиг либо два, но специально заточенных под такую технику. И то, и то можно считать высшим пилотажем, на самом деле. Пермутация. Многие считают эту технику полностью бесполезной, особенно, если она выглядит так: ----------------------------------------------------------------------------------------- 004016FB > $ EB 08 JMP SHORT Loader.00401705 004016FD EB DB EB 004016FE > BE 10164000 MOV ESI,Loader.00401610 00401703 . EB 03 JMP SHORT Loader.00401708 00401705 >^EB F7 JMP SHORT Loader.004016FE 00401707 74 DB 74 ; CHAR 't' 00401708 > EB 05 JMP SHORT Loader.0040170F 0040170A EB DB EB 0040170B > 89F7 MOV EDI,ESI 0040170D . EB 03 JMP SHORT Loader.00401712 0040170F >^EB FA JMP SHORT Loader.0040170B 00401711 74 DB 74 ; CHAR 't' 00401712 > EB 05 JMP SHORT Loader.00401719 00401714 75 DB 75 ; CHAR 'u' 00401715 > 29C0 SUB EAX,EAX 00401717 . EB 04 JMP SHORT Loader.0040171D 00401719 >^EB FA JMP SHORT Loader.00401715 ----------------------------------------------------------------------------------------- Что является просто говном, по-другому это никак не назвать!! Толку от этого не будет, обьясню почему:
И даже если добавить немного мусорного кода, менять команды на аналогичные, это все равно приведет к детекту. Так что такой вариант защитить нашу программу сразу выдаст вас с потрохами! Не задумывайтесь даже об этом. Взглянем на эту технику координально с другой стороны, которая всетаки имеет право на жизнь! Начнем с того, что забудем про JMP и перестанем менять местами по одной кострукции. Возьмем лучше наш код (можно уже видоизмененный полиморфом) и поделим его на "кусочки" размером в 15-20 команд. Очень желательно, если в них будут использоваться API. Обязательным правилом будет, чтобы в конце каждого "кусочка" присутствовала команда "RET". Дальше, просто случайным образом располагаем наши куски кода в разном порядке и заполняем какую-нибудь табличку при компиляции. Можно в виде массива, в котором индекс будет играть роль порядкового номера, каким по очереди этот код выполнять, а значение - адрес, где расположен этот кусок. Дальше - все просто дело техники: переносим весь массив в стэк и передаем на него управление при помощи все того же "RET". Как бы мы не располагали наш код, он будет выполнен, ровно в той последовательности, что и нужно, причем само использование этой команды делает передачу управления неявным, и при грамотной подаче - заранее неизвестным. Здесь может послужить один из самых простых трюков, который может быть использован для запутывания эмуля авера: call some_API ; вызываем любую API, которая возвратит заранее известное значение Add/Xor [Esp], Eax ; модифицируем наш адрес в цепочке, на основе результата API ret ; передадим управление Для антивируса проблема будет в том, что, как уже говорилось выше, он заранее не будет знать, куда мы передадим управление. Ему придется эмулировать API, а, как известно, он может вернуть какое-то дефолтное значение, не являющееся для нас истинным, что заставит перейти его не туда.. Кстати, было бы неплохо, если бы знали, что он возратит и, учтя этот вариант, пустить его по ложной тропе ;) 4. ЗаключениеЗдесь я обсудил, к сожалению, еще далеко не всё, что могло бы помочь нам утирать нос аверам.
В стороне осталось как самое простейшее: зашифровка кода и данных, так и темы: анти-эмуляция, анти-отладка, проверка
целостности кода и т.п, что является также не менее интересным. Но здесь написано лишь то, что было у меня
в голове на момент написания статьи, если бы меня спросили "Что бы я сейчас хотел рассказать начинающим?". Links:
|