
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
At that time, MSRC didn't take any action 🤡
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 :
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 (On Windows 24h2 you need SeDebugPriv to do this).
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.
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
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). 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: