В общем-то, требуется ваша помощь относительно сабжа)) Стоит такая задача- написать софтину, которая бы ограничивала запуск пользователями некоторых приложений по определенным критериям (имя файла, размер- это все детали). Очевидно, что задача должна решаться при помощи перехвата системных функций, но вот каких? Перехватывать ли мне Win32 API или же Native API. Можно конечно перехватить вызов CreateProcess (даже из userspace), но в случае функций Win32 можно легко мою программку одурачить (как известно, есть масса способов обмануть перехватчик Win32 API). Или перехватывать Native API, но и тут мне не все понятно Я конечно не могу похвастаться глубоким знанием Native-функций, но беглый просмотр справочника показал, что эти функции принимают уже указатели на какие-то системные структуры, а не на строчку с именем exe-файла, как в функциях Win32. Мне же важно получить имя файла для проверки определенных условий и генерации решения- запустить или запретить выполнение. В общем, без вашей помощи врядли разберусь... Заранее спасибо
Может быть конечно я чего-то не понимаю =) Функция ZwCreateProcess НЕ принимает в качестве параметра имя запускаемого модуля. Она возвращает хэндл созданного объекта ПРОЦЕСС в случае успешного завершения. Но в качестве входных параметров она принимает в основном служебные данные- требуемый уровень доступа, атрибуты и прочее. Разве можно каким-то образом через них добраться до имени исполняемого файла? Если можно, то просветите, пожалуйста Об этом я и писал в первом посте- перехват WIn32 API слишком ненадежен, а функции Native API работают уже с более низкоуровневыми объектами...
Насколько я знаю, структура ObjectAttributes не обязательно должна заполняться, очень часто в вызовах ZwCreateProcess значение параметра ObjectAttributes равно 0... Т.е. если мы хотим предоставить информацию- предоставляем, а не хотим ставим NULL. Может быть я и не прав, но похоже, что из вызова ZwCreateProcess имя файла вытащить все-таки не удастся (по крайней мере, в общем случае). А что если установить перехват на ZwCreate(Open)File? Вы не в курсе, как такой глобальный hook повлияет на производительность?
После вызова оригинальной ZwCreateProcess в параметре ProcessHandle будет содержаться хендл создаваемого процесса, по нему можно получить все, что нужно. Это, имхо, не самый простой путь. Все-равно кроме хендла ничего не получишь, только это будет хендл файла, а не процесса, соответственно, меньше полезной информации. Посмотри статью из Phrak`а "Invisibility on NT boxes" ("Как стать невидимым в Windows NT"), 7 раздел.
Как я понимаю, хэндл процесса в ProcessHandle устанавливается уже после того, как ZwCreateProcess успешно отработает, т.е. это выходной параметр. Т.е. если я перехватываю ZwCreateProcess, то ProcessHandle будет лишь указывать на переменную, которая и получит значение ProcessHandle после завершения оригинальной функции, само значение мне будет недоступно, ибо мой перехватчик должен передать управление true-ZwCreateProcess. Или я что-то не так понял? P.S. Погуглив, наткнулся на несколько обсуждений подобной задачи. Там предлагают использовать перехват ZwCreateSection или ZwCreateThread. Насколько я могу понять, в первом случае я получаю хэндл файла (как и в случае Create/OpenFile), а во втором случае получаю УЖЕ хэндл процесса, в адресном пространстве которого этот поток запускается. Я правильно рассуждаю? P.P.S. А за ссылочку на Phrack спасибо, почитаю Жаль только их официальный сайт прикрыли власти самой демократической демократии в мире
Схематично механизм такой: Код (Text): Перехваченный_ZwCreateProcess(PHANDLE handle) { original_ZwCreateProcess(handle); Имя_файла, размер_образа, и т.д. получаются по хендлу. Если запретить создание { ZwTerminateProcess(handle); return STATUS_ACCESS_VIOLATION; } } Если ZwTerminateProcess() здесь не прокатит (процесс еще полностью не создан, так что хз, будет ли здесь работать), то можно выполнить его точно также в перехвате ZwResumeThread
IMHO после отработки оригинальной ф. ZwCreateProcess узнать имя модуля не удастся. Я точно не помню, но порядок вызовов при создании пользовательского процесса такой: ZwCreateProcess RtlCreateProcessPrameters RtlCreateUserProcess NtResumeThread ф. RtlCreateProcessPrameters как раз имеет нужные параметры - десктоп, имя модуля и.т.п. Если процесс не должен быть создан, нужно не дать отработать NtResumeThread, после чего уже честно замочить его через ZwTerminateProcess.
Код (Text): Перехваченный_ZwCreateProcess(PHANDLE handle) { original_ZwCreateProcess(handle); Имя_файла, размер_образа, и т.д. получаются по хендлу. Если запретить создание { ZwTerminateProcess(handle); return STATUS_ACCESS_VIOLATION; } } Меня вот что интересует- действительно ли можно перехватить некоторую функцию, сделать что-то, потом передать управление оригинальному обработчику, потом опять получить управление на перехватчик и что-то опять сделать? (Как в Вашем примере: после отработки оригинальной ZwCreateProcess перехватчик снова получает управление и мы в зависимости от неких условий прибиваем процесс). Не может ли случиться так, что оригинальная функция вернет управление в какое-нибудь другое место? Может быть это и глупые вопросы, но, честно сказать до сегодняшнего дня с темой перехвата Native API был знаком лишь отдаленно А что касается ZwCreateProcess, то скорее всего, имя файла там может и не всплыть... В основном люди пользуются NtResumeThread, реже- NtCreateThread
Незнаю че там не прокатит, вот кусок кода который у меня удачно работал долгое время, описывать не стану: Код (Text): NTSTATUS NewZwCreateFile(PHANDLE FileHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, PIO_STATUS_BLOCK IoStatusBlock, PLARGE_INTEGER AllocationSize, ULONG FileAttributes, ULONG ShareAccess, ULONG CreateDisposition, ULONG CreateOptions, PVOID EaBuffer, ULONG EaLength) { NTSTATUS status; WCHAR *pExt; CHAR szProcess[NT_PROCNAMELEN]; status = OldZwCreateFile(FileHandle, DesiredAccess, ObjectAttributes, IoStatusBlock, AllocationSize, FileAttributes, ShareAccess, CreateDisposition, CreateOptions, EaBuffer, EaLength); if (wcsrchr(ObjectAttributes->ObjectName->Buffer, '\\') < wcsrchr(ObjectAttributes->ObjectName->Buffer, '.')) { if (wcslen(ObjectAttributes->ObjectName->Buffer) < 5) return status; pExt = wcsrchr(ObjectAttributes->ObjectName->Buffer, '.'); if (pExt == NULL) return status; GetProcessName(szProcess); if (bIncExt) if (!TestMask(szInclude, &pExt[1])) return status; if (bExcExt) if (TestMask(szExclude, &pExt[1])) return status; if (bIncProgram) if (!TestProgram(szPrograms, szProcess)) return status; if (bExcProgram) if (TestProgram(szExcPrograms, szProcess)) return status; PCREATE_BUFFER pBuffer = AddCreateEntry(); if (pBuffer != NULL) { LARGE_INTEGER time, time_local; TIME_FIELDS tf; pBuffer->hfile = *FileHandle; wcsncpy(pBuffer->szBuffer, ObjectAttributes->ObjectName->Buffer, 255); strncpy(pBuffer->szProcess, szProcess, NT_PROCNAMELEN); KeQuerySystemTime(&time); ExSystemTimeToLocalTime(&time, &time_local); RtlTimeToTimeFields(&time_local, &tf); if (tf.Hour < 10) { strcpy(pBuffer->szTime, "0"); _itoa(tf.Hour, (pBuffer->szTime+1), 10); } else _itoa(tf.Hour, pBuffer->szTime, 10); strcat(pBuffer->szTime, ":"); if (tf.Minute < 10) { strcat(pBuffer->szTime, "0"); _itoa(tf.Minute, (pBuffer->szTime+4), 10); } else _itoa(tf.Minute, (pBuffer->szTime+3), 10); strcat(pBuffer->szTime, ":"); if (tf.Second < 10) { strcat(pBuffer->szTime, "0"); _itoa(tf.Second, (pBuffer->szTime+7), 10); } else _itoa(tf.Second, (pBuffer->szTime+6), 10); dprintf("In the \"ZwCreateFile :\" %ws - %s, handle - %d\n", ObjectAttributes->ObjectName->Buffer, pBuffer->szProcess, *FileHandle); } } return status; } немножко нето, но сути не меняет.
TarasCo Точно Да. Все функции в Windows гарантируют нормальный возврат, либо (об этом всегда говорится в документации) выбрасывают исключение. Еще вариант - бсод, но он я думаю не рассматривается
Перехват делается так: Код (Text): #define NTCALL(_function)KeServiceDescriptorTable->ntoskrnl.ServiceTable[_function] typedef NTSTATUS (*ZWCREATEFILE)(OUT PHANDLE FileHandle, IN ACCESS_MASK DesiredAccess, IN POBJECT_ATTRIBUTES ObjectAttributes, OUT PIO_STATUS_BLOCK IoStatusBlock, IN PLARGE_INTEGER AllocationSize OPTIONAL, IN ULONG FileAttributes, IN ULONG ShareAccess, IN ULONG CreateDisposition, IN ULONG CreateOptions, IN PVOID EaBuffer OPTIONAL, IN ULONG EaLength); ZWCREATEFILE OldZwCreateFile; ----------------------------------------------------------------------------- ULONG CR0Reg; OldZwCreateFile = (ZWCREATEFILE)NTCALL(nZwCreateFile); if (NTCALL(nZwCreateFile) != NewZwCreateFile) { __asm { cli mov eax, cr0 mov CR0Reg,eax and eax, 0xFFFEFFFF mov cr0, eax } NTCALL(nZwCreateFile) = NewZwCreateFile; __asm { mov eax, CR0Reg mov cr0, eax sti } } ----------------------------------------------------------------------------- В аттаче номера функций "_function".
Большое всем спасибо за оказанную помощь, теперь думаю разберусь Меня всегда интересовал такой вопрос: перехватчик функций Native API всегда должен проверять номер версии Windows NT по той причине, что номера этих самых функций (im1111 спасибо за Functions.h) меняются от релиза к релизу или даже после установки очередного сервис-пака. Или мы проверяем версию и корректно устанавливаем драйвер или же система вылетает в БСОД. Перехватчик, написанный Свеном Шрайбером и находящийся на компакте к его книжке, прекрасно работал в Win2k, но приводит к BSOD в Windows XP. Чего и следовало ожидать- номера функций поменялись. Но, уже много лет как существуют утилиты FileMon и RegMon от Марка Руссиновича, которые прекрасно работали и в Windows NT 4.0 и в Win2k и в WinXP. Насколько я знаю, эти программы также используют тактику перехвата Native API, но неужели за столько лет не изменился ни один номер функции? Или же эти проги используют какую-то другую методику перехвата? Точно уверен, что бинарник был один и тот же, т.е. код драйвера не переписывался
Посмотри в дизассемблере код любой функции с префиксом Zw*. Там есть инструкция "mov eax,<number>". number - номер сервиса в sdt. Простейший дизассемблер - и не надо хардкодить никакие номера.
Да, смотрел А в мониторах Марка Руссиновича именно такой способ применялся? Т.е. по имени функции я ищу её адрес, потом нахожу инструкцию "mov eax,<number>", вытаскиваю оттуда number, независимо от версии? У Шрайбера это, кстати, тоже написано, но вот не помню, почему он такой подход отбросил... А эта инструкция всегда стоит первой по счету? Если не так, то придется писать полный дизасм 386го, что врядли можно назвать тривиальной задачей
Да (Win64 не в счет). Кроме того, можно проверять по опкоду "mov eax,immediate". Да и вообще зачем писать свой дизассемблер, когда чужих полно
Скажите тогда вот что: метод дизассеблирования функции с целью определения ее номера, он реально применялся кем-нибудь? Ну, Вами, например Просто хочется узнать, реально ли это на практике осуществить и стоит ли за этот дизассемблер браться. Ибо других надежных способов на моей памяти что-то нет Может быть, в статьях на wasm.ru про перехват уже все про это написано, но я, честно сказать, туда еще не смотрел... Пока что руководствуюсь мутными воспоминаниями пятилетней давности =)