VX Heaven

Library Collection Sources Engines Constructors Simulators Utilities Links Forum

Adding LDT entries in Win2K


[Back to index] [Comments]

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:

descriptor bitfieldsize,bitspossible valuespossible
  (*) valuetype
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.


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.


  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.


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 MySetLDTSelectorEntry2(DWORD dwSelector,
                             LPLDT_ENTRY lpSelectorEntry)
    return NtSetLdtEntries(dwSelector,

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;
      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;


  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;
By accessing, viewing, downloading or otherwise using this content you agree to be bound by the Terms of Use! aka