
                        ADDING LDT ENTRIES IN WIN2K
                        ---------------------------

     I  was  surprised when found that win2k allows programs to add own LDT
 entries.  When  first  entry is added, LDT for current process is created,
 with minimal possible size to contain this entry. I.e. if you will add one
 descriptor  at  LDT  offset  16  (selector  0F), total LDT size will be 24
 bytes, and previuous unused entries will be empty.

     So,  there is (at least) 3 methods to add own LDT entries. All of them
 are  called  in  ring3,  but  real  action is performed in the ntoskrnl in
 ring0.  The  only  problem  is that this ring0 code validates descriptors,
 allowing only the following conditions:

     - base < 7FFF0000, limit < 7FFF0000, base+limit <= 7FFF0000
     - Code/Data 16/32-bit descriptors
     - DPL=3

        descriptor  size,  possible         possible
         bitfield   bits    values       (*) value   type

        Base         32   0..7FFEFFFF        16,17  Data RO
        Limit        20   0..7FFEFFFF        18,19  Data RW
        Granularity   1       0,1            20,21  Data RO ED
        Default_Big   1       0,1            22,23  Data RW ED
        Reserved_0    1       0,1            24,25  Code EO
        Sys           1       0,1            26,27  Code RE
        Pres          1       0,1
        Dpl           2        3
        Type          5       (*)

     But, there is a little difference: first method of setting LDT entries
 uses  one  descryptor-verifying  subroutine,  but second and third methods
 uses  another  one;  this  results  in  different restrictions to Pres bit
 (presence  of  the  descriptor), and, using 1st method, with this bit set,
 you can put into LDT less restricted descriptors.

     And  here  is the idea, which made me writing this text. When you call
 some  system  INT,  some  ring0  handlers doesnt change DS/ES selectors to
 0x23 (std NT r0 dataseg). They assume that default DS selector has Base=0.
 Other  handlers  assume  CS = 0x1B (std NT r3 codeseg), and, if it is not,
 works  in  some  different  way.  This all may probably result in entering
 ring0,  but  i  didnt found how to do it, since system handlers works with
 memory  >  2G,  and  descriptor  bases/limits are limited to be < 2G. But,
 anyway, maybe you will invent something.

                          READING GDT DESCRIPTORS

     This  is trivial, and win32 api allows it. Reading GDT descriptors can
 be  used, for example, to find out FS base in the different contexts, when
 debugging processes and exception is occured.

  BOOL KERNEL32.GetThreadSelectorEntry( HANDLE hThread,
                                        DWORD dwSelector,
                                        LPLDT_ENTRY lpSelectorEntry );

     This  function  will  just  calls  NTDLL.NtQueryInformationThread with
 infoclass 12.

                          READING LDT DESCRIPTORS

  extern "C" // NTDLL.DLL
  int WINAPI NtQueryInformationProcess(HANDLE,DWORD,VOID*,DWORD,DWORD*);

  int MyGetLDTSelectorEntry1(HANDLE hProcess,
                             DWORD dwSelector,
                             LPLDT_ENTRY lpSelectorEntry)
  {
    DWORD buf[4];
    DWORD len;
    buf[0] = dwSelector & 0xFFFFFFF8;  // selector --> offset
    buf[1] = 8;                    // size (multiple selectors may be added)
    int res = NtQueryInformationProcess(hProcess,10,buf,16,&len);
    memcpy(lpSelectorEntry, &buf[2], 8);
    return res;
  }

     Return  values  is of type NTSTATUS, which is 0 if OK, and Cxxxxxxx if
 error,  for  example  C000011A  is  error  for  invalid  descriptor. 10 is
 ProcessLdtInformation information class.

                          WRITING LDT DESCRIPTORS

                                  Method 1

     This is the best method, as i think.

  extern "C" // NTDLL.DLL
  int WINAPI NtSetInformationProcess  (HANDLE,DWORD,VOID*,DWORD);

  int MySetLDTSelectorEntry1(HANDLE hProcess,
                             DWORD dwSelector,
                             LPLDT_ENTRY lpSelectorEntry)
  {
    DWORD buf[4];
    buf[0] = dwSelector & 0xFFFFFFF8;  // selector --> offset
    buf[1] = 8;                        // size (multiple selectors may be added)
    memcpy(&buf[2], lpSelectorEntry, 8);
    return NtSetInformationProcess(hProcess,10,buf,16);
  }

                                    Method 2

  extern "C" // NTDLL.DLL
  int WINAPI NtSetLdtEntries(DWORD, DWORD, DWORD, DWORD, DWORD, DWORD);

  int MySetLDTSelectorEntry2(DWORD dwSelector,
                             LPLDT_ENTRY lpSelectorEntry)
  {
    return NtSetLdtEntries(dwSelector,
                           *(DWORD*)lpSelectorEntry,
                           *(((DWORD*)lpSelectorEntry)+1),
                           0,0,0);
  }

                                    Method 3

     This  method  is  illustration of the idea i talked before: when CS is
 0x1B,  it  doesnt  works.  When  CS  is  changed  to something else (using
 previuos methods :-), it will work fine.

  int MySetLDTSelectorEntry3(DWORD dwSelector,
                             LPLDT_ENTRY lpSelectorEntry)
  {
    if (_CS == 0x1B) return 0xC0000000;
    asm
    {
      push    ebp
      push    ebx
      mov     ebx, dwSelector     // EBX = offset in LDT
      and     bl, 0F8h
      mov     edx, lpSelectorEntry
      mov     ecx, [edx+0]        // ECX = descriptor.dword ptr 0
      mov     edx, [edx+4]        // EDX = descriptor.dword ptr 4
      mov     eax, 0F0F0F0F1h     // EAX = F0F0F0F1
      mov     ebp, eax            // EBP = EAX
      int     2Ah
      pop     ebx
      pop     ebp
    }
    return _EAX;
  }

                               USAGE EXAMPLE

  LDT_ENTRY l1;
  DWORD base  = 0x00000000;
  DWORD limit = 0x7FFEFFFF;
  l1.BaseLow                   = base & 0xFFFF;
  l1.HighWord.Bytes.BaseMid    = base >> 16;
  l1.HighWord.Bytes.BaseHi     = base >> 24;
  l1.LimitLow                  = (limit >> 12) & 0xFFFF;
  l1.HighWord.Bits.LimitHi     = limit >> 28;
  l1.HighWord.Bits.Granularity = 1;    // 0/1, if 1, limit=(limit<<12)|FFF
  l1.HighWord.Bits.Default_Big = 1;    // 0=16bit  1=32bit
  l1.HighWord.Bits.Reserved_0  = 0;    // 0/1
  l1.HighWord.Bits.Sys         = 0;    // 0/1
  l1.HighWord.Bits.Pres        = 1;    // 0/1 (presence bit)
  l1.HighWord.Bits.Dpl         = 3;    // only 3 allowed :-(
  l1.HighWord.Bits.Type        = 27;   // [16..27]

  MySetLDTSelectorEntry1((HANDLE)-1, 0x0F, &l1);

  MyGetLDTSelectorEntry1((HANDLE)-1, 8, &l1);

  MySetLDTSelectorEntry2(0x17, &l1);

  _CS = 0x0F;
  MySetLDTSelectorEntry3(0x1F, &l1);
  _CS = 0x1B;

                                   * * *

                      (x) 2002 http://z0mbie.host.sk/
       