Защита программ от отладки на платформе Windows (ч. 2)


Макаров Тихон

Теперь перейдем ближе к ядру windows! Существуют, как известно, привилегии. Так вот, у ядра и "модулей", к нему подключенных, как известно, самые большие права. Согласитесь, что программу ядра windows отладить может быть куда сложнее, нежели пользовательскую программу.

Итак, нам понадобится компилятор для ассемблера и Windows Drivers Development Kit (пакет, помогающий создавать драйверы под Windows). Мы напишем небольшой модуль, который будет следить, к примеру, за обращениями к файловой системе*.

В нашу программу (пусть она будет на Delphi) допишем:

HANDLE h = CreateFile ('\\.\antisice.vxd', 0, 0, 0, 0, \	// вызываем драйвер
FILE_FLAG_DELETE_ON_CLOSE, 0);

Разберем для начала вкратце устройство windows. Ядром Windows является VMM - virtual machine memory. В переводе это менеджер виртуальных машин: каждая машина получает свое адресное пространство, ресурсы памяти и процессора. VMM создает одну виртуальную машину для всех приложений Windows и по одной на каждую задачу Dos. Приложения могут иметь разные права на выполнение (не путать с приоритетом, который можно изменить в диспетчере задач). Ring 3 - самый низкий уровень, им обладают все пользовательские приложения. Ring 0 - самый высокий уровень, на котором работает ядро и драйверы. Драйвер имеет обозначение VxD.

Итак, рассмотрим, наконец, написание VxD файла. Сначала включаем системные библиотеки:

.686p  // Protected mode in 686 line of processors
include vmm.inc // подключаемые библиотеки
include ifs.inc
include vwin32.inc
include ifsmgr.inc
include shell.inc
DECLARE_VIRTUAL_DEVICE  AntiSICE,6,0, AntiSICE_Control,\  	// мы описываем
	// наш драйвер: Antisice - имя. 6,0 - версия OS, под которой может
UNDEFINED_DEVICE_ID, UNDEFINED_INIT_ORDER		// выполниться
	// наша программа, Antisice - название главной функции драйвера,
	// всегда получающееся по принципу: Имя_драйвера + _Control. 
	// Undefined_device_id указывает, что у нашего драйвера не определен
	// id VxD, иначе надо его указать (предварительно зарегистрировав в
	// Microsoft). UNDEFINED_INIT_ORDER - порядок запуска его 
	// относительно других драйверов (чем ниже, тем главнее), у нас он
	// подгружаем вместе с программой, поэтому указываем его неопределенным

Кстати, выключить этот модуль можно с помощью CloseHandle (х), где х - наш handle. Теперь опишем сегмент данных:

VxD_data_seg	// в имя можно так же добавить locked, что запретит при 
		// нехватке памяти выгружать данные из памяти на диск
{данные}
VxD_data_seg_ends

Далее идет сегмент кода, в котором надо указать обработку сообщений (всем известно, что программе посылаются какие-то сообщения ядром, после чего она может, например, начать свою работу или закрыться):

VxD_LOCKED_CODE_SEG
{данные}
Begin_control_dispatch AntiSICE // функции (в DDK) обработки сообщений
// процессу, соответственно
Control_Dispatch W32_DeviceIoControl, OnDeviceIoControl // работа с
// пользовательскими программами
Control_Dispatch SYS_DYNAMIC_DEVICE_INIT, OnStartup // запуск
Control_Dispatch SYS_DYNAMIC_DEVICE_EXIT, OnClose // выход (можно
// и не ставить, чтобы она не закрывалась)
End_control_dispatch AntiSICE // более подробные данные о сообщениях можно
// найти в документации DDK в разделе control_Dispatch или в vmm.inc
{данные}
BeginProc Onstartup
{Что-нить для инициализации, например:}
VMMCall _HeapAllocate,
mov [Buffer], eax // получаем свои ID (в windows это handle
mov [Bufoffiles], eax // инициализируем буферы для внесения в них
// имен запуск. файлов
add eax, 200h // запускаем функция слежения
mov [Bufoffiles], eax
VxDCall IFSMgr_We_Will_Hook_You,
mov [pOldFSHook], eax
VMMCall Get_Sys_VM_Handle
xor eax, eax
mov ecx, OFFSET32 HelloTxt
xor edi, edi
xor esi, esi
VxDCall SHELL_Message
xor eax,eax
ret
EndProc OnSysInit
BeginProc OnSysExit // вызываем функции отмены слежения и
// выгружаем данные из памяти
VxDCall IFSMgr_Do_not_hook_any_more,
VMMCall _HeapFree,
xor eax,eax
ret
EndProc OnSysExit
BeginProc OnDeviceIoControl // стандартная инициализация
cmp [esi + DIOCParams.dwIoControlCode],DIOC_Open
if [esi + DIOCParams.dwIoControlCode] not eq DIOC_Open // извините,
// забыл условие сравнения...
mov eax,1
stc
ret
else
xor eax,eax
ret
endif
EndProc OnDeviceIoControl

Теперь подробнее рассмотрим функцию работы драйвера с пользовательскими программами: когда пользовательское приложение пытается запустить драйвер, ему передается сообщение W32_DeviceIoControl, при котором в регистре ESI лежит структура DIOCParams. Если драйвер нужно запустить, то в записи dwIoControlCode данной структуры будет лежать DIOC_open, в ответ на который драйвер возвращает 0 в регистре EAX (иначе Windows решит, что наш модуль не работоспособен), а иначе мы должны вернуть любое другое число, например 1.

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

