Иногда бывает необходимо выполнить несколько привелигерованных инструкций из юзер-моде, например, out. Очевидно, писать для этого драйвер как то неохота, не тот масштаб задач.
Как обычно, добрые люди из Майкрософт позаботились о том, чтобы мы это напрямую сделать не могли, и в целом это весьма правильно — исполнение кода приложений в нулевом кольце защиты может легко завалить систему. Однако не зря многие не считают win9х за систему — в ней существует множество досадных упущений. Одно из них (с исключением) использует WinCIH, другой, более простой и безопасный, однако, наверное, менее функциональный вариант я сейчас попытаюсь продемонстрировать. Сразу же оговорюсь, что идея эта не моя, я откопал это в исходниках
DiskId32, и присваивать авторство, естественно, не собираюсь.
Для начала определения необходимых структур:
#pragma pack(1)
struct GDT_DESCRIPTOR
{
WORD Limit_0_15;
WORD Base_0_15;
BYTE Base_16_23;
BYTE Type : 4;
BYTE System : 1;
BYTE DPL : 2;
BYTE Present : 1;
BYTE Limit_16_19 : 4;
BYTE Available : 1;
BYTE Reserved : 1;
BYTE D_B : 1;
BYTE Granularity : 1;
BYTE Base_24_31;
};
struct CALLGATE_DESCRIPTOR
{
WORD Offset_0_15;
WORD Selector;
WORD ParamCount : 5;
WORD Unused : 3;
WORD Type : 4;
WORD System : 1;
WORD DPL : 2;
WORD Present : 1;
WORD Offset_16_31;
};
struct GDTR
{
WORD wGDTLimit;
DWORD dwGDTBase;
};
#pragma pack()
Собственнно, кто программировал в защищенном режиме под дос с этими структурами хорошо знаком.
А теперь, собственно, сама pure идея.
bool CallRing0(PVOID pvRing0FuncAddr, DWORD dwParam1, DWORD dwParam2)
{
struct GDT_DESCRIPTOR *pGDTDescriptor;
struct GDTR gdtr;
WORD CallgateAddr[3];
WORD wGDTIndex = 1;
_asm Sgdt [gdtr]
// Skip the null descriptor
pGDTDescriptor = (struct GDT_DESCRIPTOR *)(gdtr.dwGDTBase + 8);
// Search for a free GDT descriptor
for (wGDTIndex = 1; wGDTIndex < (gdtr.wGDTLimit / 8); wGDTIndex++)
{
if (pGDTDescriptor->Type == 0 &&
pGDTDescriptor->System == 0 &&
pGDTDescriptor->DPL == 0 &&
pGDTDescriptor->Present == 0)
{
// Found one !
// Now we need to transform this descriptor into a callgate.
// Note that we're using selector 0x28 since it corresponds
// to a ring 0 segment which spans the entire linear address
// space of the processor (0-4GB).
struct CALLGATE_DESCRIPTOR *pCallgate;
pCallgate = (struct CALLGATE_DESCRIPTOR *) pGDTDescriptor;
pCallgate->Offset_0_15 = LOWORD(pvRing0FuncAddr);
pCallgate->Selector = 0x28;
pCallgate->ParamCount = 0;
pCallgate->Unused = 0;
pCallgate->Type = 0xc;
pCallgate->System = 0;
pCallgate->DPL = 3;
pCallgate->Present = 1;
pCallgate->Offset_16_31 = HIWORD(pvRing0FuncAddr);
// Prepare the far call parameters
CallgateAddr[0] = 0x0;
CallgateAddr[1] = 0x0;
CallgateAddr[2] = (wGDTIndex << 3) | 3;
// Please fasten your seat belts!
// We're about to make a hyperspace jump into RING 0.
_asm Mov EDX, [dwParam1]
_asm Mov EBX, [dwParam2]
// if we need more params just use ecx
// _asm Mov ECX, [..]
_asm Call FWORD PTR [CallgateAddr]
// We have made it !
// Now free the GDT descriptor
memset(pGDTDescriptor, 0, 8);
// Our journey was successful. Seeya.
return true;
}
// Advance to the next GDT descriptor
pGDTDescriptor++;
}
// Whoops, the GDT is full
return false;
}
Функция принимает 2 параметра — адрес функции и параметр. Очевидно, что т.к. тут адресное пространство линейно (т.е. одинаково что для ring3 что для ring0), параметром может быть и адрес некой структуры в user-mode.
Немного о том, как это работает. В вин9х разрешено исполнять инструкцию Sgdt. Это позволяет нам добыть адрес таблицы GDT и далее... да, верно, ее модифицировать. Все что нам надо — создать шлюз вызова с адресом нашей функции и воспользоваться им с селектором 0х28. Все.
А теперь об использовании. Тут и возникают некоторые.. хм... неудобства:
__declspec(naked) void Ring0SetPortVal()
{
// expect parameter in edx
_asm
{
mov al, bl
out dx,al
Retf
}
}
// example of use
CallRing0(((PVOID)Ring0SetPortVal, 0x20, 0x20);
Конечно, есть маааленький выход — можно объявить функцию как __fastcall и писать ее на C/C++, учитывая, что использовать мы сможем не более 2-х параметров.
Т.о., для вызова привелигированных инструкций под 9х необязательно писать драйвер, для простых функций можно воспользовться изложенной выше техникой.
За прямую работоспособность и компилируемость изложенного кода ручаться не могу, т.к. 9х под рукой уже давно нет, но метод этот работал, да и работает по сей день, а ведь главное идея, верно
Всем спасибо за внимание и успехов.