Смекни!
smekni.com

Методы перехвата API-вызовов в Win32 (стр. 4 из 4)

Ну а вся работа по перехвату делается в драйвере, за исключением того, что адрес функции GetDriveTypeA определяется также в DriveType.cpp и присылается в качестве параметра в команде перехвата. После получения этого адреса функция DTDRV_IOCTL_HOOK_Handler (из модуля DTDrvDevice.cpp) реализует перехват рассмотренным выше способом. Функция DTDRV_IOCTL_UNHOOK_Handler снимает перехват, функция DTDRV_IOCTL_SETDRIVE_Handler устанавливает значение, возвращаемое перехватчиком, функция DTDRV_IOCTL_GETSTATE_Handler возвращает значения перехвата и флаг перехвата.

Основные переменные, используемые DriveType.cpp:

unsigned char IsHook = false; //Флагперехватаunsigned char Drives[26]; //ЗначенияперехватаPUCHAR GDT; //Адрес GetDriveTypeA

В Drives[26] хранятся значения, возвращаемые функцией MGetDriveType для дисков A: .. Z: (=0xFF, если информация о диске не переопределена).

Итак, функция-трамплин NewGetDriveType будет выглядеть следующим образом:

__declspec(naked) unsigned int NewGetDriveType(LPSTR Path){ _asm { nop //Здесь будут первые 5 байт из функции GetDriveTypeA nop //(в Win98 3 команды ассемблера) nop nop nop jmp $ //А здесь - переход к GetDriveTypeA + 5 }}

Изначально эта функция «пустая», так как весь её код пишется во время перехвата функцией DTDRV_IOCTL_HOOK_Handler, которая, если оперировать терминами Detours, реализует динамический перехват.

ПРИМЕЧАНИЕКод этой функции изначально может быть любым, но он должен занимать по крайней мере 10 байт (чтобы уместились 5 байт из заголовка GetDriveTypeA и 5 байт – команда jmp).

Собственно функция-перехватчик MGetDriveType реализована в моём примере так:

unsigned int MGetDriveType(LPSTR Path) //Это – функция-перехватчик{ unsigned int res = NewGetDriveType(Path); //Вызовемстарый GetDriveTypeA unsigned char Letter = *Path; if (Letter >= 'a' && Letter <= 'z') Letter- = 'a' - 'A'; //Заглавные if (Letter >= 'A' && Letter <= 'Z') { unsigned char NewRes = Drives[Letter - 'A'];if (NewRes < 0xFF) res = NewRes; //Если диск переназначен, вернём значение из таблицы } return res;}

Сначала вызывается функция-трамплин NewGetDriveType, которая фактически выполняет код оригинальной GetDriveTypeA (сначала выполняются первые 5 байт – это 3 команды ассемблера, затем – всё остальное). После этого определяется буква диска. Преобразование буквы в верхний регистр осуществляется вручную. Далее, если данный диск перехвачен, возвращается значение из массива Drives, в противном случае – то, которое вернула NewGetDriveType.

Перехват реализован в функции DTDRV_IOCTL_HOOK_Handler следующим образом:

NTSTATUS DTDrvDev::DTDRV_IOCTL_HOOK_Handler(KIrp I){ NTSTATUS status = STATUS_SUCCESS; I.Information() = 0; if (IsHook) return status; #pragma pack(push, 1) //Включимвыравниваниепограницебайта struct { UCHAR Byte0; ULONG Byte1_4; } Patch = {0xE9}; //Кодинструкции jmp #pragma pack(pop) //Вернёмвыравниваниепоумолчанию KIRQL oldirql; KeRaiseIrql(DISPATCH_LEVEL, &oldirql); //Поднимем IRQL до DISPATCH_LEVEL GDT = (PUCHAR)*(PULONG)I.IoctlBuffer(); //GDT = Адрес GetDriveTypeA RtlCopyMemory(NewGetDriveType, GDT, 5); //Заголовок NewGetDriveType Patch.Byte1_4 = (ULONG)GDT - (ULONG)NewGetDriveType - 5; RtlCopyMemory((PVOID)((ULONG)NewGetDriveType + 5), &Patch, 5); //jmp GetDriveTypeA + 5 Patch.Byte1_4 = (ULONG)MGetDriveType - (ULONG)GDT - 5; RtlCopyMemory(GDT, &Patch, 5); //jmp MGetDriveType IsHook = true; KeLowerIrql(oldirql); //Вернём IRQL обратно return status;}

Если функция GetDriveTypeA ещё не перехвачена (IsHook=false), то:

Определяется адрес функции GetDriveTypeA (он присылается в качестве параметра);

По адресу NewGetDriveType копируются 5 байт из начала GetDriveTypeA;

За ними вставляется байт 0xE9 (код команды jmp) и смещение до точки GetDriveTypeA + 5;

По адресу GetDriveTypeA вставляется 0xE9 и смещение до точки MGetDriveType;

Флаг перехвата IsHook устанавливается в true.

Функция снятия перехвата возвращает всё на свои места:

NTSTATUS DTDrvDev::DTDRV_IOCTL_UNHOOK_Handler(KIrp I){ NTSTATUS status = STATUS_SUCCESS; I.Information() = 0; if (!IsHook) return status; KIRQL oldirql; KeRaiseIrql(DISPATCH_LEVEL, &oldirql); //Поднимем IRQL до DISPATCH_LEVEL RtlCopyMemory(GDT, NewGetDriveType, 5); //Вернёмзаголовок GetDriveTypeA наместо IsHook = false; KeLowerIrql(oldirql); //Вернём IRQL обратноreturn status;}

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

Заключение

Итак, перехват API-вызовов – вещь, хотя и достаточно сложная, но все-таки реализуемая (причём различными способами). Методы перехвата различны и часто не переносимы из одной версии Windows в другую.

Microsoft старается сохранять совместимость программного обеспечения со старыми версиями Windows, но получается это далеко не всегда, и аспекты программирования, настолько приближённые к низкоуровневому системному программированию, очень сильно различаются для разных версий Windows. Поэтому часто приходится жертвовать эффективностью в ущерб универсальности – и наоборот.