После подключения DLL к потоку происходит инициализация всех переменных (каждый процесс, к которому подключается DLL, имеет копии всех глобальных и статических переменных, описанных в ней). Из глобальных переменных у нас есть 6 экземпляров класса CAPIHook (для GetDriveTypeA в DT2Lib.cpp и для LoadLibraryA, LoadLibraryW, LoadLibraryExA, LoadLibraryExW и GetProcAddress – в APIHook.cpp). Таким образом, при подключении DLL происходит шестикратный вызов конструктора класса CAPIHook, перехватывающего вышеперечисленные функции в текущем (то есть в том, к которому только что произошло подключение) процессе.
При завершении процесса внедрённая DLL отключается. При этом происходит вызов деструктора CAPIHook для всех экземпляров класса.
Данная функция теперь будет вызываться каждый раз, когда из данного модуля будет происходить обращение к GetDriveTypeA.
int WINAPI Hook_GetDriveTypeA(PCSTR lpRootPathName){ //Вызовеморигинальнуюфункцию int Result = ((PGetDriveTypeA)(PROC) g_GetDriveTypeA)(lpRootPathName); if (Result > DRIVE_NO_ROOT_DIR) { int Drive = toupper(*lpRootPathName); if (Drive >= 'A' && Drive<='Z'){ Drive -= 'A'; //Индекс в массиве Drives //Если этот диск переопределен, вернём значение из массиваif (Drives[Drive] < 0xFF) Result = Drives[Drive];} } return Result;} |
Функция Hook_GetDriveTypeA сначала вызывает оригинальную GetDriveTypeA. Затем, если возвращаемое значение больше DRIVE_NO_ROOT_DIR (то есть функции был передан корректный аргумент, и она выполнилась без ошибок), то проверяется, переопределен ли диск, тип которого запрашивается. Информация о значениях перехватываемых функций в данном случае хранится в реализованном мной массиве BYTE Drives[26], что позволяет реализовать перехват 26 дисков, от A: до Z:. В этом массиве хранятся значения, возвращаемые функцией GetDriveTypeA для каждого из дисков. Итак, если значение элемента массива, соответствующего аргументу GetDriveTypeA равно 0xFF, то значение возвращается без изменений, в противном случае возвращается значение из массива. Запись значений в этот массив реализуется в DriveType2.cpp.
СОВЕТЕсли вы хотите, чтобы эта программа полноценно работала в WinNT, следует также перехватить функцию GetDriveTypeW. |
Ещё одна реализация данного метода описана в статье «Перехват API-функций в Windows NT/2000/XP», автор Тихомиров В. А., публиковалась в RSDN Magazine #1 (будьте осторожны, там та же ошибка, что и у Джеффри Рихтера).
ПРИМЕЧАНИЕУ этого метода есть ещё один существенный недостаток: некоторые коммерческие программы (например, популярный файловый менеджер Total Commander, упакованный ASPack) используют различные системы защиты (ASProtect, VBox и т. д.), шифрующие таблицу импорта защищаемого приложения. С такими программами этот метод не работает. |
Глобальный перехват может быть реализован и с помощью Detours (только в WinNT). А так как методов внедрения DLL известно несколько, то различных вариантов реализации глобального перехвата можно предложить довольно много.
Глобальный перехват методом подмены кода в DLL
Данный метод можно реализовать двумя способами: непосредственной правкой кода DLL, в которой расположена целевая функция, или подменой этой DLL другой, экспортирующей тот же набор функций. Второй способ известен под названием «Подмена с использованием оберток (wrappers)».
Первый способ позволяет реализовывать только сравнительно небольшие по размеру функции-перехватчики, так как код необходимо внедрять в свободные участки DLL – в основном в межсекционное пространство. Другой недостаток – код необходимо писать на ассемблере. Общая идеология работы этого метода та же, что и в Detours. В код целевой функции внедряется команда jmp к функции-перехватчику. Байты, скопированные «из-под» jmp’а, перемещаются в перехватчик (так как перехватчик всё равно пишется на ассемблере, в этом случае его проще сразу совместить с функцией-трамплином). Вот пример реализации этого метода.
В каталоге DriveType0 находится файл kernel32.dll, в котором я сделал следующие исправления (при помощи hiew32.exe):
По адресу 4E02 – локальный адрес .0BFF74E02 (это конец функции GetDriveTypeA) я поместил команду jmp .0BFF71080 – на первое попавшееся свободное место (в исполняемых файлах всегда много свободного места – обычно в концах секций).
По адресу .0BFF71080 (глобальный адрес 1080) я поместил следующий код:
.BFF71080: 3C03 cmp al,003 ;Возвращаем DRIVE_FIXED ?.BFF71082: 750E jne .0BFF71092.BFF71084: B402 mov ah,002 ;Да..BFF71086: CD16 int 016 ;/Проверим состояние ScrollLock.BFF71088: 2410 and al,010 ;.BFF7108A: 7404 je .0BFF71090 ;\Светодиодгорит ?.BFF7108C: B005 mov al,005 ;Да. Возвращаем DRIVE_CDROM.BFF7108E: EB02 jmps .0BFF71092 ;Навозврат.BFF71090: B003 mov al,003.BFF71092: 5F pop edi ;/Возвратиз GetDriveTypeA.BFF71093: 5E pop esi ; (кусок кода, скопированный.BFF71094: 5B pop ebx ; из .0BFF74E02 - .BFF74E06).BFF71095: C9 leave ;.BFF71096: C20400 retn 00004 ;\ |
Таким образом, когда светодиод ScrollLock не горит, функция GetDriveTypeA работает как обычно, а если горит – то для всех Windows-приложений все локальные диски (у меня это С:\ и D:\) превращаются в CD-ROMы.
ПРИМЕЧАНИЕЧтобы всё это заработало, необходимо заменить файл C:\Windows\System\kernel32.dll на файл DriveType0\kernel32.dll. Сделать это можно, только загрузив компьютер в режиме MS-DOS, так как kernel32.dll – одна из системных библиотек Windows. Данный пример реализован для Windows 98. Поскольку системные библиотеки меняются в зависимости от версии Windows (и даже от номера билда), то в других операционных системах этот пример работать не будет (его нужно реализовывать для каждой версии kernel32.dll заново). |
Этот способ перехвата – один из самых мощных. Однако в коммерческих продуктах его использовать не удастся, так как он, очевидно, нарушает практически любое лицензионное соглашение.
Другой способ реализации этого метода – использование оберток (wrappers). Суть его в создании собственной DLL с тем же набором экспортируемых функций, что и оригинальная. В качестве примера могу привести следующий вариант реализации вышеприведённого примера:
Системную библиотеку Kernel32.dll переименовываем в kernel31.dll :).
Создаём библиотеку с именем Kernel32.dll, в которой реализована одна функция – GetDriveTypeA (это будет функция-перехватчик), а все остальные функции переадресуем к kernel31.dll (благо компилятор Visual C++ поддерживает переадресацию функций DLL).
Полученную библиотеку помещаем в системный каталог.
При этом функция-перехватчик может вызывать оригинальную функцию из kernel31.dll.
Основным недостатком данного способа является то, что он не годится для DLL, экспортирующих переменные.
Глобальный перехват методом подмены кода DLL в памяти (только Win9X)
Идея данного метода заключается в следующем: в Win9X системные DLL загружаются в общую для всех процессов область памяти (в третий гигабайт). Поэтому если бы удалось произвести реализацию Detours под Win9X, то изменения коснулись бы всех процессов (то есть случился бы глобальный перехват). Ситуация осложняется тем, что запись в область системных DLL в Win9X возможна или из режима ядра, или недокументированными средствами. Кроме того, в момент записи необходимо остановить все потоки, которые могут вызывать целевую функцию. Это можно сделать при помощи SuspendThread. Однако эта функция требует в качестве аргумента handle потока, а используемые для перечисления потоков функции Thread32First/Thread32Next возвращают ThreadID. Функция OpenThread, которая позволяет получить handle из ThreadID, реализована только начиная с Windows ME. По этой причине в общем виде документированными средствами из режима пользователя данный метод в Win9X нереализуем. Однако есть другой способ. Обе обозначенные проблемы (запись в область системных DLL и останов всех пользовательских потоков в системе) могут быть решены, если для перехвата использовать драйвер. Драйвер работает в режиме ядра, поэтому из него можно производить запись по любым доступным адресам. А если на момент установки/снятия перехвата поднять IRQL (уровень запроса на прерывание) до DISPATCH_LEVEL, то прервать поток сможет только процедура обработки аппаратного прерывания (откуда вызывать пользовательские системные функции нельзя). Кроме того, применение драйвера позволяет решить ещё одну проблему. Функция-перехватчик и функция-трамплин должны располагаться в области памяти, общей для всех процессов (в старших 2 гигабайтах). Конечно, можно было бы создать файл, отображаемый в память (MMF – они в Win9X размещаются в третьем гигабайте), и поместить код этих функций туда, или разместить их в отдельной DLL с ImageBase в третьем гигабайте, но проще реализовать их непосредственно в драйвере (драйверы в Win9X размещаются в четвёртом гигабайте – в разделе кода и данных режима ядра).
Рассмотрим пример, реализующий описанный метод. Для осуществления перехвата и размещения функции-трамплина и функции-перехватчика я написал WDM-драйвер (с использованием Visual C++ 6.0, Windows 2000 DDK и Compuware DriverStudio 2.7), а также программу для взаимодействия с ним. Программа и драйвер расположены в каталоге DriveType1 (там же – инструкции по установке).
Пример DriveType1 состоит из двух частей – драйвера DTDrv.sys и установочного скрипта DTDrv.inf, а также программы DriveType.exe.
DriveType.exe компилируется из одного модуля DriveType.cpp, в котором реализованы пользовательский интерфейс и интерфейс с драйвером. Интерфейс с драйвером реализуется через функции CreateFile (открытие драйвера), DeviceIoControl (операции ввода-вывода) и CloseHandle (закрытие драйвера). Реализованы четыре команды, вызываемые через DeviceIoControl – перехват функции GetDriveTypeA, снятие перехвата, установка возвращаемого значения функцией перехвата для каждого из дисков A: .. Z:, чтение текущего состояния перехвата.