Теперь наша программа уже может запускаться и закрываться! Это уже солидный проект! :). Теперь напишем сервис, перехватывающий обращения к файловой системе. За это отвечает IFSMgr_InstallFileSystemApiHook. Реализуется ее вызов, например, так:

VxDCall IFSMgr_InstallFileSystemApiHook, // FileHook - ни 
// что иное, как наша функция обработки, мы передаем ее адрес.

а убирать ловушку можно так:

VxDCall IFSMgr_RemoveFileSystemApiHook

IFSMgr_InstallFileSystemApiHook возвращает адрес переменной, содержащей адрес предыдущей ловушки. Что же может сделать наша ловушка? Одно из нижеследующих действий:

1) Проигнорировать запрос и передать его предыдущему обработчику.
2) Обработать запрос и вернуть управление менеджеру IFS.
3) Изменить запрос, после чего вернуть управление IFS.
4) Передать запрос предыдущему обработчику, а когда тот выполнит положенные ему действия, вернуть управление IFS.

Принцип работы для других ловушек такой же. Каким именно вариантом пользоваться - решайте сами.

Так, теперь нам нужно написать саму процедуру обработки обращений к файловой системе. Для этого надо разобраться со значениями, с которыми работает FileSystemApiHook:

1) Адрес функции, что ее вызвала.
2) Номер функции; определяет тип операции, что она должна сделать.
3) Раздел, на котором находится наш файл.
4) Тип ресурса (удаленный диск, hdd, съемный).
5) Кодовая страница, в которой передана строка, содержащая имя требуемого ресурса.
6) Указатель на структуру IOREQ.

Значит наша функция будет выглядеть так:

BeginProc FileHook // @@ означает "локальная переменная"
push ebp
mov ebp,esp
@@adress equ ebp + 08h
@@operation_type equ ebp + 0Ch
@@Drive equ ebp + 10h
@@res_type equ ebp + 14h
@@code_page equ ebp + 18h
@@structure equ ebp + 1Ch

Теперь подумаем, что именно должна ловить наша программа. Пусть нас будут интересовать все изменяемые файлы, причем нас будет интересовать, что же именно было изменено. Тогда ядро передаст нашему драйверу управление, когда будет произведено обращение к файлу и мы должны будем определить, является ли это запрос на открытие файла:

cmp dword ptr [@@operation_type], IFSFN_OPEN
jz OpenFile

Иначе вернуть ядру (точнее настоящему обработчику) право на обработку запроса:

mov eax, [pOldFSHook]
pop ebp
jmp dword ptr [eax]

После чего наша функция должна определить, было ли это изменение файла:

mov eax, [@@structure] // вообще подробную информацию можно получить с
// документации DDK раздела IOREQ
cmp word ptr [eax + 018h], ACTION_OPENEXISTING // но в данном случае мы
// смотрим поле, в котором передается, новый ли это будет файл или нет
jnz Some // из структуры IOREQ, а его смешение - 018h

Пусть мы теперь находимся в ring 0. Нам желательно выключить все остальные драйверы, т.к. иначе у них будут права на закрытие нашего драйвера (кстати, чтобы его нельзя было просто отключить от программы, желательно поставить в него часть программы, без которой она откажется работать).

Как выключить другие драйверы? Для начла их нужно найти. Легко, скажете вы: с помощью VMMcall Hook_Device_Service мы найдем вражеский драйвер и закроем его. Нет, так нельзя. Ведь если нас заметит этот самый драйвер, неизвестно кто кого закроет первым. А заметить он может очень просто: достаточно следить за вызовом VxD Call функций. Поэтому мы должны написать свою функцию такого типа. Сначала возьмем список VxD, просмотрим до нужного нам, после чего возьмем таблицу OFFset и изменим в ней handler, а точнее его адрес. Остается один вопрос: как найти эти VxD-структуры? Можно с помощью VMMcall Get_DDB, но это тоже VxD call. Поэтому мы должны написать свою функцию, ищущую эти структуры.

Итак, как это сделать? Просто (почти): задаем начальный адрес поиска 0C0001000h, ищем там call (в hex - 15FF), проверяем, что мы не вышли за структуру VMM, сравниваем с DDB_Reserved1, и если сходится - мы нашли драйвер, теперь надо определить его id - сравнить DDB_Req_Device_Number. Теперь сохраняем адрес нашей DDB_Service_Table_Ptr. Теперь я напишу код z0mbie, опираясь на Get_DDB:

mov eax, VMM
mov edi,0
; код Get_DDB
jecxz __exit
mov edx, [ecx].DDB_Service_Tabler_Ptr
lea eax,new
xchg eax, [edx+4*ххх]
mov old, eax

Теперь у нас есть его адрес, но этого мало. Нам надо его выключить. Просто выключить нам его не позволят - он будет жаловаться. Можно только его самого отладить. Для этого, к сожалению, необходимо знать устройство самого драйвера.

Мы должны подменить его обращения на ложные. К сожалению, я сам плохо знаком с устройством FrogIce, поэтому Вам самим придется его поотлаживать. Главное помнить, что строки типа CD 20 001B010A VxDcall VWIN32_Debug надо менять например на nop.

Вот, собственно и все. Теперь нам остается только самим открыть файл. Если кого-то заинтересует, я могу отослать оставшиеся коды (про открытие файла). Пишите по адресу makarov [at] irs [dot] ru.

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

Примечание:
! - работает не только для отладчиков того типа, в котором он описан.
* - я выбираю именно файловую систему так как написание драйвера для нее будет достаточно кратким, а работу с памятью организовать намного сложнее (то есть дольше, а не сложнее), к тому же если Вы поймете данный принцип, то обращения к памяти и другим устройства.

Защита программ от отладки на платформе Windows (ч. 1)