Исполнение user-mode кода в ring0 (win9)
От: Andrew S Россия http://alchemy-lab.com
Дата: 13.12.02 16:35
Оценка:
Иногда бывает необходимо выполнить несколько привелигерованных инструкций из юзер-моде, например, 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х под рукой уже давно нет, но метод этот работал, да и работает по сей день, а ведь главное идея, верно
Всем спасибо за внимание и успехов.
http://www.rusyaz.ru/pr — стараемся писАть по-русски
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.