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
Код: Загрузи драйвер!
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