Пишу драйвер (не рут) который должен жить в мире и согласии с антивирусами не влияя на их нормальное функционирование. в драйвере хукаю несколько sys API через SSDT. К сожалению, есть несколько антивирусов с которыми драйвер регулярно бсодит. Удалось восоздать в лабораторных условиях (под дебагером) сценарий приводяший к падению системы: 1. драйвер антивируса поставил свой хук на функцию в SSDT. 2. мой драйвер сделал то-же самое. Вместо настояшей функции мой драйвер вызывает функцию в драйвере антивируса. Как уже упоминалась: важно, чтоб антивирус функционировал полноценно. 3. драйвер антивируса решил отчалить. Уж не знау что у него там случилось. Может update? Увидев, что в SSDT вектор поменялся антивирус понимает, что кто-то поставил свой хук, решает, что если Вы свой хук поставили то сами с ним и живите и делает ручкой. 4. Мой драйвер передает управление функции в драйвере антивируса который уже выгружен из памяти... Синий экран ... Вижу два решения проблемы: 1. Пропатчить сами функции. Достоинства: должно быть достаточно надежно (по крайней мере в теории). Недостатки: сложнее чем хукинг SSDT, не совсем понятно как блокировать все другие потоки от доступа к функции в процессе патчинга. 2. Пропатчить обработчик sysenter. Достоинства: 100% получаем управление перед антивирусом. Недостатки: относительно сложно - нужно патчить не начало функции а где-то в середине там где sysenter делает call по адресу из SSDT; синхронизация. Вопрос, собственно - из Вашего опыта какие подводные камни могут встретится при реализации? Может есть другие способы обойти проблему? Спасибо!
о май гад а нельзя чтоли поменять обработчик sysenter на свой просто? делается одной инструкцией wrmsr нет, проще хукать ssdt
Great Можно, но тогда придется возится с копированием аргументов и на стек и прочее, не хочу сказать, чтоб это было уж слишком сложно, но, кажется, надежнее патчить существующий обработчик чем писать свой. Естественно, только антивирусы это не любят.
Что мешает перед вызовом функции проверить: а не выгрузился ли драйвер антивируса? Если выгрузился - восстановить sdt и вызывать уже настоящую функцию, а не переходник антивируса. Это требует конечно проверки на присутствие антивируса, но проблем быть не должно (ZwQuerySystemInformation)
cresta Немного геморойно. Есть масса антивирусов и нет никакой возможности сделать ad-hok решение для каждого из них. Нужно что-то универсальное и стабильное.
Какая разница, сколько антивирусов? Хоть сто штук Механизм хука на sdt один и тот же для всех желающих похукать его, и не нужно писать разных процедур восстановления sdt. Только одну. Если адрес из sdt никому из загруженых системных модулей не принадлежит - восстановить sdt, чтобы адрес указывал на ntoskrnl, и ничего падать не будет. В любом другом случае - ничего не трогать. Или я что-то не так понял?
Clerk наскока я знаю это бывает редко.. обычно адрес таблицы равен либо KeServiceDescriptorTable либо KeServiceDescriptorTableShadow для гуйных потоков
А есть ли какой callback вызываемый при выгрузке драйвере (не моего)? Т.е., аналог PsSetLoadImageNotifyRoutine только вызываемый при выгрузке драйвера.
cresta Вроде нашел замену - хукаем ZwUnloadDriver и можно подсмотреть какой драйвер выгружается. Проблема в том, что ето будет работать только если драйвер выгружается из user mode приложения.
Была же мысль хукать сплайсингом. Думаю, это поможет - антивири обычно так не делают. Задрать IRQL или просуспендить все потоки.
не вариант. надо хукать WorkItem для выгрузки, тем более что несложно это сотворить - всего лишь поменять адрес Worker Routine Уже 1000 раз обсуждали что никакой саспенд не спасет от того, что другой поток может в момент заморозки выполнять код перехватываемой функции. А ты нагло туда суешь джамп поверх выполняемого другим потоком кода. После резюма/понижения IRQL другой поток продолжит выполнение, а там уже джамп лежит. В режиме ядра последствия очевидны. Правда бывает это редко, но, как говорится, метко
Great Извиняюсь, не понял, что нужно хукать? Что такое WorkItem/worker routine? А какие могут быть причины НЕ патчить обрабoтчик sysenter? Проблему синхронизации там можно решить просто - копируем старый обработчик, патчим копию, wrmsr. Правда, не совсем очевидно, что антивирусы не будут ругатся на это.
И какой антивирус так делает, если не секрет? Вы тут уже начали изобретать неведать что, и неведать зачем. В нормальных условиях ничего подобного возникать не должно, цепочки хукеров должны нормально соосуществовать, в противном случае, либо ошибка в твоем драйвере, либо кг/ам-щик тот, с чьим софтом у тебя проблемы. Я почитал топик, и у меня возник вопрос, - ты точно уверен, что причина BSOD-а именно в том, что ты написал? Я на своей практике сталкивался с двумя софтинами, из-за кривых хуков которых все падает в BSOD, если кто-то хукает после них - это Kaspersky и COMODO. У первого не совпадало кол-во параметров одной из функций в Shadow SDT (4 вместо 5), что приводило к неверной коррекции стека, а поскольку KiSystemService не чуствителен к этому, ничего не падает до тех пор, пока ф-цию не похукает еще кто-то, когда стек будет использоваться следующим в цепочке. С COMODO было еще хуже - у него стоит хук на ZwWriteFileGather, и обработчик мало того, что неверно корректирует указатель стека, да еще и перезаписывает DWORD в его вершине, т.к. считает, что эта функция принимает 10 параметров, вместо положенных 9. Не исключаю, что есть и другие, похожие, чудо-хукеры =). Чтобы разрулить эту ситуацию и застраховаться от подобных багов, я сделал wrapper для безопасного вызова системных сервисов, которые похуканы кем-то еще... В итоге вышло по типу такого: Код (Text): LONG GenericServiceCall( PVOID Address // адрес вызываемой функции ULONG ParCnt, // кол-во параметров PULONG_PTR ParArray // массив параметров ) { LONG ESPreg, Result; __asm { mov ESPreg, esp // сохраняем ESP sub esp, 16 // вызывающая функция может перезаписывать до 4 DWORD в стеке mov ebx, ParArray mov ecx, ParCnt __loop: dec ecx js __done push [ebx+ecx*4] jmp __loop __done: call Address mov Result, eax mov esp, ESPreg // восстанавливаем ESP } return Result; } Это решило все проблемы совместимости =). Ну а выгрузка SSDT хукеров - это вообще кг/ам, и в коммерческом софте я такого вроде пока не встречал.
Ну ты заикнулся про IopLoadUnloadDriver, так вот скажу, Как происходит обработка запроса на загрузку дрова. Zw(Un)LoadDriver ставит в очередь WorkItem - рабочий элемент, который будет исполняться рабочим потоком (worker thread) процесса System. Рабочий поток, получив workitem, начинает исполнять указанную в нем Worker Routine - рабочую функцию. Обычно это IopLoadUnloadDriver. Она вызывает IopLoadDriver, которая уже дергает DriverEntry и все такое. Так вот, можно похукать код, устанавливающий адрес WorkerRoutine в структуре WorkItem (там один mov, кажется). Это перезапись дворда без изменения опкода - безопасная операция написал бы явно пролог/эпилог и поставил бы naked, а то вдруг компилятору взбредет в голову (или что у него на этом месте) убрать его в целях оптимизации.. вот тогда весело будет в доступе к локальным переменным с сорванным стеком. Я бы, кстати, лучше в регистре сохранял бы старый ESP. Например, в EDI. Или других регистрах, которые апи менять не имеют права - esi, ebp, ebx, че там еще..