PDA

Просмотр полной версии : Поиск KeServiceDescriptorTableShadow и восклицание об IsGUIThread


desTiny
15.05.2009, 20:24
Диалог кода с Win XP:
Код: Загрузи драйвер!
XP: ОК!

Диалог кода с Win Vista:
Код: Загрузи драйвер!
Vista: не буду (
Код: Можно загрузить драйвер?
Vista: ну, можно..
Код: Загрузи драйвер!
Vista: ОК!

(из наблюдений)

00:begin
В предыдущем топике я что-то сказал об идее поиска KeServiceDescriptorTableShadow. И как-то код взял и написался...

10:interface

Напомню, в чём состояло предложение: ищем в тибе негуишного треда оффсет ServiceTable за счёт сравнения всей структурки с известной нам KeServiceDescriptorTableОбычнойСОчень ДлиннымНазванием, потом переводим поток в гуи и читаем по найденному оффсету, где уже находится указатель на KeServiceDescriptorTableShadowСЕщёБолееД линнымНазваниемЧемВыше

20:implementation
От слов к делу. Начнём с драйвера. Он будет принимать IOCTL запросы и выводить на них оффсет ServiceTable либо адрес одной из интересующих нас таблиц.

Для начала напишем код нахождения оффсета. А чтобы не сканировать многократно структурку, запишем его в структуру расширения устройства.
Ниже я опускаю всякие проверки, но это всё есть в прилагаемом исходнике.

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) :

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 обычной

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)...И её асм листинг отвечает на вопрос "почему":

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 будет такой:

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 без параметров.


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:

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

0x0c0de
31.05.2009, 09:39
case IOCTL_FIND_OFFSET:


Слуушай, а зачем ты через ioctl запросы смещение ищещь? Почему бы сразу в DriverEntry не получить? )) И не надо этого всего с подгруженной/неподгруженной user32.dll, извращения какие-то. Проверила с kd сейчас на xpsp3, ок

Ну и чисто эстетически вынеси в отдельную функцию. Какую-нибудь

ULONG GetServiceTableOffset(VOID)

например.
Сам кодес не качала.

PS 5*(+) )

desTiny
01.06.2009, 19:28
>>Слуушай, а зачем ты через ioctl запросы смещение ищещь? Почему бы сразу в DriverEntry не получить? ))

Можно и так... Я не знаю, какому из способов отдать предпочтение)


>>И не надо этого всего с подгруженной/неподгруженной user32.dll, извращения какие-то.

В смысле не надо? подгрузить её всё равно следует, а извращения в подгрузке библиотеки я не вижу)


PS куда красивый INDENT дели? ))

0x0c0de
01.06.2009, 20:09
Я не про ту подгрузку. Я имела ввиду, что ты юзаешь CreateThread. И что в общем-то полаконичней будет сразу в DriverEntry получить нужный офсет.


Так что же делать? А мы так поступим - при вызове некоторых (большинства, а вероятно и просто всех) функций USER32.DLL поток переводится в гуи. Так что можно воспользоваться любой подходящей функцией... Мне понравилась GetDesktopWindow без параметров.


вообще не обязательно user32.dll. это может быть и gdi32.dll. важно просто вызвать сервис из win32k.sys. В той же gdi32 есть скажем вызовы какой-нибудь NtGdiDdUnlock. тогда в KiSystemService будет вызвана _PsConvertToGuiThread которая собственно и изменит указатель в KTHREAD.ServiceTable с KeServiceDescriptorTable на KeServiceDescriptorTableShadow

desTiny
01.06.2009, 21:03
вообще не обязательно user32.dll. это может быть и gdi32.dll. важно просто вызвать сервис из win32k.sys.
А я и не просил user32)) я просто предложил) Понятно, что надо бы как-нибудь из ядра _PsConvertToGuiThread вызывать, чтобы в юзермоде не лезть, но я не хотел заморачиваться