asm.inject

ручная троянизация приложений под windows

крис касперски ака мыщъх, FreeBSD@smtp.ru

время тупых троянов давно прошло. сегодня уже никто не пытается сокрушить диск или удалить все файлы под чистую. такой троян будет слишком заметен, а потому навряд ли получит большое распространение. гораздо заманчивее внедрить на компьютер бакдор или шпион для перехвата паролей, электронных кошельков, номеров кредитных карт и т. д. как это слелать? а вот как!

Крупные компании и синдикаты пишут свои собственные программы (например, привлекательные хранители экрана) с закладками внутри. Соблазненный народ так и прет, а синдикат пожинает урожай.

Но так то синдикат! Написать такую программу в одиночку практически нереально, даже есть хвост разорвать, поэтому, хакеры предпочитают модифицировать уже существующие. Например, можно выпустить «новую» версию AVP Pro, или выложить на варезный сайт взломанную софтину, которую тут же утянет голодный народ. А файлобменные сети? Да через них не меньше половины всего Интернет-траффика проходит! Стоит только забросать туда программу и уже никто не сможет ее удалить!

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

Наши эксперименты будут носить совершенно невинный характер. Мы возьмем стандартный notepad.exe и будем над ним издеваться, предварительно разорвав лицензионное соглашение от Microsoft на мелкие куски. Оно нам больше не понадобится. Модификация notepad'а аннулирует все льготы, обязательства и гарантии со стороны Microsoft. Свежую Windows со скидкой уже не получишь! Ну и больно надо! Поставим Linux! Но это потом, а пока…

Рисунок 1 текст дрозофилы, набранный в редакторе WinAsmStudio

Большинство хакерских статей, рассказывающих о троянизации приложений, предлагает напрямую править их в HIEW'е или любом другом HEX-редакторе. Но это порочная практика (живодерство сплошное)! И к тому же полноценную программу в HIEW'е не напишешь, а если и напишешь, то потом не изменишь, ведь чтобы добавить одну-единственную команду зачастую приходится перебивать весь код целиком.

Поступим умнее! Наберем программу в своем любимом редакторе, например, TASMED, WinAsm Studio или FAR'е и откомпилируем ее FASM'ом. Полученный двоичный файл легко вставляется HEX-редактором в любой экзек. Ну… или почти любой.

Еще нам потребуется IDA Pro или другой приличный дизассемблер, которым мы будем исследовать подопытный файл. Отладчик — Soft-Ice, MS DBG, OllyDbg. Он поможет найти ошибки во внедряемом коде. Крайне маловероятно, что написанная нами программа заработает с первого раза, поэтому без отладчика далеко не уплывешь.

Остальные ингредиенты (пиво/квас/сигареты) по вкусу, но пива побольше.

Проще всего внедряться в свободное место готового файла, который мы будем звать дрозофилой, а сам внедряемый код — бациллой. Если свободного места ни хрена нет, можно раздвинуть последнюю секцию и внедриться в нее, но это намного сложнее, поэтому такой способ здесь не рассматривается, а всех любопытствующих мы отсылаем к «пути воина — техника внедрение в PE-файлы», который можно отыскать на сайте www.wasm.ru.

Типичный PE-заголовок вместе с таблицей секций и OLD-EXE заголовком занимает чуть больше 200h байт, а минимальное физическое выравнивание внутри файла (File Alignment), которое только поддерживает Windows, как раз и составляет 200h! Таким образом, в нашем распоряжении оказывается практически 200h незанятых байт или даже больше того! Для ускорения загрузки файла большинство линкеров выравнивает адрес начала первой секции не по File Alignment, а по Section Alignment, который никак не меньше 1000h. Как следствие — наши «владения» увеличиваются аж до E00h байт. Для ассемблерных программ это целый материк, на котором и слона разместить можно, ну если не слона, то полноценную бациллу — точно! В упакованных файлах, заголовок прижат к первой секции практически вплотную, поэтому, перед началом внедрения их необходимо распаковать.

Отроем notepad.exe в редакторе HTE и будем прокручивать его до тех пор, пока не врежемся в напаханную целину сплошных нулей. В нашел случае она начинается с адреса 2F0h (именно 2F0h, а не 2EFh, поскольку последний нуль служит завершителем строки WINSPOOL.DRV и трогать его нежелательно). Из любви к круглым цифрам, выберем 300h, хотя, выравнивать начало внедряемого кода совершенно необязательно.

