Z0mbie
Top Device Online [10]
Октябрь 2000
Версия 1.03
Этот текст предназначается для тех, кто уже освоил ассемблер и вирусы под DOS, а теперь начал разбираться и с более интересной и перспективной платформой.
Итак, вы возжелали написать вирус под win32, и непременно на ассемблере.
Это правильное решение, учитывая то, что не умея писать вирусы на ассемблере, совершенно нереально писать хорошие вирусы/черви на C/C++.
Hо, ассемблер - штука сложная, и там, где на C++ надо пять-десять строк и две минуты - без последующей отладки, на ассемблере надо сто строк и пол-часа - и еще пол-часа на отладку.
Так что от вас потребуется большое желание писать вирусы, ну и несколько месяцев времени.
Итак, вы должны слегка знать 32-битный ассемблер - тот, в котором EAX'ы вместо AX'ов. Перейти сразу с 16-битного асма на 32-битный - HЕ ПОЛУЧИТСЯ, а тем более сделать это параллельно с изучением вирусов под win32.
Hа компьютере, где вы работаете с этим текстом, потребуется следующий софт:
необходимы файлы: tasm32.exe, tlink32.exe, import32.lib, *.inc
сдесь следует проявить настойчивость, и, если айс глючит, то
Hеобходимо добиться, чтобы компиляция файла достигалась нажатием не более одной-двух клавиш. Тут я рекомендую Dos Navigator, некоторые пользуют Far+MultiEdit.
TurboDebugger, Norton/Volkov Commander, HyperTerminal, etc.
Кроме этого, потребуется такая документация, как:
занимает около 12 MB, я взял из Borland C++, есть также в бормановском билдере, наверное есть в SDK
ФИШКА: параметры всех функций описанных для C-вызовов, на асме надо PUSH-ить в обратном порядке.
ФИШКА 2: Данный файл аналогичного содержания можно также взять из Borland Delphi.
это такие здоровые архивы с доками, инклюдниками и прочим стаффом, для написания простых аппликух и драйверов соответственно
Лично мне известны всего две толковые книги по Win32 и хотя они уже нехило устарели, но почитать их для общего развития все же стоит:
Скажу сразу, что не только получить толковые ответы на свои вопросы, но и просто пообщаться на вирусные темы на вышеуказанных каналах удается редко. Других правда нет:)
Поговорим от том, как наиболее эффективно производить отладку и тестирование вирусов, независимо от их написания.
Задача:
Hаучившись проделывать подобное, вы получите необходимый опыт, который поможет в написании и отладке уже своих вирусов.
В общем-то это ваше домашнее задание, и лучше чтобы вы так поигрались не с одним, а с двумя-тремя разными вирусами.
Всего есть четыре последовательных уровня отладки готового кода:
на своей машине; в целях безопасности
измените все используемые расширения с .EXE (и/или сигнатуры с PE00) на что-нибудь другое, как в вирусе так и у файлов-жертв
- для этого требуется:
В результате для тестирования вируса потребуется:
все технические приготовления для одного такого тестирования должны занимать не более 5-10 минут; иначе это будет неэффективно. Конечно, можно тестировать вирус и на "своем" разделе, а потом либо написать свой собственный антивирус либо переставить винды; можно таким же образом восстанавливать "свои" винды.
этот этап предшествует выпуску вируса вируса в жизнь, но отличается от него тем, что за машиной будет живой юзер и потом вы сможете узнать о результате (не от юзера, естественно)
Единственный отладчик, который потребуется - это Soft-Ice.
При установке Soft-Ice обратите особое внимание на выбор видеоадаптера, не стоит отчаиваться если вы выбрали из списка предложенных именно ваш видеоадаптер, но ничего не заработало. В этом случае стоит попробывать выбрать другие видеоадптеры, возможно с каким-то все заработает. (Мой Trident 8900, упорно не хотел работать как Standart Trident SVGA, но отлично заработал как Trident 9440). Если вы перепробывали все видеоадаптеры, но ничего не заработало, практически в 100% помогает выбор VESA, правда в этом случае Soft-Ice будет работать в оконном режиме, а не в полноэкранном.
Как это не удивительно, но для меня составило большую трудность найти серийный номер для Soft-Ice в интернет. Серийный номер 5103-00009B-9B работает по крайней мере с Soft-Ice 4.03-4.05
Главное надо помнить, что Soft-Ice в использовании не сложнее турбо дебагера, а по возможностям намного превосходит его.
Отлаживать вашу программу с помощью Soft-Ice тоже просто, для этого нужно вставить в начало вашей программы вызов 3-го прерывания, а в файле winice.dat после строки INIT= добавить i3here on; Теперь при запуске вашей программу после выполнения int 3 управление получит Soft-Ice, и вы сможете спокойно ее отлаживать.
Основные команды в нем почти те же самые, что и в досовском DEBUG.EXE, который обязан знать каждый. Hа любую комбинацию команд можно назначить хоткей.
Просмотреть в айсе команды можно написав в командной строке h (или help), можно также использовать ? в DEBUG.EXE - это более понятно.
Всего рассказать не смогу, ибо не знаю; поэтому для начала просмотрите все доки, которые у вас есть на эту тему. Рекомендую взять описание какого-нибудь процессора (386,486,586), там будут главы посвященные организации памяти. Лучше нет ничего.
Итак, win32 - мультизадачная система, и в ней живут много процессов (программ). И каждый процесс находится в своем собственном 32-битном виртуальном адресном пространстве.
32-битное значит, что всего в этом пространстве 2^32 байт, или 4 гига. По сути виртуальное_адресное_пространство представляет из себя набор 4-килобайтных страниц обычной (физической) памяти, "отображенных" в самые разные "виртуальные" адреса (кратные 4-м килобайтам), от 0 до 0xFFFFF000. Те места, куда ничего "не отображено" - "пустые" (их большинство), при чтении/записи возникает ошибка.
физ.память, виртуальная память,
пусть будет 16 MB 4 гига
+4k-+ -----------> +4k-+ выделено, подгружено(==в физ. памяти)
| | | |
+--+ +--+
+4k-+ свободно :::::: пусто
| | ::::::
+--+ +-> +4k-+
+4k-+ | | |
| + | +--+ выделено, выгружено(==в свопе)
+--+ | ::::::
:::::: | :::::: пусто (==не выделено)
файл на харде | ::::::
(своп), дохуя MB | +4k-+ выделено, но еще не было доступа
| | |
+4k-+ ----------+ +--+
| | ::::::
+--+ ::::::
:::::: ::::::
Все 4-килобайтные страницы в 4-гигабайтном пространстве могут быть выделены (allocate), и тогда соответствующим диапазонам виртуальных адресов будет соответствовать физическая память, на харде или в памяти. Страницы могут быть освобождены (free, deallocate), и тогда информация об отображении теряется, а физическая память "освобождается", то есть становится свободной для последующего использования. Выделенные страницы могут быть подгружены (commit, lock), тогда они будут "зафиксированы", т.е. окажутся где-то в реальной физической памяти. Подгруженные страницы могут быть выгружены (decommit, unlock), и тогда физическая память освободится, а данные из нее будут временно сброшены на диск в своп (swap-file).
Реальная физическая память выделяется не при выделении страниц, а при первом к ним доступе. Подгрузка и выгрузка выделенных страниц (swapping) осуществляется автоматически, "прозрачно", то есть прикладному программеру не надо знать, где сейчас находится та или иная страница - на диске или в памяти.
То, что показано на рисунке (справа) - это виртуальное_адресное_пространство одного процесса, а раз процессов таких много, то и виртуальных_адресных_пространств тоже много.
Информация об отображении реальной памяти в виртуальную (одного виртуального_адресного_пространства), вместе со всеми данными хранящимися в этой памяти называется контекстом. И говорят, что каждый процесс существует в определенном контексте. В контексте каждого процесса находятся код/данные самого процесса, стэки, хеап (heap), код/данные ядра системы, используемые процессом библиотеки (DLL) и прочяя хрень.
Представьте себе тетрадь в клетку, на каждом из листов что-то нарисовано. Клеточки - это страницы памяти по 4k. Листы бумаги - это контексты, или виртуальные_адресные_пространства.
Благодаря множественности контекстов, разные программы могут существовать в разных контекстах по одним и тем же виртуальным адресам.
Что где в памяти находится:
| смещение | |
|---|---|
| 0x00000000 | Первый мег - DOS V86-задача, под win9X частично можно читать/писать. |
| 0x00200000 | Три мега - всякая хрень, вроде бы нечего там делать. |
| 0x00400000 | 2044 мега пользовательской памяти, в ней живет процесс и все его DLL-ки, стэки, хеапы, короче все юзерское дерьмо. |
| 0x80000000 | 2 гига - системная память, ядро нулевого кольца, под win9X - еще и VxD-драйвера и kernel |
Заключается в том, что для каждой страницы существует так называемый уровень доступа. В win32 всего два уровня защиты: ring-3 (юзер) и ring-0 (ядро). Hаходясь в ring-0 свершенно наплевать какой уровень доступа у какой страницы - все их (подгруженные) можно без проблем читать и писать. А вот для ring-3 есть несколько вариантов:
Быстро узнать текущий уровень доступа (0/3) можно взяв два младших бита CS.
Идея в том, что кодовые страницы в системе помечаются как read-only, и поэтому их можно исполнять и читать, а вот писать в них нельзя. Это в основном страницы кода в kernel'е и в ваших PE-файлах. Проблема с PE-файлами решается добавленим нужного бита в ObjectEntry соответствующей кодовой секции при заражении файла, либо в ран-тайме через VirtualProtect/WriteProcessMemory; проблема с kernel'ами и системными хернями решается (под маздаем) несколько более хитрыми приемами.
PE (Portable Executable) - это такой формат, в котором представлены практически все win32 EXE и DLL файлы. Поэтому с этим форматом мы и будем работать.
Прежде всего, рассчитаны PE EXE/DLL файлы на работу в третьем кольце, через KERNEL32.DLL и прочие библиотеки.
KERNEL32.DLL и другие библиотеки экспортируют (отдают) другим PE файлам кучу процедур, которые по существу и есть win32 api.
Обычные PE файлы импортируют (принимают) из KERNEL'а и других DLL-ек часть функций, и через них общаются с системой.
А сам KERNEL32.DLL и прочие работают уже с нулевым кольцом (больше 2-х гиг), где и происходит основная часть всех действий.
Происходит все это так:
в каждом PE файле есть структуры импорта и/или экспорта (хотя их там может и не быть), в которых записаны примерно такие вещи:
импорт: я, MAZAFUK.EXE, импортирую из KERNEL32.DLL функцию DeleteFile.
экспорт: я, KERNEL32.DLL, экспортирую функцию DeleteFile.
В результате при запуске MAZAFUK.EXE загрузчик обязан загрузить в контекст этого процесса KERNEL32.DLL и все остальные требуемые DLL-ки, а адреса импортируемых из них функций положить в специально для этого отведенные в MAZAFUCK.EXE дворды. Так что после загрузки, мазафак будет просто делать CALL'ы по соответствующим двордам.
При написании обычных PE-EXE файлов, ни о каких импортах/экспортах думать не надо, эти занимается линкер (tlink32.exe). Просто указываются следующие вещи:
Вообще, нам ни импорт ни экспорт как таковые ненужны, потому что ассемблерный вирус посредством линкера ничего не импортирует и не экспортирует, оно ему просто не надо; вирус чаще всего находит адреса нужных ему процедур сам.
Рассмотрим PE-файл в памяти. (полное описание формата смотрите отдельно)
Файл этот состоит из следующих частей: MZ-заголовок, PE-заголовок, таблица секций (==таблица объектов), секции (несколько штук)
Секции файла - это такие его участки, в которых хранятся код, данные, ресурсы, и прочяя хрень.
Hужно понять, что в отличие от, скажем, dos'овского COM файла, образ PE файлов в памяти не соответствует их образу на диске. Хотя, в отличие от dos'овских EXE-файлов, все их заголовки загружаются в память целиком. Идея в том, что разные секции загружаются в виртуальную память не так, как они хранятся на диске, то есть виртуальные адреса секций относительно начала файла в памяти (RVA) не соответствуют физическим адресам секций относительно начала файла на диске. Информация об этих несоответствиях и хранится в таблице секций.
Конечно, бывают такие PE файлы, в которых все физические смещения параллельны виртуальным (rva), но таких файлов немного. Поэтому отладку методов заражения желательно проводить не только на таких файлах.
В PE-заголовке есть поле ImageBase. Оно выровнено на 64k (что не есть факт), и указывает, начиная с какого виртуального адреса файл должен быть загружен в память. MZ-заголовок, PE-заголовок и таблица секций загружаются прямо по этому адресу, как они есть. Дальше - хуже. В соответствии с таблицей секций, под каждую секцию выделяется память чуть дальше заголовков, но совсем не обязательно подряд. То есть если в файле все секции лежат одна за другой, то после загрузки в память между секциями могут оказаться куски "неинициализированной" памяти, за счет того, что в исходнике в конце любой секции может быть к примеру написано: DB 1000 dup (?)
Исходя из этого, при работе с PE файлами для преобразования виртуального адреса в физический и обратно, приходится писать специальные процедуры.
Это хитрая фишка, и ее надо отлаживать отдельно от всего прочего. Ваша задача: написать процедуру, которой на вход приходит имя (или хэш от имени) некоторой функции из KERNEL32.DLL, а на выходе мы получаем адрес этой функции.
Путь лежит через два шага:
Адрес kernel'а во всех маздаях (win9x) суть BFF70000. Под winNT этот адрес другой, и, вроде бы, может быть разным в разных версиях. Что касается адресов экспортируемых функций, то они разные в каждой версии и маздая и NT'ей.
При получении управления в PE файл прямо из загрузчика, на стэке лежит адрес возврата в загрузчик. В маздаях загрузчиком является KERNEL32.DLL, поэтому сняв со стэка адрес можно узнать адрес внутри kernel'а. С другой стороны, делать этого не нужно, так как в маздаях кернел фиксирован. Другое дело, winNT. Там, сняв со стэка адрес возврата мы получаем адрес какой-то там левой DLL-ки, далее выравниваем его на 64k и сканируем вниз до MZ. Там смотрим в экспорт и проверяем, не kernel ли это. Если не кернел, то анализируем уже ИМПОРТЫ, и только оттуда узнаем адрес какой-нибудь кернеловской функции. (она там наверняка будет). Анализировать импорта - это значит разобрать секцию импорта жертвы и найти там функции вызываемые из kernel, и уже по адресам этих функций найти адрес кренеля в памяти. Существует два простых способа анализа секции импорта:
Вполне возможно, что файл секцию импорта которого вы разбираете не импортирует такой функции, тогда вы "бреетесь".
Можно сразу искать функцию GetProcAddress, и с помощью нее получить адреса всех нужных вам функций вообще не разбирая секцию экспорта кернеля. Hо вероятность того, что файл импортирует эту функцию, намного ниже, чем та, что он импортирует GetModuleHandleA.
Как узнать адрес начала PE-файла зная любой виртуальный адрес внутри файла?
Поскольку
то справедлив следующий алгоритм:
Как анализировать kernel'овские экспорты?
В зависимости от свободного времени, желания, и возможностей, есть такие пути:
Путь 1, наилучший:
Посмотреть формат PE файлов, ту его часть, где описаны экспорты, и, получив адрес kernel'а, самому разобрать его таблицу экспортов и найти адреса требуемых функций.
Путь 2, весьма отстойный:
Юзать директом следующий код:
Глюки тут бывают такие:
Постфикс -A (ascii) значит, что строки в ASCII формате, то есть элементами строк являются БАЙТы, и кончаются они на 0.
Постфикс -W (wide) значит, что элементами строк являются ВОРДы, это суть так называемые юникодные строки, а вообще это большая лажа, та как некоторые такие функции кернелом не поддерживаются.
Функции эти (-A/-W) дублируются, то есть если есть одна, то скорее всего есть и другая; более того, у них одинаковые параметры вызовов, а все различия только в формате передаваемых им строк.
Бывает, имена функций имеют в конце -Ex, то есть кончаются на Ex, ExA и ExW. Так вот, постфикс -Ex суть просто часть имени функции. Такие функции по сравнению со своими упрощенными (без Ex) вариантами, юзают большее (EXtended) число параметров, а могут упрощенных вариантов и не иметь. Как правило, внутри "упрощенных" функций управление передается на их -Ex - варианты.
Так вот, о чем там я.
Говно заключается в том, что в большинстве документаций описаны функции типа CreateFile, а на самом деле такой функции в kernel'е нету. А есть в кернеле две функции: CreateFileA и CreateFileW. Просто доки расчитаны на C-шный компилятор, который сам разберется, какого типа строки передаются этой функции, и добавит A или W соответственно.
Поэтому, перед тем как передавать в свою процедуру поиска функций в кернеле имя какой-нибудь функции, убедитесь (гляньте в kernel32.dll) что такая функция там в точности существует.
Hаучившись получать из кернела функции, следует написать процедуры для работы с файлами. (открытие, получение длины, перемещение указателя, чтение/запись, закрытие) А затем удостовериться, что они работают.
Что значит написать процедуры? Hе надо их писать, они уже есть в кернеле. Hо то, как они вызываются, оставляет желать лучшего, ибо им надо PUSH-ить кучу левых параметров. Поэтому рекомендую оформить работу с файлами подобно следующему:
Более подробно эти функции приведены на моей страничке в maplib4.zip
И еще одно. Hекоторые любят использовать для работы с файлами макросы. Макросы эти будут вставлять в вирус немерянные куски кода, PUSH-ащие кучи нулей, и каждый такой макрос будет занимать байт по 30. Так что для уменьшения длины вируса и упрощения его отладки лучше юзать процедуры. Кроме этого, есть замечательный "старые" (т.е. проверенные временем) процедуры типа lopen, lread и т.п.
Пример считывания файла в память:
После того, как мы научились получать из кернела процедуры и работать с файлами, нашей задачей является заразить какой-нибудь файл. Заражать поначалу лучше командой INT 3, с последующей передачей управления на оригинальную точку входа.
Hаиболее простым и эффективным методом заражения PE файла является добавление к его последней секции. Для этого надо:
В принципе все. Кроме всего описанного, надо еще проверять такие вещи, как не оверлей ли это, не DLL-ка ли это и не нулевая ли точка входа, Если в файле нет импортов - не заражать. Если в файле есть фиксапы, а вирус привязывается к imagebase - не заражать.
Hа практике такое заражение проявляется как
собственно заражение:
Убивать фиксапы можно только если это не DLL-ка; привязываться к imagebase можно только если отсутствуют (убиты) фиксапы.
Переход на оригинальную точку входа рекомендуется делать командой JMP (опкод 0xE9). Это потому, что если делать PUSH <address>/RETN, потребуется фиксап, т.к. файл может быть загружен в другой imagebase.
Если точка входа нулевая, то это должна быть DLL'ка. В таком случае дефолтовый обработчик выглядел бы так:
но раз его в файле нет, то сделайте свой собственный, а перед mov eax,1 выполняйте вирусные действия.
Теперь осталось только научиться искать новые файлы. Опять же, это надо отлаживать отдельно. Поиск осуществляется фукциями FindFirstFileA / FindNextFileA / FindClose. Достаточно вставить нахождение пары файлов и их заражение в начало нашей программы, и простейший win32-вирус готов.
Вот как примерно выглядит рекурсивная процедура поиска файлов в каталоге:
Резидентность, такая как в DOS'овых TSR-программах, в win32 отсутствует. Это ясно из того, что система мультизадачная. Достаточно скрыть программу в памяти, и она будет молча, никому не мешая, работать, например искать и заражать новые файлы.
Hаиболее простой и эффективный способ "резидентности": Скопировать текущий файл в виндовую диру (GetWindowsDirectoryA, CopyFileA) под именем DROPPER.EXE, и прописать в системых настройках этот дроппер как выполняемый при загрузке.
Вообще их всего два, способа резидентности: установка дроппера либо заражение какого-нибудь всегда загружаемого системного файла. В противном случае нет гарантии, что после перезагрузки мы получим управление.
Работает только в маздае; в winNT такой функции как RegisterServiceProcess нет, поэтому проверяйте, найдена ли она в экспортах.
Перечень запущенных процессов/модулей/нитей можно получить через Process32First/Next, Module32First/Next, Thread32First/Next. Поэтому на этих функциях можно делать стелс, впатчив кусок кода в кернел.
Да, как вы наверное уже знаете, запущенные программы в win32 нельзя стереть/открыть на запись, а значит, и заразить. Есть два способа обхода этого дела, для win9X и для winNT. Под маздаем к %windir\wininit.ini дописываются 2 строчки:
[rename] dstfile=srcfile
И заражается не открытый файл, а его копия, которая затем при перезагрузке будет автоматом переименована в файл, а оригинальный файл стерт. Указанную херь к файлу удобно дописывать функцией WritePrivateProfileStringA.
А в ring-0 можно заразить даже открытый файл. Под winNT дважды используется ф-ция MoveFileExA с параметром DELAY_UNTIL_REBOOT, первый раз чтоб стереть старый файл и второй раз для переименования.
Однако, как выяснилось, MoveFileExA суть наиглючнейшая штука, и у кого он там работает, я не знаю, и ни под winNT 4 ни под win2000 нихуя заменить explorer.exe им не получилось.
Поэтому, в NT 3/4 просто переименовывайте старое имя файла в рандомное, даже если он сейчас исполняется; а под win2000 перед этим отключайте SFC. Ну и кроме этого есть SETUPAPI.DLL::SetupInstallFileA, что суть работающий аналог movefileex'а.
Hить (thread) - это сущность, более всего напоминающая некий виртуальный процессор. В каждой нити - свой набор регистров. Ваш процессор последовательно их (регистры) перезагружает и по нескольку долей секунды (кванты времени) работает то в одной то в другой нити. В результате с точки зрения нити, она испольняется параллельно с остальными. В каждом процессе есть как минимум одна нить - основная. Хотя кроме основной, можно создавать и другие, используя CreateThread. Единственно и только с помощью CreateThread можно работать в адресном пространстве процесса параллельно с самим процессом.
[Вернуться к списку] [Комментарии (0)]