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

Goal: 1000 CNY · Raised: 1310 CNY

100%

CVE-2025-24990 PoC — Windows Agere Modem Driver Elevation of Privilege Vulnerability

Source
Associated Vulnerability
Title:Windows Agere Modem Driver Elevation of Privilege Vulnerability (CVE-2025-24990)
Description:Microsoft is aware of vulnerabilities in the third party Agere Modem driver that ships natively with supported Windows operating systems. This is an announcement of the upcoming removal of ltmdm64.sys driver. The driver has been removed in the October cumulative update. Fax modem hardware dependent on this specific driver will no longer work on Windows. Microsoft recommends removing any existing dependencies on this hardware.
Description
Proof of Concept CVE-2025-24990 (Agere Systems's driver) 
Readme
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)

<p align="center">
  <img src="pics/pic1.png"/>
</p>
  
At that time, MSRC didn't take any action 🤡


## 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) :

<p align="center">
  <img src="pics/pic2.png"/>
</p>

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).

<p align="center">
  <img src="pics/pic3.png"/>
</p>

## 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.

<p align="center">
  <img src="pics/pic4.png"/>
</p>

The **`ud_response`** calls **`ll_load_diagnostics`**, and I will reach the following code:

<p align="center">
  <img src="pics/pic5.png"/>
</p>

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

<p align="center">
  <img src="pics/pic6.png"/>
</p>

I will leverage this later.

## Exploit entry point 0x802b2003

<p align="center">
  <img src="pics/pic7.png"/>
</p>

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.

<p align="center">
  <img src="pics/pic8.png"/>
</p>

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.

<p align="center">
  <img src="pics/pic9.png"/>
</p>

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. 

<p align="center">
  <img src="pics/pic10.png"/>
</p>

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

<p align="center">
  <img src="pics/pic11.png"/>
</p>

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

<p align="center">
  <img src="pics/pic12.png"/>
</p>

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

<p align="center">
  <img src="pics/pic13.png"/>
</p>

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

Windows doesn't load the driver at default state. So you need to load it manually. The file ltmdm64.sys is located at `C:\Windows\System32\DriverStore\...\ltmdm64.sys`, run this command as admin and run the exploit:

> sc create ltmdm64_srv binPath="C:\Windows\System32\DriverStore\...\ltmdm64.sys" type=kernel && sc start ltmdm64_srv
  

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 (win 11 24h2 need SeDebugPriv is `enabled`)


https://github.com/user-attachments/assets/05a35b38-d26c-484f-9fb7-137f8fe8c079

## CVE Authors

I reported this bug to ZDI. But seem like it duplicate with the submission of [Fabian Mosch](https://x.com/shitsecure) and [Jordan Jay](https://x.com/0xLegacyy) to MSRC, so this PoC just shows the bug and appreciate their work.
Almost my first CVE 😍





File Snapshot

Log in to view the POC file snapshot cached by Shenlong Bot

Log in to view
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 →