Можно загрузить в своё адресное пространство персональную копию какой-либо dll (допустим advapi), пересчитать адреса импорта, и пользоваться ею вместо той, что была подгружена при запуске процесса. Вопрос такой: можно ли реализовать такую штуку в кернеле, и загрузить себе персональное ядро? Не с целью выполнять его код, а для того, чтобы иметь некий эталон для сравнения. И какие могут быть подводные камни? Если нет, то почему?
1) Какова основная цель? 2) Реализовать можно, особенно если не выполнять код. Если грузить код для сравнения, то можно загружать ядро как двоичный файл. В этом случае всё элементарно: CreateFile(), ReadFile(), CloseHandle() или что-нибудь аналогичное.
Можно загрузить (сэмулировав системный загрузчик) и настроить все импорты, релоки, защиту секций и пр. Может быть даже удастся выполнить какую-нибудь простенькую функцию. Можно как в ядре, так и в юзермоде, они в этом смысле ничем не отличаются. Еще даже флажок есть у LoadLibraryEx (см. MSDN) как раз для этих целей. Только действительно зачем? Эталон это конечно хорошо, но не надо забывать, что код возможно будет отличаться (из-за тех же релоков, например) а также нет проблем перехватить вызовы чтения файла и возвращать уже "исправленные" буфера, так чтобы память совпадала с образом на диске с целью скрыть изменения... А потом основные изменения вносятся (имхо) в область данных, а она в образе на диске отсутствует вообще, так что и сравнить будет не с чем. Ну не настраивать же ворох структур ядра заново! Можно ведь сделать, чтобы код совпадал побайтно, а функция все равно делала не то, что надо
Нужна таблица SDT (неискаженная) В принципе я это сделал в юзере, но из драйвера по-моему это значительно проще будет. Код выполнять нет необходимости, просто получить заведомо непатченные адреса вызовов, и по возможности проверить код, начинающийся с этих адресов на идентичность. Данные понятно что изменяются по ходу работы системы, с этим ничего не сделаешь, хотя можно попробовать перенастроить адреса.
Кстати, через LoadLibrary ещё и не всякая dll загрузится. С TLS-callback'ами не загружается. Далее, а запросы настоящего ядра ты игнорировать будешь? А копию ядра так просто не создашь, не уронив системы. Не все страницы в память загружены.
Ещё вопрос по теме: Если вызывать сервисы напрямую, минуя SDT, например так: Код (Text): push NULL push memSize push memPtr push SystemProcessesAndThreadsInformation mov ecx,8057A7BAh call ecx то при нахождении в контексте процесса system (например по DriverEntry), имеем на выходе STATUS_SUCCESS. Если вызывать таким способом по DeviceIoControl, то имеем ошибку STATUS_ACCESS_VIOLATION. Как я понимаю, из-за того, что находимся в контексте юзермодного процесса. Если причина неполучения доступа в этом, то как можно поправить это дело?
Насколько я понимаю, PrevMode указывает валидность буфера, переданного при I/O: the driver must be able to determine whether the caller's pointers are valid in user mode or kernel mode. Я же не пытаюсь записывать в буфер или читать из буфера, просто вызываю непосредственный адрес в ядре. Или я неправильно понял?
<font color="gray][ cresta</font><!--color--><font color="gray]: Как я понимаю, из-за того, что находимся в контексте юзермодного процесса. ]</font><!--color--> Нет. Если PreviousMode = UserMode, то NtQuerySystemInformation первым делом проверит, лежат ли переданные её буферы в юзермоде. В том числе и ReturnLength. Поставив SEH будет звать ProbeForWrite. А твой memPtr кажет, наверное, на ядерную память и не проходит EndAddress < MM_USER_PROBE_ADDRESS. Код (Text): VOID ProbeForWrite ( IN PVOID Address, IN ULONG Length, IN ULONG Alignment ) { ULONG_PTR EndAddress; ULONG_PTR StartAddress; . . . StartAddress = (ULONG_PTR)Address; if ((StartAddress & (Alignment - 1)) == 0) { EndAddress = StartAddress + Length - 1; if ((StartAddress <= EndAddress) && <font color="red] (EndAddress < MM_USER_PROBE_ADDRESS)) {</font><!--color--> . . . return; } else { <font color="red] ExRaiseAccessViolation();</font><!--color--> } . . . } } return; } <font color="gray][ cresta</font><!--color--><font color="gray]: Если причина неполучения доступа в этом, то как можно поправить это дело? ]</font><!--color--> PreviousMode временно поменять на ядерный. Насколько это безопасно - хз. Можно в отледьном WorkItem'е все получить и скопировать юзеру. Либо передевать из юзера буфер.
Four-F Если менять PreviousMode небезопасно, то наверное лучше для call XXXXXXXX сделать отдельный тред со своим чисто ядерным буфером, и в нем клацать таймером, заполняя буфер. А в DispatchControl если тред в данный момент не работает с буфером, скопировать буфер в переданную из юзера память. Если тред занят заполнением своего буфера, то взвести KeInitializeTimerEx и зациклить его, пока тред не отработает. Эта схема как, правильная или не очень?
Проще поручить эту работу системному рабочему потоку. Примерно так: Код (Text): typedef struct _WORKER_CONTEXT { PIO_WORKITEM pWorkItem; PVOID pBuffer; ULONG cbBuffer; KEVENT Event; BOOL bDataValid; } WORKER_CONTEXT, *PWORKER_CONTEXT; WORKER_CONTEXT Context; Context.pWorkItem = IoAllocateWorkItem( pDeviceObject ); if ( Context.pWorkItem ) { KeInitializeEvent( &Context.Event, NotificationEvent, FALSE ); Context.pBuffer = <указатель на буфер>; Context.cbBuffer = <размер буфера>; Context.bDataValid = FALSE; IoQueueWorkItem( Context.pWorkItem, Worker, DelayedWorkQueue, &Context ); KeWaitForSingleObject( &Context.Event, UserRequest, UserMode, FALSE, NULL ); if ( Context.bDataValid ) { // // Context.pBuffer содержит нужные данные // } } void Worker ( IN PDEVICE_OBJECT pDeviceObject, IN PWORKER_CONTEXT pContext ) { PAGED_CODE (); // // Находясь в контексте системного процесса и на IRQL == PASSIVE_LEVEL, // заполнить pContext->pBuffer нужными данными // if ( OK ) { pContext->bDataValid = TRUE; } IoFreeWorkItem ( pContext->pWorkItem ); KeSetEvent( &pContext->Event, IO_NO_INCREMENT, FALSE ); } Тут могут быть нюансы. Например, буфер может выделять сама процедура Worker, т.к. заранее может быть не известен его размер, как в твоём случае. А освобождать его будет основной код.
Спасибо, попробую этот вариант. Единственно, непонятно, что за PAGED_CODE(); такая? На асме это как, а то у меня нет си, чтобы посмотреть на код этой штуки.
Код (Text): #if DBG #define PAGED_CODE() \ { if (KeGetCurrentIrql() > APC_LEVEL) { \ KdPrint(( "EX: Pageable code called at IRQL %d\n", KeGetCurrentIrql() )); \ ASSERT(FALSE); \ } \ } #else #define PAGED_CODE() NOP_FUNCTION; #endif PAGED_CODE - это макрос. Используется на этапе разработки драйвера. Если функция, в которой он встретился будет вызвана на IRQL > APC_LEVEL, то ASSERT позовёт дебаггер. На асме что-то вроде этого: Код (Text): invoke KeGetCurrentIrql .if eax > APC_LEVEL invoke DbgPrint, $CTA0("Pageable code called at IRQL %d\n"), eax int 3 .endif Повторяю: только для дебага. Можешь просто забить на него. Worker Routines всегда вызываются на PASSIVE_LEVEL. Пример работы с системным рабочим потоком есть в KmdKit\examples\basic\WorkItem
Да я уже попробовал без этого макроса. Все работает. Замаппировал секцию, и прямо её адрес в ядро передал. Код (Text): Worker proc uses ebx pDeviceObject:PDEVICE_OBJECT, pContext: PTR WORKER_CONTEXT LOCAL hSection :HANDLE LOCAL oa :OBJECT_ATTRIBUTES LOCAL ptrSection :PVOID LOCAL ViewSize :LARGE_INTEGER LOCAL status :DWORD mov ebx,pContext assume ebx : ptr WORKER_CONTEXT mov status, STATUS_INVALID_DEVICE_REQUEST lea ecx, oa InitializeObjectAttributes ecx, offset g_usProcListSection, OBJ_CASE_INSENSITIVE, NULL, NULL invoke ZwOpenSection, addr hSection, SECTION_MAP_WRITE or SECTION_MAP_READ, addr oa .if (eax == STATUS_SUCCESS) mov ptrSection, NULL mov ViewSize.HighPart, 0 mov ViewSize.LowPart, 0 invoke ZwMapViewOfSection, hSection, NtCurrentProcess, addr ptrSection, 0, 10000000, NULL, addr ViewSize, ViewShare, 0, PAGE_READWRITE .if (eax == STATUS_SUCCESS) mov eax,ptrSection mov g_hMemProcThread,eax mov g_ProcThreadMemSize,10000000 call GetProcessList mov [ebx].bDataValid, TRUE mov status, STATUS_SUCCESS invoke ZwUnmapViewOfSection, NtCurrentProcess, ptrSection .endif invoke ZwClose, hSection .endif invoke IoFreeWorkItem, [ebx].pWorkItem invoke KeSetEvent, addr [ebx].Event, IO_NO_INCREMENT, FALSE assume ebx : nothing mov eax,status ret Worker endp Спасибо за помощь
Забыл спросить. Чему у тебя равен ptrSection. Я когда статью писАл, маленько поэкспериментировал с ZwMapViewOfSection. У меня получалось, что сколько раз её не зови, она всё время возвращает новый адрес, но только он кажет в юзерные адреса. Т.е. если находится в контексте юзерного процесса, то секция переотображается в юзера же и приходится ставить SEH. Интересно как ведёт себя ZwMapViewOfSection в контексте системного процесса. ЗЫ: 10000000... Хм. 2500 страниц... не многовато ли?
Для юзера вызов ZwMapViewOfSection всегда выдает адрес AA0000h. Для кернела в течение одной загрузки windows ZwMapViewOfSection выдаёт одинаковые адреса, например такие: 170000h, или 150000h. Перезапуск самого драйвера в течение одной загрузки windows адрес не меняет. 10000000... Хм. 2500 страниц... не многовато ли? Я даже думаю мало, надо бы до 20 мег увеличить В обычных условиях хватает памяти порядка 32-128 кБайт. Но у меня тут живет hxdef100 , и если действовать неосторожно, то он просекает, что кто-то пытается открыть его процесс, и каким-то образом влияет на размер памяти, запрашиваемой ZwQuerySystemInformation - небходимый для успешного вызова размер возрастает до 16 Мег. Чтобы не перевыделять память 10 раз подряд, сразу выделяю достаточное кол-во.
В продолжение темы... по ссылке от 90210 в исходниках есть такая процедура : getKiServiceTableAddr. И в комментариях к ней указано, что это "a more stable way to find the address of KiServiceTable". Если автор процедуры заглянет в топик, то к нему вопрос: чем это процедура стабильней "стандартного" способа с пересчетом, использованного, например в SDTrestore? "Стандартный" способ может дать неправильный результат? Или может кто-то ещё разбирался с этим вопросом, просветите пожалуйста.