А настолько ли уж он, сплайсинг, безопасен в своей природе? Ладно уж если будем затирать nop'ы в тех или иных проявлениях (mov edi,edi), ну или сохранять в буфере код пролога, допустим, но ведь возможны же неэнтерабильные случаи, когда мы своим 2х (5ти) байтным жампом затрём или даже если сохраним, то только часть команд/команды, т.е. незаконно врежемся в середину какого-нить опкода... Ну а если тем более ф-я без пролога - сразу нужные команды неизвестной в рантайм длины. Интересно услышать ваши соображения.
Для этих случаев придумали дизассемблер. Например, у Ms Rem в коде к статье про перехваты функций используется простейший дизассемблер длин инструкций. Сплайсинг небезопасен, но по другим причинам: 1) трудно получить эксклюзивную блокировку (тем не менее, решаемо как в ядре, так и в юзер-моде) 2) нет гарантий, что другой поток не прервется между двумя инструкциями, которые мы потом заменим на свои (решаемо только в юзер-моде)
gilg Я думаю, что, наоборот, решаемо только в кернел моде, т.к. можно запретить планирование потоков через повышение IRQL до уровня DPC/Dispatch. действительно, все это решаемо, поэтому сплайсинг достаточно безопасен, если правильно его использовать А тебе не пофиг, что ты там затер своим джампом? Все равно потом из буфера восстановишь. Сплайсинг вообще даже с учетом всех минусов я считаю самым удобным способом перехвата апи в юзермоде и в кернел моде (тех функ, которых нет в SSDT).
Great А поток тем временем возобновит свое выполнение и попадет либо в середину инструкции, либо хз куда Имеется в виду, что поток может быть прерван ДО начала установки хука и возобновлен ПОСЛЕ. В ядре отследить этот факт невозможно
Я имел ввиду установку своей функции-посредника, а не заместителя, т.е. продолжить исполнение исходной ф-ии с буфера. При неполных опкодах - не удастся, идея с дизассемблером длин как раз есть выход из ситуации, но разве простейшим можно обойтись при всём многообразии и избыточности решений?
Дизассемблер НЕ НУЖЕН! Ты затираешь первые 5 байт джампом, предварительно копируя их в буфер. Далее внутри хука для вызова оригинальной функции твои действия: - восстановить 5 байт из буфера - вызвать функцию - снова записать джамп. Короче, смотри мою статью про сплайсинг. Я там все написал, кажется =\ Линк: http://forum.antichat.ru/thread32176.html У меня там новый обработчик NtQuerySystemInformation сначала снимает хук, потом вызывает оригинальную функцию и ставит хук обратно. Никаких дизассемблеров не надо Если другой поток будет прерван до установки хуки и возобновлен после (так и будет, если повышать IRQL), вроде бы ничего страшного не случится.. или я туплю? Ведь самое страшное будет если наш поток установки хука будет прерван посередине установки хука, то другой поток, который захочет вызвать функцию, вылетит. Ну а если мы повышаем IRQL до DPC/Dispatch, наш поток может быть прерван: - до установки хука. Тогда другой поток при вызове попадает в оригинальную функцию - после установки хука. Тогда другой поток попадает уже к нам в перехватчик. А во время установки хука он прерван быть не может, т.к. при IRQL >=DISPATCH_LEVEL потоки не планируются. Еще на всякий пожарный можно сбросить бит прерываний в регистре флагов - CLI
Great В момент каждого изменения этих первых пяти байт существует вероятность, что некий поток будет выполнять эти инструкции ==> GP или BSoD. Ни о какой стабильности здесь речи идти не может.
gilg Это, конечно, возможно. Тут тоже можно выкрутиться, если попробовать такой алгоритм: - запретить планирование потоков - просмотреть список потоков планировщика, готовых к выполнению. Если EIP какого-либо из них указывает как раз в эти инструкции, которые мы хотим (но еще не успели) перезаписать, разрешим этому потоку чуть-чуть выполниться и снова запретить планирование. Делать так, пока никакие потоки не будут прерваны во время исполнения начала перехватываемой функции - установить перехват - разрешить планирование потоков
Код (Text): push ebp ---> здесь поток прерывается mov ebp, esp sub esp, 30 Устанавливаем хук, поток возобновляется и падает, потому что mov ebp, esp там уже не будет, а будет кусок джампа А теперь если подсчитать, во сколько процессору обходится каждое повышение IRQL на двухпроцессорной машине? Плюс опять же отсутствие надежности. Сплайсинговые хуки должны устанавливаться один раз при запуске приложения и сниматься при завершении. Тогда вероятность падения действительно низка. Накладные расходы при этом будут нулевые (2 far jump на вызов функции)
Это работает в юзер-моде, т.к. в ядре еще существуют прерывания, очереди DPC, APC и т.д. И во сколько обойдется просмотр списков планировщика?
Ну вообщем и так ясно, что расходы на сплайсинг в ядре большие. а как же быть, если хочется вызвать оригинальную функцию? Тогда может и потребуется дизассемблер длин, чтобы скопировать в буфер целое число команд, вместо джампа придется записывать CALL, потом придется мудрить с адресом возврата в стеке, сэмулировать выполнение команд из буфера и сделать джамп на адрес оригинальной функции после затертых байт. И не забыть оставить в стеке адрес возврата на наш перехватчик.
Может я криво написал, я имел в виду - копирование целого числа команд, а в перехватчике - эмулирование этих команд (там обычно только пролог будет стандартный) и переход на оригинальную функцию после затертых инструкций. потом обработка результатов ее выполнения ЗЫ. Не в тему, но тут заикнулись вроде про MOV EDI,EDI. Я так и не понял, зачем эта байда лежит в начале многих функций?
джамп. Алгоритм такой: Код (Text): orig_func: jmpf hook_start nop nop continued: saved_code: push ebp mov ebp, esp ... jmpf continued hook_start: .... call saved_code ...
Вообщем, к чему мы все-таки пришли? В кернел моде нужно ставить хук один раз при запуске программы и больше не снимать до завершения. А для вызова оригинальной функции использовать код похожй на тот, что привел gilg, копируя целое число инструкций в буфер.
Сплайсинг детектится легко. Проверяем первые команды по маске(push/ret, jmp, call, int). Я делал так: В начало функции копировал N инструкций своего обработчика, потом jmp на N+1 инструкцию. В буфер копировал начало оригинальной функции(те инструкции, которые затираются моим кодом и jmp), потом jmp на продолжение оригинальной функции. Поис по маске не проходит. Можно правда проверять куда передается управление, и если вне ntoskrnl.exe то бить тревогу, но пока я не встречал таких антируткитов. Сорц который всё это делает я выкладывал как пример сплайсинга.