关联漏洞
            
        
            描述
            Proof of Concept CVE-2025-24990 (Agere Systems's driver) 
        
        
            介绍
            Windows Agere Modem Driver (`ltmdm64.sys`). This driver is very old and is not loaded by default on my test machine, so I will exploit it in a BYOVD scenario. Interestingly, according to my research this driver has existed on Windows 7 and has at least one bug. [here](https://github.com/int0/ltmdm64_poc)

At that time, MSRC didn't take any action 🤡
So I found a new the bug and decided to report it to ZDI, but they hadn't reviewed it yet. It was patched in October (CVE-2025-24990), about one month after I reported it, and I had no knowledge of this. Funny is, the bug is old, I reported it, but someone else patched it shortly after the report and labeled it as an "exploit-in-the-wild." 🤡
Btw, I think it's CWE-781. not [CWE-822](http://cwe.mitre.org/data/definitions/822.html)
## Vulnerabilities
Some IOCTLs within this driver use `METHOD_NEITHER` but do not check whether the address buffer supplied by the caller is from user-mode or kernel-mode. Here is an example IOCTL code that I decoded with [OSR](https://www.osronline.com/article.cfm%5Earticle=229.htm) :

This means you can supply a kernel address to the `DeviceIoControl` API and the driver will handle it normally.
Note that you have to bypass kASLR first to leak the kernel address, I will use [EnumDeviceDrivers](https://learn.microsoft.com/en-us/windows/win32/api/psapi/nf-psapi-enumdevicedrivers) (On Windows 24h2 you need SeDebugPriv to do this).

## Null Dereference
The issue is in IOCTL `0x802b200f` (`ud_response`). Again, this IOCTL dispatch does not validate the address I supply from user-mode, but I will leverage it later.
  
The **`ud_response`** calls **`ll_load_diagnostics`**, and I will reach the following code:

At the beginning the global variable **`eeprom`** isn't initialized, so it will contain `NULL`. Here is a simple code that will trigger this.

I will leverage this later.
## Exploit entry point 0x802b2003

This IOCTL simply converts the driver version string `"8.36"` to the number `0x836` (a `DWORD`) and writes it to the address supplied by the caller (thanks to `METHOD_NEITHER`). Technically, I can write these four bytes (`36 08 00 00`) to an arbitrary kernel address. I will leverage this to overwrite the driver’s global variables and change the execution flow. 
I will call this 0x802b2003 is `IOCTL_GET_VERSION`
## Exploit
**Arbitrary null 1 byte:**
Going back to the NULL-dereference case, I use the `VirtualAlloc` API to allocate a fixed address (`0x083600000000`). I then use the `IOCTL_GET_VERSION` to write to `*(eeprom + 4)` the four bytes described above. When the driver later dereferences `eeprom`, it will read from the address I allocated.
After fix the NULL dereference, the IOCTL writes a string to the address I supply from user-mode, based on the buffer size.

This code simply demonstrates what I described above, allocate a buffer and fill it with `0xAA`, fix the NULL dereference, then call the driver. Note that I allocate 11 bytes but only provide a buffer size of 10 to the driver to see how it behaves.

It writes a fixed sequence of bytes to my buffer and then nulls out the final byte (the 11th), even though I only provide a size of 10. It replaces the last `0xAA` in my buffer with `0x00`. This indicates that if I provide a size of `0`, the driver still writes a single `0x00` byte at the target address.
**Arbitrary decrement**
Now I have the null and arbitrary with fixed 4 bytes let craft other primitive. 

This IOCTL will set the global `LtMsgEvent` to my user buffer then check `WDM` is null then set zero again.

Then in 0x802b2207 it will call [ObfReferenceObject]() API.
At inital state the `WDM` is null but with the help of `IOCTL_GET_VERSION` I can set `WDM` to `0x36` (it's size only 1 byte) and the `LtMsgEvent` is still my buffer. Then I will null out `WDM` and call 0x802b2207. Finally reach the ObfReferenceObject. I will call this two ioctl is `IOCTL_SET_LtMsgEvent` and `IOCTL_DEREF_LtMsgEvent`.
The exploit technique using `ObfReferenceObject` changes the `PreviousMode` of our `KTHREAD` from `UserMode` to `KernelMode` you can read about it [here]((https://hnsecurity.it/blog/from-arbitrary-pointer-dereference-to-arbitrary-read-write-in-latest-windows-11/)). However, Windows has fixed this exploit, so we cannot use it.
But the primitive in `ObfReferenceObject` still exists. The API subtracts `0x30` from the address we provide, casts the result to an 8-byte integer, and then subtracts 1.
>     *(signed long long)(LtMsgEvent-0x30) -= 1

But the problem is it checks whether the next value is `0` or whether the current value is `< 1` (interpreted as an 8-byte signed integer). If either condition is true, it jumps to `KeBugCheckEx` and crashes the system.
**Arbitrary write**
With Arbitrary decrement in hand I need to find somewhere else to write the byte `0xFF` then decreate it to the byte I want and I found this ioctl `0x802b2243`:

We will focus on the `flip` branch. `pbVar5` is the address I supply from user-mode and can be any target address I choose. I write the byte `0x0C` to `DAT_TARGET_EX` (with the help of `IOCTL_GET_VERSION` and the arbitrary-decrement primitive), and I also null out 1 byte at the target address. The first call to this IOCTL sets `0xC0` at the target address, which is then decremented to `0xBF`. A second call sets `0xFF` at the target address (`0xBF | 0xC0 = 0xFF`). Once the target contains `0xFF`, I just decrement it to the desired value.
I will write one byte one and be careful of the `KeBugCheckEx` in`ObfReferenceObject` . 
**Arbitrary read**
For the read primitive I use the technique described [here]([Mastodon](https://exploits.forsale/pwn2own-2024/)) ([@carrot_c4k3](https://x.com/carrot_c4k3)). I simply overwrite the `UNICODE_STRING` object in the kernel (`ExpManufacturingInformation`) and then call `NtQuerySystemInformation`. Because of `ObfReferenceObject` calls `KeBugCheckEx`, I will null out 8 bytes adjacent to `ExpManufacturingInformation`.
That’s it, now we have arbitrary R/W, we can use those primitives to do many things. The driver is not loaded by default, so I will exploit it in a BYOVD scenario and set the PPL of a process.
## Exploit in Windows 11 22H2+:
The exploit I have decribe above is work in all windows version but unstable due to the `KeBugCheckEx`. But In Windows 11 22h2+ there is a technique called [ioring](https://windows-internals.com/one-i-o-ring-to-rule-them-all-a-full-read-write-exploit-primitive-on-windows-11/). This technique simply overwrites `ioring->Buffer` with a controllable address. Concretely, we can overwrite `ioring->Buffer` and its size with `0x083600000000` and `0x836` respectively (using `IOCTL_GET_VERSION`). Using this technique I only perform 2 write and then use the R/W primitive very stably. Note that this approach requires leak kernel address.
# Exploit Run
The exploit will use ioring technique to turn off PPL of lsass.exe and use my data-only technique to set PPL to notepad.exe
https://github.com/user-attachments/assets/05a35b38-d26c-484f-9fb7-137f8fe8c079
# Disclosure
I reported this bug to ZDI on 22 September, but it was patched in October ([@0xLegacyy](https://x.com/0xLegacyy)). I guess I was slower A LITTLE BIT. :)
        
        文件快照
        
            
                
 [4.0K]  /data/pocs/ac2f66a3967b1af2087e7e8d9b22abed072b05bd
├── [4.0K]  pics
│   ├── [ 25K]  pic10.png
│   ├── [9.0K]  pic11.png
│   ├── [ 20K]  pic12.png
│   ├── [ 12K]  pic13.png
│   ├── [100K]  pic1.png
│   ├── [ 14K]  pic2.png
│   ├── [ 30K]  pic3.png
│   ├── [ 12K]  pic4.png
│   ├── [ 11K]  pic5.png
│   ├── [ 28K]  pic6.png
│   ├── [ 25K]  pic7.png
│   ├── [ 44K]  pic8.png
│   └── [9.0K]  pic9.png
├── [7.5K]  README.md
├── [4.0K]  src_data_only
│   ├── [4.0K]  capstone-4.0.2-win64
│   │   ├── [5.3M]  capstone.dll
│   │   ├── [5.2K]  capstone_dll.lib
│   │   ├── [7.0M]  capstone.lib
│   │   ├── [ 20K]  ChangeLog
│   │   ├── [2.6K]  CREDITS.TXT
│   │   ├── [5.4M]  cstool.exe
│   │   ├── [4.0K]  include
│   │   │   ├── [4.0K]  capstone
│   │   │   │   ├── [ 28K]  arm64.h
│   │   │   │   ├── [ 18K]  arm.h
│   │   │   │   ├── [ 29K]  capstone.h
│   │   │   │   ├── [4.3K]  evm.h
│   │   │   │   ├── [ 12K]  m680x.h
│   │   │   │   ├── [ 14K]  m68k.h
│   │   │   │   ├── [ 17K]  mips.h
│   │   │   │   ├── [3.9K]  platform.h
│   │   │   │   ├── [ 25K]  ppc.h
│   │   │   │   ├── [ 11K]  sparc.h
│   │   │   │   ├── [ 14K]  systemz.h
│   │   │   │   ├── [8.1K]  tms320c64x.h
│   │   │   │   ├── [ 42K]  x86.h
│   │   │   │   └── [4.8K]  xcore.h
│   │   │   ├── [3.1K]  platform.h
│   │   │   └── [4.0K]  windowsce
│   │   │       ├── [ 315]  intrin.h
│   │   │       └── [2.9K]  stdint.h
│   │   ├── [3.3K]  LICENSE_LLVM.TXT
│   │   ├── [1.6K]  LICENSE.TXT
│   │   ├── [2.1K]  README.md
│   │   ├── [ 177]  RELEASE_NOTES
│   │   └── [ 857]  SPONSORS.TXT
│   ├── [ 214]  common.h
│   ├── [ 428]  debug.h
│   ├── [4.8K]  demo.cpp
│   ├── [5.2M]  demo.exe
│   ├── [4.5K]  exploit.cpp
│   ├── [ 516]  exploit.h
│   ├── [7.4K]  utils.cpp
│   └── [ 586]  utils.h
└── [4.0K]  src_ioring
    ├── [ 177]  common.h
    ├── [ 428]  debug.h
    ├── [ 834]  exploit.cpp
    ├── [ 241]  exploit.h
    ├── [ 11K]  io_ring.cpp
    ├── [9.6K]  io_ring.h
    ├── [5.6K]  main.cpp
    ├── [1.7K]  ntdll.h
    ├── [5.8K]  utils.cpp
    └── [ 869]  utils.h
7 directories, 60 files
                
             
         
        备注
        
            
                1. 建议优先通过来源进行访问。
                2. 如果因为来源失效或无法访问,请发送邮箱到 f.jinxu#gmail.com 索取本地快照(把 # 换成 @)。
                3. 神龙已为您对POC代码进行快照,为了长期维护,请考虑为本地POC付费,感谢您的支持。