Рисунок 2 поиск места для внедрения бациллы в редакторе HTE

Теперь необходимо определить базовый адрес загрузки файла. Он содержится в заголовке. Не выходя из HTE нажмем <F6> (mode) и выберем «pe/header». Там, вразделе «optional header: NT fields», будетполе «image base». Это и есть базовый адрес, в нашем случае равный 1000000h. Так же необходимо убедиться, что заголовок действительно распахнут на всю ширину (он лежит в том же разделе в поле «size of headers») и в нашем случае равен 600h. Это значит, что при загрузке файла в память отображаются только первые 600h байт от его начала, а поскольку мы начинаем внедрение с 300h байта, размер бациллы не может превышать 600h –300h == 300h байт. Плохо! Очень плохо! Но при желании это поле можно увеличить до 1000h. Главное, чтобы оно не превышало Section Alignment, указанного ниже. Так же необходимо убедиться, что файл не содержит перемещаемых элементов, которые могут испортить всю малину. Смотрим, если поле «baserelocationtable» в разделе «optionalheader: directories» не равно нулю, лучше всего отказаться от внедрения. При большом желании можно внедриться и в перемещаемые файлы, это лишь чуть-чуть труднее, однако, не будем лезть в дебри и для начала разберемся с малым.

Контрольную сумму (checksum) править необязательно. Windows все равно ее игнорирует. Антивирусы, кстати говоря, тоже. Я всегда говорил, что они тупые создания! Впрочем, для перестраховки это поле можно обнулить или рассчитать новую контрольную сумму с помощью утилиты editbin, поставляемой с компилятором Microsoft Visual C++.

Рисунок 3 основные поля дрозофилы, ответственные за внедрение бациллы

Так же можно внедряться в конец кодой секции, в хвосте которой «пасется» до FFFFh свободных байт, оставленных для выравнивания, однако, чаще всего их количество не превышает 50h, чего для полноценной программы явно недостаточно, но на всякий случай будем иметь эту заначку ввиду.

Берем все тот же HTE, привычным движением руки давим <F6>(mode), «pe/header» и смотрим атрибуты секции .text (в некоторых случаях она называется CODE или как-то еще). Как быстро перейти в конец секции .text? Очевидно, необходимо переместиться на начало следующей секции (в нашем случае эта секция .data), а затем вернуться на один байт назад.

Переводим HTE в режим страничного имиджа (<F6>, «pe/image»), давим <F5> (goto) и говорим «section(».data«)», заставляя редактор перейти к началу секции «.data». Переводим курсор на несколько строк вверх и… здравствуй, хвост секции .text! Нулевые байты (которым соответствует ассемблерная команда add [eax],al) никем не заняты и могут использоваться по нашему усмотрению. В данном случае здесь содержится 38h байт. Хм, не слишком-то длинный хвост. Бывают хвосты и подлиннее!

Рисунок 4 незанятый хвост секции .text, готовый к внедрению бациллы

Бацилла (если это только не вещь в себе по типу океана Соляриса), должна как-то взаимодействовать с внешним миром: открывать файлы, устанавливать сетевые соединения, выводить ругательные сообщения и т. д. Обычно для этого используется прямой вызов API-функций. Но это не лучший вариант. Намного удобнее использовать высокоуровневые библиотечные функции, доставшиеся бацилле в наследство от дрозофилы. Согласитесь, намного удобнее открывать файл с помощью _fopen, чем CreateFile. Одних только аргументов в последнем случае потребуется миллион!

Библиотечные функции легко распознаются при помощи IDA Pro. Вот, например:

Рисунок 5 библиотечные функции, автоматически распознанные IDA Pro

Мы видим, что адрес функции _fopen равен 401988h, значит, ее вызов будет выглядеть приблизительно так:

pushaRb; «rb» указатель на строку с режимом открытия файла

pushaName; указатель на имя файла (аргументы заносятся справа налево)

call401988hl вызываем функции _fopen

addesp, 8; удаляем аргументы из стека

Листинг 1 пример вызова стандартной библиотечной функции языка Си

Однако, использовать библиотечные функции можно только после того, как отработает Start-Up (стартовый код), иначе у нас ничего не получается. В этом нам вновь поможет IDA Pro, распознающая функцию main (WinMain) языков Си/Си++ и процедуру Begin на Паскаль. В notepad'е ее вызов расположен по адресу 1006571h. Здесь находится call 100299Eh, где 100299Eh – адрес WinMain. Для внедрения дрозофилы, достаточно изменить call 100299Eh на свой адрес, в смысле адрес свой бациллы.

