Блог → Методы защиты от трассировки по заданному прерыванию

Итак, чтобы не откладывать в долгий ящик, я сразу же перехожу к рассмотрению следующего материала. Сегодня бы подробно поговорим о методах защиты от трассировки программ по заданному прерыванию. Вообще, трассировка программы по заданному прерыванию заключается в следующем: программа пользователя (оригинальная или стандартная типа QA или UTIL) адресует вектор заданного прерывания на собственную функцию, которая до или после выполнения прерывания останавливает процесс выполнения программы и позволяет пользователю ознакомиться с интересующей его информацией, например, содержанием регистров, таблицой базы диска и т.д., или выполнить какие-либо другие действия.

Существует также термин "трассировка прерывания". В него вкладывается несколько другой смысл. Трассировка прерывания есть дисассемблирование последовательности команд, начиная от команды, находящейся по адресу, указанному для исследуемого прерывания в таблице прерываний, до команды IRET. Вполне очевидно, что при трассировке дисковых прерываний можно, например, проследить процесс чтения некопируемой метки (если, конечно, этот процесс выполняется с использованием прерываний), а затем дублировать метку или моделировать процесс чтения.

Перечислю методы защиты от трассировки по дисковым прерываниям при работе с гибкими дисками. При работе с гибким диском можно использовать два прерывания:
- int 13h;
- int 40h.

Прерывание int 40h в части, касающейся гибких дисков, полностью аналогично прерыванию int 13h. Для защиты от трассировки по этим прерываниям можно использовать следующие приемы:
- работа с ключевой меткой путем прямого программирования контроллера гибкого диска;
- определение одного из неиспользуемых прерываний для работы с диском;
- прямой вызов соответствующих функций BIOS'a;
- определение факта переопределения адреса прерывания на другую программу и невыполнение в этом случае дисковых операций.

Относительно первого метода можно заметить, что некоторые программные средства позволяют отслеживать операции с портами контроллера. Поэтому, защита таким методом также полностью не решает проблемы. Кроме того, этот метод вызывает затруднения при работе с нестандартными контроллерами и тем самым накладывает ограничения на класс совместимых компьютеров. Далее, затруднительно синхронизировать обмен с портами на низкоскоростных и высокоскоростных компьютеров с работой процессора, что также наложит ограничения на класс совместимости.

Как установлено исследованием большого количества машин, адрес, на который передается управление при вызове прерывания int 40h, в BIOS'e является постоянным и составляет F000:EC59. При этом по данному адресу в BIOS'e находятся либо непосредственно команды, выполняющие дисковые операции с гибкими дисками, либо безусловный переход (JMP) на аналогичные функции прерывания int 13h в том же сегменте F000h.

Таким образом, достаточно скопировать соответствующие сегмент и смещение в один из нескольких неиспользуемых векторов прерываний, например 62h, а затем вместо прерывания int 40h вызывать прерывание int 62h. После окончания работы, естественно, необходимо восстановить прежнее значение вектора 62h, что скроет факт использования данного прерывания и заодно предотвратит неприятности в случае использования данного прерывания другой программой.

Проиллюстрирую сказанное выше, примером программного кода. Фрагмент программы будет выглядеть следующим образом:

mov ax,0
mov es,ax
; адрес вектора прерывания 62h - 0000:0188h
mov bx,188h
mov ax,es:[bx]

; сохранение предыдущего значения смещения
mov s_62_o,ax
mov es:[bx], 0EC59h

; засылка нового смещения
add bx,2
mov ax,es: [bx]

; сохранение предыдущего значения сегмента
mov s_62_s,ax
mov es : [bx] , OFOOOh ; засылка нового сегмента
mov ax,00201h
mov bx,offset bf
mov cx,OOOOlh
mov dx,seg bf
mov es, dx
mov dx, 0

; установлены параметры для чтения сектора с дискеты.
; es:bx - адрес буфера чтения. Считывается первый сектор
; нулевой дорожки нулевой головки дисковода А:
int 62h

; операции через сх, чтобы не испортить код возврата в ах
mov cx, 0
mov es, cx
mov bx, 188h ; адрес вектора прерывания 62h
mov cx,s_62_o ; восстановление предыдущего
mov es: [bx] ,cx ; значения смещения
add bx, 2
mov cx, s_62_s ; восстановление предыдущего
mov es: [bx] ,cx ; значения сегмента
cmp ah, 0
je metO

; завершение программы в случае неверного чтения
mov ax,04C00h
int 21h
met0: …

