POC详情: ac2f66a3967b1af2087e7e8d9b22abed072b05bd

来源
关联漏洞
标题: Microsoft Windows 安全漏洞 (CVE-2025-24990)
描述:Microsoft Windows是美国微软(Microsoft)公司的一套个人设备使用的操作系统。 Microsoft Windows Agere Modem Driver存在安全漏洞,该漏洞源于攻击者利用该漏洞可以提升权限。
描述
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)

![](pics/pic1.png)

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

![](pics/pic2.png)

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

![](pics/pic3.png)

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

  ![](pics/pic4.png)

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

![](pics/pic5.png)

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

![](pics/pic6.png)

I will leverage this later.

## Exploit entry point 0x802b2003

![](pics/pic7.png)

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.

![](pics/pic8.png)

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.

![](pics/pic9.png)

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. 

![](pics/pic10.png)

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

![](pics/pic11.png)

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

![](pics/pic12.png)

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

![](pics/pic13.png)

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付费,感谢您的支持。