Меня интересуют методы безопасной и надежной установки перехвата функций режима ядра, используемые в современном защитном ПО (антивирусы, файрволлы) с точки зрения * Правильной работы на многопроцессорных системах * Сложности обхода (возможности вызова исходной функции) Для синхронизации при установке и снятии перехвата я посылаю DPC на все процессоры, кроме текущего, с использованием KeSetTargetProcessorDpc, и жду в цикле сигнала (посредством обращения к разделяемой переменной) от всех DPC о том, что соответствующие процессоры переведены на высокий уровень IRQL. Далее устанавливаю перехват как на однопроцессорной системе и изменяю разделяемую переменную, давая понять остальным процессорам, что можно переходить на исходный IRQL. При этом у меня есть несколько вопросов: 1. Можно ли вызывать оригинальную функцию на повышенном IRQL, как это делается в статье Ms-Rem'a? Ведь большенство функций рассчитано на на уровень PASSIVE_LEVEL и работает с указателями на память пользовательского режима... 2. Если так делать нельзя, то при восстановлении оригинального заголовка функции и понижении IRQL до исходного уровня, другой поток может вызвать оригинальную функцию без перехвата - как предотвратить такую возможность? 3. Исходя из предыдущих вопросов - какой способ перехвата надежнее в плане защищенности от обхода из пользовательского режима - сплайсинг, или запись в SDT? 4. Стоит ли кроме этого пытаться защититься от снятия перехватов из режима ядра (RootkitUnhooker и др.), или современный защитный софт ограничивается только контролем загрузки новых драйверов ввиду нецелесообразности борьбы с угрозами из ядра? 5. Есть ли разница между обнулением IF инструкцией cli и повышением IRQL до HIGH_LEVEL? Стоит ли ограничиваться уровнем (IPI_LEVEL - 1) для сохранения возможности межпроцессорного взаимодействия, учитывая, что исходные DPC уже доставлены всем процессорам? Заранее благодарю за помощь!
Сплайсинг всегда надежнее, потому что спалить чуть труднее чем хук в SDT. Да и к тому же при сплайсинге вызов оригинальной функции тебе не поможет для обхода. Но сплайсинг намного труднее реализовать чем хук в SDT. Большинство антивирей и фаеров ставят хуки именно в SDT (запусти gmer и сразу увидишь) Я чтобы ставить хук в SDT юзаю такой вот код: Код (Text): // функция установки и снятия перехвата BOOL SetHook(USHORT id, ULONG NewAddr, PULONG OldAddr) { BOOL ret = false; ULONG Addr; PHYSICAL_ADDRESS PhysicalAddr; ULONG VirtualAddr; Addr = (ULONG)KeServiceDescriptorTable->ServiceTable; // получаем адрес таблицы сервисов if (Addr) // если нет ошибки { Addr += 4 * id; // вычислим оффсет адреса функции которую необходимо перехватить if (OldAddr) // если указана память для сохранения старого значения { *(ULONG*)OldAddr = *(ULONG*)Addr; // сохраним старый адрес } // получим физический адрес, зная виртуальный PhysicalAddr = MmGetPhysicalAddress((void*)Addr); if (PhysicalAddr.QuadPart) { // выделим себе виртуальную память по физ. адресу VirtualAddr = (ULONG)MmMapIoSpace(PhysicalAddr, 4, 0); if (VirtualAddr) { *(ULONG*)VirtualAddr = NewAddr; // установим перехват ret = true; MmUnmapIoSpace((void*)VirtualAddr, 4); // освободим вирт. страницу памяти } } } return ret; } Таким вот образом ты убиваешь сразу двух зайцев: обходишь защиту памяти и нет нужны в синхронизации при работе в многопроцессорной системе.
повышение IRQL, не гарантирует безпроблемного сплайсинга, т.к. остается вероятность существования замороженного потока как раз в промежутке заменяемых команд. Запись в SDT такой проблемы лишена из-за атомарности доступа к адресу функции в памяти. Вызывать оригинал на повышенном IRQL, разумеется, нельзя. Надо вызывать оригинал не меняя команды перехвата обратно на первоначальные, а использовать вызов через так называемый трамплин (копия нескольких команд оригинальной функции с последующим jmp на ее продолжение). В современных 64 битных виндовсах МС запретила и сплайс и патч SDT под страхом BSOD'a (см. PatchGuard), надо использовать сервисы перехвата предоставляемые ОС.
Спасибо за помощь, с синхронизацией и сплайсингом разобрался, сейчас возникла новая проблема, возможно связанная с использованием функций-трамплинов. В памяти при установленном перехвате все выглядит вот так, правильность генерации Trampoline функции проверял отладчиком: Код (Text): // пример для NtOpenProcess NTSTATUS NewNtOpenProcess(...) { // some code return TrampolineNtOpenProcess(...) /* в дизассемблере: push ... ; параметры call TrampolineNtOpenProcess pop ebp ret 2Ch ; <-- ! BSOD ! */ } __declspec(naked) NTSTATUS TrampolineNtOpenProcess(...) { nop nop ... // сюда копируются первые "N" байт исходной функции nop nop ... // сюда записывается переход на "хвост" оригинала: push offset NtOpenProcess + N , ret } NTSTATUS NtOpenProcess(...) { // измененный заголовок push offset NewNtOpenProcess ret .... // продолжение } Вот код установки перехвата, при необходимости могу выложить остальное: Код (Text): // ProcAddress - NtOpenProcess, HookAddress - NewNtOpenProcess, TrampolineAddress - TrampolineNtOpenProcess, HeaderBuffer - первые 6 байт оригинала void HookFunction(PVOID ProcAddress, PVOID HookAddress, PVOID TrampolineAddress, PVOID HeaderBuffer) { ULONG OldCR0; KIRQL OldIrql; JUMP_FAR jmp_to_hook, jmp_to_original; ULONG TrampolineHeaderSize; ULONG InstructionSize; PUCHAR InstructionPtr; BOOLEAN LdeStatus; KdPrint((DRIVERNAME " - HookFunction, ProcAddress = %lx\n", (ULONG)ProcAddress)); // инициализация структур дальнего переходa следующего вида: // push address; ret; jmp_to_hook.pushop = jmp_to_original.pushop = 0x68; jmp_to_hook.retop = jmp_to_original.retop = 0xc3; jmp_to_hook.address = HookAddress; // запрет прерываний, сброс бита WP регистра CR0 LockAllProcessors(&OldIrql); __asm { mov eax, cr0 mov OldCR0, eax and eax,0xFFFEFFFF mov cr0, eax } // // вычисление размера сохраняемого заголовка для функции-трамплина // с использованием дизассемблера длин инструкций Lde TrampolineHeaderSize = 0; InstructionPtr = (PUCHAR)ProcAddress; do { LdeStatus = Lde(InstructionPtr, &InstructionSize); ASSERT(LdeStatus != FALSE); InstructionPtr += InstructionSize; TrampolineHeaderSize += InstructionSize; } while(TrampolineHeaderSize < sizeof(JUMP_FAR)); // копирование кода в Trampoline-функцию - заголовок + переход на оригинал jmp_to_original.address = (PVOID)((ULONG)ProcAddress + TrampolineHeaderSize); RtlCopyMemory(TrampolineAddress, ProcAddress, TrampolineHeaderSize); RtlCopyMemory((PVOID)((ULONG)TrampolineAddress + TrampolineHeaderSize), &jmp_to_original, sizeof(JUMP_FAR)); // сохранение исходного заголовка функции RtlCopyMemory(HeaderBuffer, ProcAddress, sizeof(JUMP_FAR)); // запись структуры перехода на место заголовка RtlCopyMemory(ProcAddress, &jmp_to_hook, sizeof(JUMP_FAR)); // // разрешение прерываний, восстановление регистра CR0 __asm { mov eax, OldCR0 mov cr0, eax } UnlockAllProcessors(OldIrql); } Для определения "N" пришлось использовать дизассемблер длин инструкций, так как, к примеру, в функции могут идти подряд две пятибайтовые команды, тогда в трамплин должны записываться обе инструкции (то есть 10 байт, а не 6) и осуществляться переход на третью инструкцию оригинала. Проблема заключается в том, что при выходе из функции перехвата (в данном примере NewNtOpenProcess) при определенных условиях случается BSOD, цепочка вызовов выглядит таким образом: NewNtOpenProcess -> TrampolineNtOpenProcess -> NtOpenProcess -> NewNtOpenProcess(возврат из NtOpenProcess) -> 0x0000000(возврат из NewNtOpenProcess) -> BSOD. То есть при выполнении команды ret функции перехвата возврат осуществляется не на вызвавшую ее функцию, а на нулевой байт памяти - это может быть связано с повреждением стека в каком-то месте в данной цепочке вызовов? Еще одна странность - поведение драйвера почему-то зависит от количества и типа перехватываемых функций. При перехвате NtOpenFile и NtCreateFile возникает описанный BSOD; при перехвате NtOpenProcess и NtCreateFile вновь запускаемые программы отображаются в "упрощенным интерфейсе" как в Windows 2000 / 9x (NtOpenProcess не блокирует открытие процессов, просто ставится перехват и передается управление оригиналу); при перехвате только одной из функций все работает нормально... Подскажите пожалуйста, в чем может быть проблема и такое странное поведение, я уже устал с бубном плясать Спасибо!