bf db 512 dup (0)
s_62_o dw 0
s_62_s dw 0


В данном примере необходимо отметить следующее. Если непосредственно перед чтением с дискеты защелка дисковода была открыта для того, чтобы вставить дискету, а программа, читающая с дискеты, находится на другом дисководе, то при первом чтении будет всегда возвращен код ошибки 6 в регистре АН - дискета сменена. Поэтому, чтение сектора необходимо произвести 2 раза. Аналогично можно выполнить и прямой вызов функции в BIOS'e. Для этого необходимо:
- подготовить регистры;
- подготовить таблицу базы диска (если она должна быть изменена);
- дать команду PUSHF, поскольку выход из прерывания будет произведен по команде IRET;
- вызвать подпрограмму командой CALL;
- возвратить старые значения таблице базы диска.

И снова, пример кода. Чтение загрузочного сектора дискеты в дисководе A: через прямой вызов BIOS'а.

.MODEL TINY
.CODE
org 0h

s:
push cs
pop ds
mov ax,0201h
mov bx,offset bf
mov cx,000lh
mov dx,seg bf
mov es, dx
mov dx, 0

; установлены параметры для чтения сектора с дискеты;
; es:dx - адрес буфера чтения. Считывается первый сектор
; нулевой дорожки нулевой головки дисковода А:
pushf
call dword ptr [ourfun]

; печать считанной информации
mov dx,offset bf
mov ah,09h
int 21h

; завершение программы
mov ax,04C00h
int 21h
bf db 512 dup (0)

; Признак окончания печати
db "$"

; Абсолютный адрес в виде сегмент:смещение в формате двойного слова
ourfun dd 0F000EC59h
end s


Следующий способ - запрещение операций работы с диском при переопределении прерывания на программу пользователя. Надо отметить, что такой способ хорош только для программ, использующих прерывание int 40h, поскольку int 13h обычно переопределено на IBMIO.SYS. Перед выполнением прерывания int 40h необходимо убедиться, что в таблице прерываний в поле сегмента (адрес 0000:0102h) находится число F000h, которое указывает на сегмент BIOS'a.

.MODEL TINY
. CODE
org 0

s:
; ds=cs
push cs
pop ds

; установить адрес 0000:0102h (0000:0258)
mov ax,0
mov es,ax
mov bx,258

; сравнение с f000h
cmp es:[bx],0f000h
je ml

; вывод сообщения mesl, при неравенстве прерывание перехвачено
mov ah,09h
mov dx,offset mesl
int 21h

; завершение программы
mov ax,4c0 0h
int 21h

; вывод сообщения mes2, при равенстве прерывание указывает на BIOS
ml: mov ah,09h
mov dx,offset mes2
int 21h

; завершение программы
mov ax,4c00h
int 21h
mesl db "Прерывание перехвачено.$"
mes2 db "Оригинальный вектор прерывания.$"
end s


Для защиты от трассировки по прерыванию при работе с жесткими дисками применяется несколько методов. При операциях с жестким диском, как правило, используется прерывание int 13h. Для предотвращения трассировки программы по заданному прерыванию (в данном случае int 13h) можно также использовать указанные выше методы, а именно:
- переопределение исходного прерывания в BIOS'e на неиспользуемый вектор прерывания;
- прямой вызов функций BIOS'a.

В обоих случаях возникает задача восстановления адреса прерывания int 13h в BIOS'e поскольку, как правило, данное прерывание перехвачено, как минимум IBMIO.SYS или, что гораздо хуже, программой "взломщика". Это можно сделать используя функцию 13h прерывания 2Fh. Ниже я привожу пример кода, предназначенного для восстановления адреса int 13h в BIOS'e.

#include <dos.h>
main()
i unsigned int s_es,s_ds,s_bx,s_dx;
asm(
mov ah,013h
int 2Fh
mov ax, es
mov s_es,ax
mov ax, ds
mov s_ds,ax
mov s_bx,bx
mov s_dx,dx
}
asm{
mov ax,s_es
mov es, ax
mov ax,s_ds
mov ds, ax
mov bx,s_bx
mov dx,s_dx
mov ah,013h
int 2Fh
}
printf ("Адрес обработчика int 13h в BIOS %x:%x", s_ds, s_dx);
return(0);
}


