# CVE2015-0057漏洞样本构造探索

Author: 乌云历史资料库 (@wooyun_archive)
Published: 2015-02-15T02:15:00Z
Canonical: https://wepostx.com/topics/715

> 乌云历史资料归档
>
> **原始作者：** cssembly
> **原始编号：** superkieran-wooyundrops:473
> **原始发布时间：** 2015-02-15 10:15
> **声明：内容仅用于技术研究和个人使用，版权归 wooyun.org。**

---

# 0x00 前言

微软最新的补丁包修补了CVE2015-0057的提权漏洞，同一天，漏洞的发现者发表了分析文章《One-Bit To Rule Them All: Bypassing Windows’ 10 Protections using a Single Bit》，看完文章，想尝试构造一下样本，原本以为很简单，结果期间遇到了几个问题，分享出来，希望能与大家一起讨论。

# 0x01 分析

由于分析文章里提到漏洞是由xxxEnableWndSBArrows引起的，就通过CreateWindowEx创建ScrollBar，然后调用EnableScrollBar，执行到xxxDrawScrollBar，按照分析文章里的说明，完整流程如下：

![原文图片](/media/2016/06/689bf39751f01cd1bccc09bff8fa5d95)

结果发现可以执行到xxxGetColorObjects，但是总是没法走到xxxDefWindowProc，因为(*((_WORD *)P + 0x15) & 0x3FFF) == 0x29A总是成立，最后google了一下，发现这里是判断当前窗体是不是ScrollBar。

![原文图片](/media/2016/06/ef365a392a95fd0692d7009617591f6e)

*((_WORD *)P + 0x15)表示FNID，是通过NtUserSetWindowFNID在创建窗体时设置的，可以在reactos的代码中看到，windows包含了如下的FNID。

```text
#!c++ +// FNID's for NtUserSetWindowFNID +#define FNID_BUTTON 0x02A1 +#define FNID_COMBOBOX 0x02A2 +#define FNID_COMBOLBOX 0x02A3 +#define FNID_DIALOG 0x02A4 +#define FNID_EDIT 0x02A5 +#define FNID_LISTBOX 0x02A6 +#define FNID_MDICLIENT 0x02A7 +#define FNID_STATIC 0x02A8 +#define FNID_IME 0x02A9
```
NtUserSetWindowFNID中，可以看到这里会对(_WORD *)(v2 + 0x2A)处的值进行设置，(_WORD *)P + 0x15与等价(_WORD *)(v2 + 0x2A)。

![原文图片](/media/2016/06/450ba3c236a16b6ca474debd5749213e)

看来直接通过创建ScrollBar是不能执行到xxxDefWindowProc的。通过内核调试器，在此处设置断点，在屏幕上点动各种窗体，当打开任务管理器，点击显示所有用户进程时，触发了断点。查看此时的调用栈，看到了CListView！！！看来可能可以通过MFC的CListCtrl来触发xxxDefWindowProc函数的执行。

![原文图片](/media/2016/06/74cb2cdff692d0d42e18b0a9c1c99828)

重载了CListCtrl，当ListCtrl显示的东西过多时，就会出现ScrollBar，这样在CListCtrl里处理一下afx_msg void OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)消息，就能够处理CListCtrl里的ScrollBar的消息，进而释放掉ScrollBar窗体。当点击CListCtrl里的横向ScrollBar时，就会触发CListCtrlEx::OnHScroll，这次再调用EnableScrollBar，就能执行到xxxDefWindowProc函数。

```text
#!c++ void CListCtrlEx::OnHScroll( UINT nSBCode, UINT nPos, CScrollBar* pScrollBar ) { hScroll = this->m_hWnd; BOOL bEnable = ::EnableScrollBar( this->m_hWnd , SB_HORZ , ESB_DISABLE_BOTH ); CWnd::OnHScroll(nSBCode, nPos, pScrollBar); }
```

![原文图片](/media/2016/06/ad3099dc1b5b9482849e2d0b521efcf9)

不过又遇到了新的问题，signed int __stdcall xxxLoadUserApiHook()函数里总是没法执行到xxxLoadHmodIndex，显然这个函数是与UserApiHook相关的，(1 << gihmodUserApiHook) & (_DWORD *)(((_DWORD *)gptiCurrent + 46) + 0xBC)的条件总是成立，好吧，断点设置在win32k!xxxLoadUserApiHook+0x2b处，结果程序启动的时候，加载user32.dll，调用InitUserApiHook，最终会执行几次xxxLoadHmodIndex，之后就不再调用了。

![原文图片](/media/2016/06/3e46121b80c365fe2334f870b92bb026)

![原文图片](/media/2016/06/4076e8d7adc620272127139d9b4d39e8)

重新看了一下分析文章，核心就是在用户态回调时，释放掉窗体，因此只要找到另一条路径，在xxxEnableWndSBArrows返回前，将窗体释放就可以了。在xxxDefWindowProc里，会调用到SfnDWORD，该函数中就存在一个回调，如下图。KeUserModeCallback的第一个参数2表示的是ApiNumber，也就是说最终会通过用户态的KiUserCallbackDispatcher调用USER32!__fnDWORD函数，如果能够在调用该函数的时候，将窗体释放掉，也是能够达到触发漏洞的效果的。

![原文图片](/media/2016/06/3ac153e2932b4b926faa1682889da5f7)

定义一个forFree函数，在KeUserModeCallback回调到USER32!__fnDWORD时，调用这个函数将窗体对象释放掉，第一次调用DestroyWindow( hScroll )释放窗体，第二次则对窗体是否被释放进行验证。通过在USER32!__fnDWORD处调用forFree，可以看到调用EnableScrollBar最终触发了forFree执行。注意USER32!__fnDWORD会被频繁调用，因此要进行区分。

```text
#!c++ void forFree() { DestroyWindow( hScroll ); DestroyWindow( hScroll ); return ; }
```

![原文图片](/media/2016/06/880bfcc9e7fc78ce7a13686d2128563d)

第一次调用DestroyWindow后，可以看到函数执行成功，返回值eax=1。再次调用DestroyWindow后，win32k!ValidateHwnd就无法根据hScroll的值来获取到对应的内核窗体结构，返回值eax=0。从xxxDefWindowProc返回之后，实际上窗体已经不再与之前传递给EnableScrollBar的句柄对应了。

![原文图片](/media/2016/06/4c1ae1a485c5b6f35e0dd60488eab6c4)

![原文图片](/media/2016/06/a5d440546e89160f9b9fd4f1e28e24b1)

不过我在32位win7上，并没有找到分析文章里提到的OR操作和AND操作。

![原文图片](/media/2016/06/0f0bfba6cc1174eedb47ca3630d41e47)

# 0x02 结语

上面就是我对CVE2015-0057漏洞样本构造过程的探索，与分析文章文章里的内容存在差异，有什么不对的地方，希望能与大家交流，我的邮箱：[email protected]

## Replies