Рисунок 6 функция WinMain в стартовом коде

А что делать, если библиотечные функции не распознаны или среди них нет той, что нужна нам? Тогда можно обратиться к импорту дрозофилы и поискать там. В IDA Pro этоделаетсятак: View  Open Subview  Imports:

Рисунок 7 функции, импортируемые из USER32.DLL

Вот, например, адрес функции MessageBoxW равен 01001204h. На самом деле, это еще не сам адрес, а только указатель на него, инициализируемый на стадии загрузки, а потому вызов функции будет выглядеть так:

xoreax,eax; обнуляем eax

push30h; uType

pushlpCaption; lpCaption

pushlpText; lpText

pusheax; hWnd

calldword [1001204h]; вызываемфункции MessageBoxW

Листинг 2 пример вызова API-функции из импорта дрозофилы

Но в некоторых случаях, в импорте искомой функции нет функции. Что тогда? Есть два пути: использовать связку LoadLibrary\GetProcAddress или сканировать импорт вручную. Оба этих способа уже рассматривались в статье «техника написания переносимого shell-кода», опубликованной в таком-то номере журнала, так что не будет здесь повторятся.

Кодовая секция недоступна для записи. Где же дрозофила будет хранить свои переменные? Проще всего воспользоваться стандартной Сишной функций malloc или API-функций VirtualAlloc, причем выделенную память можно даже не освобождать — при закрытии приложения Windows это сделает самостоятельно.

Еще проще выделить на стеке. Команда SUB ESP, n – выделяет n байт, а ADD ESP, n – возвращает их обратно. Однако, типичный объем стека составляет всего несколько мегабайт, а этого количества не всегда достаточно, так что на практике оба способа приходится комбинировать друг с другом.

Вот и подошли вплотную к внедрению. Создадим бациллу, выводящую простое диалоговое окно перед запуском дрозофилы. Открываем FAR или любой другой редактор и пишем. А что мы, собственно, пишем?

Поскольку это не совсем обычный файл, транслятор должен знать с какого адреса начинать ассемблирование. Мы решили внедряться в дрозофилу со смещения 300h, так? Базовый адрес загрузки равен 1000000h, следовательно, первый байт бациллы соответствует адресу 1000300h. Так и запишем: «ORG 1000300h». ORG – это директива, отвечающая за базирование файла.

Еще необходимо указать «USE32», чтобы FASM знал, что это 32-разрядный код. Вот, собственно, и все отличия от нормальных файлов. Дальше можно кодить как обычно. Ах да, чуть было не забыл. Ведь это notepad.exe под Windows NT и, следовательно, ASCII-функций в нем нет, а импортировать их вручную нам лениво. Поскольку, FASM не поддерживает уникода, перекодировку приходится осуществлять самостоятельно. В FAR'е для этого можно набросать следующий макрос: «' Right ' , 0 ,» (одинарная кавычка, стрелка влево, одинарная кавычка, запятая, ноль, запятая), который будет преобразовывать ASCII строки в UNICODE. Правда, только на английском языке. Для русского все намного более заморочено. Проще всего взять notepad.exe, записать в нем строку, сохранить, как уникод, и вставить получившийся файл в исходный текст при помощи db.

Законченный текст программы может выглядеть например так:

org1000300h; адрес начала бациллы в памяти

use32; 32-разрядный код

pushad; сохраняем все регистры

xor eax,eax; EAX := 0

pusheax; uType (диалог с кнопкой ОК)

pushcaption; указатель на заголовок

pushtext; указатель на текст в окне

pusheax; hWnd (нет владельца)

call dword [1001204h]; MessageBoxW

popad; восстанавливаем регистры

jmp 100299Eh; передаем управление WinMain

ret; возвращаемся в стартовый код

caption db 'h',0,'e',0,'l',0,'l',0,'o',0,0,0

text db 'I',0,' ',0,'l',0,'o',0,'v',0,'e',0,' ',0,'y',0,'o',0,'u',0,0,0

Листинг 3 исходный текст простейшей бациллы, признающийся в любви