Вероятно, этот пример требует дополнительных пояснений, так что я остановлюсь на нём подробнее. Особенностью данной функции (13h) прерывания 2Fh является то, что после выполнения его в регистрах DS и DX возвращаются адреса обработчика прерывания int 13h в BIOS'e, а значения регистров, заданные перед выполнением прерывания, занимают их место. Поэтому, дальше в таком состоянии работать нельзя, поскольку любая дисковая операция приведет к непредсказуемым последствиям. Следовательно, прерывание после получения результата надо вызвать еще раз, установив в регистрах DS, DX, ES, ВХ полученные после первого выполнения прерывания 2Fh результаты. Данный способ, зачастую, упоминается, как один из методов, используемых вирусами.

Идём дальше, и поговорим о защите от трассировки прерывания DOS (int 21 h). Трассировка по данному прерыванию также достаточно опасна. В основном таким образом могут быть выделены следующие действия программ:
- определение факта замены векторов прерываний на собственные функции (в частности для защиты от отладчиков);
- файловые операции, связанные со считываниями различных счетчиков или паролей;
- файловые операции, связанные со считыванием заголовков исполняемых файлов.

Третье действие опасно по причине того, что при остановке в данном месте программа в оперативной памяти, как правило, уже готова к исполнению, а, следовательно, может быть считана в файл в незащищенном виде. Кроме того, в самих защищаемых программах, полученных при помощи распространенных компиляторов, в самом начале идёт последовательность прерываний 21h. Этот факт позволяет в очень многих случаях при остановке на одном из этих прерываний считать код программы из памяти в незащищенном виде, поскольку к этому моменту антиотладочные средства обычно завершают свою работу.

Для защиты от трассировки программы по int 21h можно предложить три основных способа по аналогии с теми, которые предложены выше:
- определение факта перехвата прерывания и запрещение работы в этом случае;
- применение функции косвенного вызова DOS (функция 5Dh прерывания 21h);
- прямой вызов функции (путем прямой адресации к расположенным в ОП функциям или через вызов СР/М, адрес перехода в int 30h - 31h).

Для операций с файлами (для версий DOS 3.0 и выше) можно предложить еще один способ борьбы с трассировкой int 21h:
- работа с указателями файлов (handle) напрямую в таблице открытых файлов (open-file table).

; Компиляция в .СОМ-файл
.MODEL TINY
.CODE
org l00h
s:

; DS = CS
push cs
pop ds

; указание на функцию косвенного вызова
mov ax,5D00h

; в DX — смещение таблицы регистров
; в DS — сегмент таблицы регистров
mov dx,offset r_ax

; загрузка в поле регистра DS в таблице сегмента метки
; mes - выводимого сообщения
mov bx,ds
mov r_ds,bx

; загрузка в поле регистра ВХ в таблице смещения метки
; mes - выводимого сообщения
mov r_dx,offset mes
int 21h
mov ax,4C00h
int 21h
mes db "Вывод сообщения через косвенный вызов DOS$"
r_ax dw 0900h ; AX AH=09h — вывод на экран
r_bx dw 0 ; BX
r_cx dw 0 ; CX
r_dx dw 0 ; DX
r_s i dw 0 ; SI
r_di dw 0 ; DI
r_ds dw 0 ; DS
r_es dw 0 ; ES
end s


В том случае, если все вызовы int 21h в вашей программе будут оформлены в виде косвенного вызова, работа по анализу программы во много раз замедлится, поскольку в каждом случае нужно будет выяснять параметры и тип вызываемой функции, просматривая структуры со значениями регистров. По смещению 34h и 36h относительно начала PSP (смещение и сегмент сооответственно) находится ссылка на таблицу открытых файлов (open-file table). Структура таблицы такова - она состоит из последовательности байт, число которых определено в поле со смещением 32h от начала PSP, каждый байт с номером i соответствует i-му указателю (handle) файла, а содержание байта указывает на расположение реальных параметров файла в системной таблице файлов SFT - System File Table.

Значение FFh указывает на то, что i-й handle указывает на закрытый файл. Пусть открыт файл с именем NAME и ему соответствует handle j, тогда скопируем содержание j-го байта в таблице открытых файлов в m-ый байт. Операции с указателем файла с номером m будут соответствовать реально операции с файлом NAME. Тогда для маскировки файловых операций нужно:
- определить текущий PSP;
- определить параметры таблицы открытых файлов;
- открыть большое число файлов;
- переместить значения в таблице на другие указатели;
- закрыть прежние указатели;
- работать с файлами через вновь созданные указатели файлов.

Не представляет труда и прямой вызов нужных функций через СР/М. Но при этом надо помнить, что:
- через СР/М можно вызвать не все функции DOS;
- номер функции указывается в регистре CL;
- перед вызовом стек формируется в обратном порядке.