Диалог кода с Win XP: Код: Загрузи драйвер! XP: ОК! Диалог кода с Win Vista: Код: Загрузи драйвер! Vista: не буду ( Код: Можно загрузить драйвер? Vista: ну, можно.. Код: Загрузи драйвер! Vista: ОК! (из наблюдений) 00:begin В предыдущем топике я что-то сказал об идее поиска KeServiceDescriptorTableShadow. И как-то код взял и написался... 10:interface Напомню, в чём состояло предложение: ищем в тибе негуишного треда оффсет ServiceTable за счёт сравнения всей структурки с известной нам KeServiceDescriptorTableОбычнойСОченьДлиннымНазванием, потом переводим поток в гуи и читаем по найденному оффсету, где уже находится указатель на KeServiceDescriptorTableShadowСЕщёБолееДлиннымНазваниемЧемВыше 20:implementation От слов к делу. Начнём с драйвера. Он будет принимать IOCTL запросы и выводить на них оффсет ServiceTable либо адрес одной из интересующих нас таблиц. Для начала напишем код нахождения оффсета. А чтобы не сканировать многократно структурку, запишем его в структуру расширения устройства. Ниже я опускаю всякие проверки, но это всё есть в прилагаемом исходнике. Code: case IOCTL_FIND_OFFSET: // // Ищем офсет ServiceTable в ETHREAD // // тут проверка переданных параметров //указатель на буфер результата res = (ULONG *)Irp->AssociatedIrp.SystemBuffer; //если ещё не искали оффсет, то там лежит -1, который записали в DriverEntry if (deviceExtension->ServiceTableOffset == -1) { //основной код сканирования на предмет KeSDT _asm { push esi mov esi, fs:[0x124] //esi = ETHREAD push eax mov eax, KeServiceDescriptorTable push ecx mov ecx, 0x200 //должно хватить - максимальный офсет find: //цикл поиска KeServiceDescriptorTable в ETHREAD cmp dword ptr [esi], eax jz found dec ecx jz not_found inc esi jmp find found: sub esi, fs:[0x124] //offset mov offs, esi //переменная оффсета jmp end not_found: mov offs, -1 end: pop ecx pop eax pop esi } //не нашли... if ( offs == -1 ) { *res = -1; DebugPrint(("\t\tOffset ServiceTable not found\n")); break; } deviceExtension->ServiceTableOffset = offs; } //всё ок! *res = deviceExtension->ServiceTableOffset; DebugPrint(("\t\tOffset ServiceTable 0x%x\n", deviceExtension->ServiceTableOffset)); //длина вывода OutBytes = 4; break; По коментариям должно быть ясно, что происходит. Теперь кусок кода, выводящий то, что записано по этому оффсету (взятого из DeviceExtension) : Code: case IOCTL_FIND_SSDT: // // Ищем KeServiceDescriptorTableShadow, зная офсет // //тут чекаем параметры //указатель на буффер результата res = (ULONG *)Irp->AssociatedIrp.SystemBuffer; //уже, вероятно, посчитанный офсет STOffset = deviceExtension->ServiceTableOffset; //а вдруг не посчитанный? if ( STOffset == -1 ) { status = STATUS_ACCESS_DENIED; break; } //собственно, читаем _asm { push eax mov eax, fs:[0x124] add eax, STOffset //ETHREAD->ServiceTable mov eax, [eax] //надеемся, SSDT push ecx mov ecx, res mov [ecx], eax //записываем найденное SSDT в буфер pop ecx pop eax } //вернули 4 байта - дворд OutBytes = 4; break; Ну и так, для проверки - вывод KeSDT обычной Code: case IOCTL_GET_SDT: // // Тут просто возвращаем KeServiceDescriptorTable // //чекаем параметры //указатель на буффер результата res = (ULONG *)Irp->AssociatedIrp.SystemBuffer; *res = (ULONG)KeServiceDescriptorTable; OutBytes = 4; break; На этом интересный код драйвера кончается. (На всякий случай, вышеприведённый код делался под спин-блокировкой). 30: Usermode, странность и баг в виндовском коде Начнём со странности. Вроде я где-то читал, что поток в винде создаётся вначале как негуишный. Так вот - это не совсем правда ) Не знаю почему, но почему-то, на XP SP3 у меня стартовый поток загружается как негуишный, а на чужом XP SP3 почему-то уже при старте оказывается подгруженным User32.dll. Так что основной код будет выполняться под CreateThread'ом - он вроде везде нормально работает. ==> А теперь пара ласковых слов об IsGUIThread По msdn'у эта функция должна проверить поток на гуишность (как от неё ожидается), а если ей передан параметр TRUE, то попытаться сделать его таковым. Но почему-то код с этой функцией работал криво (на XP SP3)...И её асм листинг отвечает на вопрос "почему": Code: MOV EAX,DWORD PTR FS:[18] //указатель на TIB LEA ESI,DWORD PTR DS:[EAX+6CC] //LEA!!! NEG ESI SBB ESI,ESI NEG ESI //а вдруг esi == 0? JNZ SHORT label ... label: mov eax, esi ret Но задаёт другой: "ну как так можно??" Это замечательный код проверяет, не указывает ли fs:[18] случайно на -0x6cc. А так редко бывает) Вывод - не юзайте эту функу. И ещё смешной момент - чтобы вызвать эту функцию, даже если бы она и работала, надо, чтобы к процессу была подгружена User32.dll, но которая в DllMain переводит вызывающий поток в гуй. Так что её (если бы она работала) имеет смысл использовать в одном потоке, только если она уже подгружена в другом. <== IsGUIThread Из-за сделанного замечания (а также из-за особенностей висты) main функция exe будет такой: Code: HANDLE hThread; //функция для того, чтобы дров грузился в висте - получаем привилегию грузить дрова (см. эпиграф) if (!SetLoadDriverPrivilege()){ printf("Couldn't set privilege.. Trying without\n"); } //почему-то иногда стартовый поток запускается как GUI, а это плохо. Поэтому делаем отдельный поток. hThread = CreateThread( NULL, // default security attributes 0, // use default stack size find, // thread function name NULL, // argument to thread function 0, // use default creation flags NULL); // returns the thread identifier //ждём, пока он кончится WaitForSingleObject(hThread, INFINITE); CloseHandle(hThread); _getch(); return 0; Теперь немного о реализации переход в гуи. IsGUIThread не работает. User32.dll бывает уже подгруженной и её DllMain второй раз в потоке не сработает. Так что же делать? А мы так поступим - при вызове некоторых (большинства, а вероятно и просто всех) функций USER32.DLL поток переводится в гуи. Так что можно воспользоваться любой подходящей функцией... Мне понравилась GetDesktopWindow без параметров. Code: DWORD WINAPI find( LPVOID lpParam ) /* Thread function */ { HANDLE hDevice; ULONG ulReturnedLength; DWORD errNum = 0; UCHAR driverLocation[MAX_PATH]; ULONG offset; //ServiceTable в ETHREAD BOOL bStatus; ULONG SDT_addr; ULONG SSDT_addr; PVOID user32func; //какая-нибудь функа для перхода в GUI /* это закомменчено из-за бажности апишки :( if (IsGUIThread(FALSE)){ printf("Is GUI. Terminating..\n"); return 1; } else { printf("Is not GUI. Well..\n"); } */ // // тут код, грузящий драйвер // и открывающий его в hDevice // получаем и записываем офсет ServiceTable из EPROCESS bStatus = DeviceIoControl( hDevice, // Handle to device IOCTL_FIND_OFFSET, // IO Control code NULL, // Input Buffer to driver. 0, // Length of input buffer in bytes. &offset, // Output Buffer from driver. sizeof(offset), // Length of output buffer in bytes. &ulReturnedLength, // Bytes placed in buffer. NULL // synchronous call ); if ( !bStatus ) { printf("Ioctl failed with code %d\n", GetLastError() ); } else { //именно -1 означало Not Found bStatus = offset != -1; } if ( !bStatus ) { printf("ServiceTable offset not found :(\n"); } else { printf("Found ServiceTable offset: 0x%x\n", offset ); /* bStatus = IsGUIThread(TRUE); - баг */ //поэтому переводимся в GUI вызовом простенькой гуишной функи типа GetDesktopWindow //она без параметров user32func = GetProcAddress(LoadLibraryA("user32.dll"), "GetDesktopWindow"); //чтобы не преобразовывать :) _asm call user32func; //надеюсь, тут багов не возникло :) bStatus = TRUE; } if ( !bStatus ) { //вообще это не выполнится... printf("Couldn't convert to GUI Thread: error code %d\n", GetLastError() ); } else { //Для справки SDT bStatus = DeviceIoControl( hDevice, // Handle to device IOCTL_GET_SDT, // IO Control code NULL, // Input Buffer to driver. 0, // Length of input buffer in bytes. &SDT_addr, // Output Buffer from driver. sizeof(SDT_addr), // Length of output buffer in bytes. &ulReturnedLength, // Bytes placed in buffer. NULL // synchronous call ); } if ( !bStatus ) { printf("Couldn't get normal SDT: error code %d\n", GetLastError() ); } else { //выведем printf("KeServiceDescriptorTable: 0x%.8x\n", SDT_addr ); //Кульминация - SSDT! bStatus = DeviceIoControl( hDevice, // Handle to device IOCTL_FIND_SSDT, // IO Control code NULL, // Input Buffer to driver. 0, // Length of input buffer in bytes. &SSDT_addr, // Output Buffer from driver. sizeof(SSDT_addr), // Length of output buffer in bytes. &ulReturnedLength, // Bytes placed in buffer. NULL // synchronous call ); } if ( !bStatus ) { printf("Ioctl failed with code %d\n", GetLastError() ); } else { printf("Found KeServiceDescriptorTableShadow: 0x%.8x\n", SSDT_addr ); } CloseHandle(hDevice); // // Выгружаем драйвер // return 0; } Ну вот: основной код закончен, детали реализации - в прилагаемом исходнике. 40: end Вот вроде и всё... Исходник готов, программа и драйвер скомпилены... И вроде работают) Вывод на XP SP3: Code: Found ServiceTable offset: 0xe0 KeServiceDescriptorTable: 0x80562520 Found KeServiceDescriptorTableShadow: 0x805624e0 Правильно... //а от микрософта такой пакости с IsGUIThread не ожидал. Спасибо всем принявшим участие в тестировании кода! 50: src + compiled (всё компилилось под WDK. Кстати, если кому интересно, загрузка драйвера реализована в двух вариантах - через SCM и ZwLoadDriver) Compiled: http://redxak.co.cc/ShadowSDT.rar Sources: http://redxak.co.cc/ShadowSDT_src.rar
Code: case IOCTL_FIND_OFFSET: Слуушай, а зачем ты через ioctl запросы смещение ищещь? Почему бы сразу в DriverEntry не получить? )) И не надо этого всего с подгруженной/неподгруженной user32.dll, извращения какие-то. Проверила с kd сейчас на xpsp3, ок Ну и чисто эстетически вынеси в отдельную функцию. Какую-нибудь ULONG GetServiceTableOffset(VOID) например. Сам кодес не качала. PS 5*(+) )
>>Слуушай, а зачем ты через ioctl запросы смещение ищещь? Почему бы сразу в DriverEntry не получить? )) Можно и так... Я не знаю, какому из способов отдать предпочтение) >>И не надо этого всего с подгруженной/неподгруженной user32.dll, извращения какие-то. В смысле не надо? подгрузить её всё равно следует, а извращения в подгрузке библиотеки я не вижу) PS куда красивый INDENT дели? ))
Я не про ту подгрузку. Я имела ввиду, что ты юзаешь CreateThread. И что в общем-то полаконичней будет сразу в DriverEntry получить нужный офсет. вообще не обязательно user32.dll. это может быть и gdi32.dll. важно просто вызвать сервис из win32k.sys. В той же gdi32 есть скажем вызовы какой-нибудь NtGdiDdUnlock. тогда в KiSystemService будет вызвана _PsConvertToGuiThread которая собственно и изменит указатель в KTHREAD.ServiceTable с KeServiceDescriptorTable на KeServiceDescriptorTableShadow
А я и не просил user32)) я просто предложил) Понятно, что надо бы как-нибудь из ядра _PsConvertToGuiThread вызывать, чтобы в юзермоде не лезть, но я не хотел заморачиваться