I discovered a driver vulnerability that allows arbitrary modification of the msr register. A common attack scenario is to modify msr[lstar] to point it to the attacker's malicious code. Then, when the syscall instruction is executed, the malicious code execution can be triggered.
Of course, the above attack method requires ROP to bypass SMEP, so msr[lstar] points to a ROP gadget, but the ROP I constructed does not work properly, so I want to debug it.
But I found that once msr[lstar] is modified, the operating system will freeze and the debugger will not be able to catch any exceptions. What is the reason?
Test Methods:
0: kd> u FFFFF802325B4A87
nt!KeFlushCurrentTbImmediately+0x17:
fffff802`325b4a87 0f22e1 mov cr4,rcx
fffff802`325b4a8a c3 ret
fffff802`325b4a8b cc int 3
fffff802`325b4a8c 0f20d8 mov rax,cr3
fffff802`325b4a8f 0f22d8 mov cr3,rax
fffff802`325b4a92 c3 ret
fffff802`325b4a93 cc int 3
fffff802`325b4a94 cc int 3
0: kd> bp FFFFF802325B4A87
0: kd> bl
0 e Disable Clear fffff802`325b4a87 0001 (0001) nt!KeFlushCurrentTbImmediately+0x17
0: kd> rdmsr 0xC0000082
msr[c0000082] = fffff802`32624d40
0: kd> wrmsr 0xC0000082 fffff802`325b4a87
0: kd> rdmsr 0xC0000082
msr[c0000082] = fffff802`325b4a87
0: kd> g
I read some articles about msr exploitation, but none of them mentioned how to debug it. For example: https://idafchev.github.io/blog/wrmsr/#10-exploit-explanation https://slideplayer.com/slide/17845751/ https://www.unknowncheats.me/forum/general-programming-and-reversing/575107-kernel-debug-yes-yes.html
You can't set a breakpoint in the first few instruction of the routine pointed to by MSR LSTAR (0xc0000082) because the routine has a special handling concerning the GS segment.
The first instruction are as follows:
0: kd> vertarget
Windows 10 Kernel Version 22621 MP (4 procs) Free x64
Edition build lab: 22621.1.amd64fre.ni_release.220506-1250
Kernel base = 0xfffff802`4b400000 PsLoadedModuleList = 0xfffff802`4c0134a0
0: kd> rdmsr 0xc0000082
msr[c0000082] = fffff802`4bef61c0
0: kd> ln fffff802`4bef61c0
Browse module
Set bu breakpoint
(fffff802`4bef61c0) nt!KiSystemCall64Shadow
0: kd> u nt!KiSystemCall64Shadow
nt!KiSystemCall64Shadow:
fffff802`4bef61c0 0f01f8 swapgs ; swap user <-> kernel GS
fffff802`4bef61c3 654889242510a00000 mov qword ptr gs:[0A010h],rsp ; save user RSP in _KPCRB.UserRspShadow
fffff802`4bef61cc 65488b242500a00000 mov rsp,qword ptr gs:[0A000h] ; get kernel space CR3 from KPRCB.KernelDirectoryTableBase
fffff802`4bef61d5 650fba242518a0000001 bt dword ptr gs:[0A018h],1 ; shadow flags
fffff802`4bef61df 7203 jb nt!KiSystemCall64Shadow+0x24 (fffff802`4bef61e4)
fffff802`4bef61e1 0f22dc mov cr3,rsp ; set kernel PML4 base.
fffff802`4bef61e4 65488b242508a00000 mov rsp,qword ptr gs:[0A008h] ; get kernel base stack pointer
fffff802`4bef61ed 6a2b push 2Bh ; setup kernel segments...
Note: this might differ a bit for the non-spectre routine (the non-"shadow" one).
As you can see the first thing the kernel syscall landing point routine does is:
If, by any chance, you are able to replace the pointer kept by the MSR LSTAR, you'll need to do nearly the same thing otherwise it will crash / freeze the OS.
Now let say you try to put a BP:
nt!KiSytemCall64[Shadow]
)For the sake of the example, let take a software BP / 0xcc, that is, interrupt vector 3:
0: kd> !idt 3
Dumping IDT: fffff8024ec7c000
03: fffff8024bef42c0 nt!KiBreakpointTrapShadow
Let's look at the first few instruction executed when a soft. BP is hit (I'm taking the non-shadow which is a bit simpler):
0: kd> u nt!KiBreakpointTrap L10
nt!KiBreakpointTrap:
fffff802`4b8249c0 4883ec08 sub rsp,8
fffff802`4b8249c4 55 push rbp
fffff802`4b8249c5 4881ec58010000 sub rsp,158h
fffff802`4b8249cc 488dac2480000000 lea rbp,[rsp+80h]
First let's imagine you're still with a user-mode stack because you haven't exchange user-RSP for kernel-RSP, this is not going to end well with SMEP activated.
Somewhere later in the same interrupt vector 3:
0: kd> u nt!KiBreakpointTrap+0x81
nt!KiBreakpointTrap+0x81:
fffff802`4b824a41 0faee8 lfence
fffff802`4b824a44 65488b0c25a8950000 mov rcx,qword ptr gs:[95A8h]
fffff802`4b824a4d 4885c9 test rcx,rcx
fffff802`4b824a50 741f je nt!KiBreakpointTrap+0xb1 (fffff802`4b824a71)
fffff802`4b824a52 f3480f1eca rdsspq rdx
fffff802`4b824a57 654c8b1425a0950000 mov r10,qword ptr gs:[95A0h]
fffff802`4b824a60 4983c208 add r10,8
fffff802`4b824a64 493bd2 cmp rdx,r10
There are some accesses to the GS segment, but imagine that you haven't yet swapped the user-mode GS for the kernel-mode one. The kernel code is trying to access the GS base from user-mode!
Knowing that, you are facing multiple potential problems:
All in all, trying to set a breakpoint at the start of the system call routine (or replacing the syscall routine with your own without doing what's necessary) and you end up in some sort of in-between limbo: you're in kernel addresses, but not really in kernel and not really in user-land...
Another point to add is that, if I'm not mistaken, PatchGuard (PG) is looking at the MSR LSTAR (having a kernel debugger attached disable patchguard). So you also have to bypass PG for it to work.