Ассемблируем программу FASM'ом и на выходе получаем двоичный файл (для определенности пусть это будет inject.bin). Запускаем HTE, открываем notepad.exe и тут же открываем inject.bin (<F3> (open), «inject.bin). При помощи shift'а выделяем весь код бациллы и ждем <Ctrl-Ins> для копирования в буфер обмена. Переключаемся на notepad.exe (<Alt-2>), находясь в hex-режиме подводим курсор к смещению 300h и нажимаем <Shift-Ins>, чтобы вставить. Сохраняем изменения по <F2> и… открываем пиво. Первый этап внедрения завершен. Теперь можно послушать BurningPoint, собраться с мыслями и немного расслабиться.

Рисунок 8 вставка бациллы в дрозофилу

И вот наступает последний решительный этап: перехват управления у WinMain, точка вызова которой, как мы помним, лежит по адресу 1006571h. Переводим HTE в дизассемблерный режим (<F6> (mode), pe/image), жмем <F5> (goto) и говорим: «1006571h». Теперь нажимаем <Ctrl-A> (Assemble) и вводим «CALL 1000300h», где 1000300h — адрес начала бациллы. Кстати говоря, HIEW для этой цели непригоден, поскольку неправильно ассемблирует код и все летит к черту. Во всяком случае, версия 6.09 ведет себя именно так, а более поздние не проверял. Зачем платить деньги за коммерческий HIEW, когда есть и бесплатные HEX-редакторы не хуже!?

HTE все ассемблирует правильно, но переходить к бацилле по перекрестной ссылке наотрез отказывается, считая, что ее нет. Ну нет — и не надо. Нажимаем <F2> (save), чтобы изменения возымели силу и выходим по <F10>.

Рисунок 9 перехват управления у WinMain

C замираем сердца запускам notepad.exe и… Оторвать мыщъх'у хвост это работает, выдавая симпатичное диалоговое окно:

Рисунок 10 сообщения от бациллы

И все бы было у нас хорошо, если бы антивирусы ни ругались на инфицированный файл. А они ругаются, причем матом, что портит нам весь бизнес и создает угрозу отрыва хвоста и детородного органа вместе с ним:

Рисунок 11 реакция антивирусов на бациллу

Вернемся к адресу 1006571h, в котором происходит передача управления на бациллу и посмотрим, что тут можно предпринять:

100656a ! ff1594100001calldword ptr [KERNEL32.dll:GetModuleHandleA]

1006570 ! 50pusheax

1006571 ! e88a9dffffcall1000300h

1006576 ! 894598mov[ebp-68h], eax

1006579 ! 50pusheax

100657a ! ff1568110001calldword ptr [MSVCRT.dll:exit]

Листинг 4 окрестности точки в которой происходит перехват управления

Очевидно, антивирус отслеживает команду CALL, эмулируя ее выполнение. А как на счет передачи управления через ret? Интересно, сможет ли он с ним справиться? Давайте заменим CALL 1000300h на PUSH 1000300h/RET. Это ведь несложно. Правда, возникает одна проблема, — «чистый» CALL на байт короче и этого самого байта нам как раз не хватает! К счастью, следом за CALL'ом расположена команда MOV [EBP-68h], EAX, копирующая код возврата в локальную переменную, а кому он сейчас нужен? Поэтому мы можем смело оттяпать от нее один байт и перекрыть оставшиеся два байта операциями NOP. (Как вариант, можно перенести часть Start-Up кода внутрь бациллы, но в нашем случае это излишне).

Подводим курсор к 1006571h, жмем <Ctrl-A> и пишем PUSH 1000300h/RET/NOP/NOP. К сожалению, HTE позволяет ассемблировать только одну команду за раз, поэтому жать <Ctrl‑A> приходится многократно. Еще необходимо в теле бациллы заменить JMP 100299Eh/RET на CALL 100299Eh/JMP 1006579h (мы ведь заменили CALL на эквивалент JMP'а, поэтому, для достижения гармонии необходимо проделать обратную ему операцию).

Сохраняем изменения по <F2>, выходим и… Бацилла по прежнему работает как миленькая, но антивирус уже не ругается. Обиделся, наверное. Или уснул.

Рисунок 12

Вообще-то, это не самый надежный антивирусный прием, зато самый простой! Остальные можно найти в статье «техника выживания в мутной воде или как обуть антивирус», опубликованной в таком-то номере хакера.

Созданная нами бацилла предельно проста и пользы от нее немного. Но ведь и вреда никакого! Более сложные программы пишутся аналогичным способом и каждый из нас сможет справится с этой задачей самостоятельно, так что не будем охлаждать творческий стимул и выдавать на гора кучу готовых полуфабрикатов.