Search code examples
reverse-engineeringwindbgexploitwindows-kernelmsr

After modifying msr[lstar], why the expected breakpoint cannot be hit?


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


Solution

  • 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:

    1. Swap user GS segment for kernel GS.
    2. keep track of the user stack (so it can be restored when returning from kernel before SYSEXIT).
    3. Set up kernel page table (CR3 / PML[4|5] base).
    4. Set up the kernel stack.

    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:

    • At the very start of the "normal" routine (nt!KiSytemCall64[Shadow])
    • In your new routine (but haven't done any of the necessary stuff that the "normal" routine do.

    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:

    • GS user-mode is way smaller than the kernel-mode one (thus you're probably accessing whatever data is there; the kernel probably doesn't deal well with non-sensical data).
    • This page is probably not mapped.
    • SMEP prevents you from accessing user-mode addresses from kernel.

    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.