Смерть защитам Николай «gorl» Андреев Хакер, номер #081, стр. 081-124-2 Вот только возможность эту не так просто получить. Ты, наверное, уже заметил, что все защиты, о которых я говорил выше, перехватывают Native API – в этой неприятной тенденции и кроется основная проблема. Дело в том, что ни одна уважающая себя защита не станет перехватывать какое-нибудь системное событие на пользовательском уровне. Она постарается максимально усложнить жизнь хакеру, а, следовательно, будет делать все исключительно на уровне ядра, где не ступала еще нога юзера без прав администратора. А от перехвата на уровне ядра избавиться на порядок сложнее, чем от обычной подмены записи в таблице импорта или сплайсинга функции в user mode. Сложнее, но ведь для нас нет ничего невозможного! [перехват API в kernel mode] За некоторым системным событием можно следить на разных этапах его развития. Мда, хорошо сказал. На примере, думаю, будет понятнее. Есть, скажем, событие модификации памяти одного процесса другим. Для обхода файрвола мы вызывали его с помощью функции WriteProcessMemory. Защита может перехватить эту функцию и в принципе этого будет достаточно, чтобы предотвратить хакерскую деятельность. Однако кодеру ничего не будет стоить в user mode этот перехват обойти. Защита также может перехватить Native API функцию NtWriteVirtualMemory, экспортируемую библиотекой ntdll.dll, но и этот перехват будет осуществляться на пользовательском уровне, а, следовательно, будет ненадежен. Поэтому разработчики защит предпочитают реализовывать перехват на уровне ядра. Пишут драйвера и тихо мирно подменяют адреса Native API функций в Service Descriptor Table (если ты еще не знаешь, что такое SDT или недостаточно знаком с механизмом работы низкоуровневой части Windows, очень советую почитать книгу Свена Шрайбера «Недокументированные возможности Windows 2000», статьи с www.rootkit.com, а также статью ms-rem’а «Перехват API-функций в Windows 2000. Нулевое кольцо»). Это очень легко делается, фактически в одну строку (естественно, перед заменой элемента в таблице надо не забыть сбросить WP bit и запретить прерывания): // сначала определяется простенький макрос #define SYSTEMSERVICE(_function) KeServiceDescriptorTable->ntoskrnl.ServiceTable[*(PULONG)((PUCHAR)_function+1)] // и производится замена SYSTEMSERVICE(NtWriteVirtualMemory) = NewNtWriteVirtualMemory; KeServiceDescriptorTable – это экспортируемый ядром указатель на SDT, структуру в которой содержится таблица системных сервисов, из которой в конечном счете берутся адреса функций Native API. [структуры SDT и SST] typedef struct _SERVICE_DESCRIPTOR_TABLE { PNTPROC ServiceTable; PDWORD CounterTable; ULONG ServiceLimit; PBYTE ArgumentTable; } SERVICE_DESCRIPTOR_TABLE; Чтобы получить номер ячейки в SDT, в которой будет содержаться адрес перехватываемой Native API функции на уровне ядра нужно всего лишь прибавить к адресу функции-переходника с тем же именем из ntdll.dll единичку – и взять по полученному адресу значение. Наверное, тебя интересует, откуда там возьмется этот номер? Все очень просто. Давай, дизассемблируем любую Native API функцию, экспортируемую ntdll.dll: |