
I. Overview#
Trước đây mình từng đọc các bài blog phân tích tactic của các nhóm APT thì mình thấy có CLFS (Common Log File System) thường được sử dụng như 0day để leo quyền thành system từ đó load rootkit. Do mình thấy tỉ lệ CLFS xảy ra nhiều vuln như vậy nên mình sẽ phân tích nó.
Vậy đầu tiên ta cần hiểu CLFS là gì. Dựa vào các docs từ microsoft hoặc các bài blog ta đại khái có thể hình dung ra nó liên quan đến cơ chế lưu giữ log trên windows và cho phép user có thể revert lại khi cần.
Thành phần handle các operation trên chính là clfs.sys driver. Do đó ta sẽ focus vào nó.
II. Vuln description#
CVE mình chọn là CVE-2025-29824 sau khi đọc bài phân tích từ qianxin và starlab. CVE này là một bug use after free (UAF) từ đó lợi dụng để LPE. Bên cạnh đó CVE này chỉ có writeup exploit cũng như poc crash mà không có full script LPE.
Ngoài ra, nó có ảnh hưởng đa phần đến windows 11 (<=24H2), windows 10 và win server. Chi tiết về version bị lỗi có thể xem ở đây
III. Analysis#
a. Diff check#
Ở đây mình sẽ sử dụng winbindex để tìm kiếm các version bị lỗi và không bị lỗi để diffcheck
CClfsRequest::Close là thay đổi đáng kể

g_Feature_3334536505_56880687_FeatureDescriptorDetails) thì còn thấy nó được implement ở hàm CClfsRequest::Cleanup
CClfsRequest::Cleanup
CClfsRequest::Close khi patch được implement như sau:

FsContext2 do đó field này chắc chắc xảy ra vấn đề gì đó. Trong đó, nó đa phần focus vào việc check FsContext2 có null không, nếu không sẽ free và clear field đó thành null. Đây cũng là một check điển hình hay gặp trong các bài heap trong CTF.
Do đó, mình sẽ dựa vào binary trước khi patch để xem FsContext2 được sử dụng như thế nào. Ta có thể thấy như sau:
FsContext2được freeCClfsRequest::Cleanup- Để có thể tạo ra bug UAF như CVE description,
FsContext2phải được sử dụng ở đâu đấy sau khi free
Từ đây ta sẽ focus làm sau để trigger ra được CClfsRequest::Cleanup trước. Trace ngược lại thì nó sẽ được thực thi khi có IO Request gửi đến trong ClfsDispatchIoRequest
CClfsRequest::Cleanup và CClfsRequest::Close thì hàm Cleanup sẽ được call trước rồi mới đến Close. Từ đây, ta thấy flow UAF như sau:
- User gọi CloseHandle
- Cleanup thực thi và free
FsContext2 - Tìm cách malloc lại chunk
FsContext2và trigger tương ứng - Close thực thi
Hay nói cách khác, dù có UAF, ta phải tìm cách race trong khoảng thời gian giữa Cleanup và Close mới có thể LPE thành công.
b. Exploitation#
Vậy ta cần trigger qua hàm nào, khi reverse một hồi ta sẽ thấy nó FsContext2 được sử dụng khá nhiều ở ClfsMgmtDispatchIo
CClfsRequest::ReserveAndAppendLog()CClfsRequest::WriteRestart()CClfsRequest::ReadArchiveMetadata()CClfsManagedLogClientUser::ReadNotification()
Trong đó ở đây mình sẽ sử dụng hàm ReadNotification do thấy nó dễ nhất. Để call được hàm này, đầu tiên nó sẽ deference FsContext2 ở offset nào đấy và check

FsContext2 này thì có thể dễ dàng call đến địa chỉ bất kỳ ta muốn. Từ đây ta có primiative đầu tiên: arbitrary call
Để LPE, ta cần ghi token của process hiện tại thành system, hoặc ghi đè token->privilege của process hiện tại. Mà để ghi được như vậy ta cần leak địa chỉ kernel base để call địa chỉ ta muốn.
Đối với các Windows version < 24H2, để leak địa chỉ đơn giản có thể thực thi qua các API
Từ đây mình có thể call đến địa chỉ mình muốn, địa chỉ mình chọn ở đây là RtlSetAllBits, hàm này sẽ memset địa chỉ mình muốn với len thành 0xff hết. Mình sẽ memset _TOKEN->privileges thành 0xff hết để enable hết bit privilege trong đó từ đó có quyền system

Ta sẽ tóm tắt exploit như sau:
- Leak địa chỉ cần thiết
- Gọi
CloseHandle FsContext2được free- Race và spray để control được
FsContext2và callRtlSetAllBitsTrong đó, bước quan trọng và khó nhất chính là bước race để spray
c. Racing in kernel pool#
Chunk FsContext2 có size là 0x120 và có structure như sau


- Thread 1: alloc trước N pipe và ghi vào pipe đó để kernel allocate N chunk 0x120 byte, sau đó read M pipe trên xen kẻ để free n chunk trên, sau đấy create K clfs handle với mục đích cho FsContext2 vô tính sử dụng lại 1 trong các chunk trong M pipe được free trên. Sau đó
CloseHandlecác clfs handle trên và write lại M pipe nhằm allocate lại chunkFsContext2trên
- Thread 2: thread này đơn giản hơn, chỉ việc gọi DeviceIoctl liên tục để race

- Trong đó các giá trị N M K, được tune lại tuỳ thuộc vào version windows. Ngoài ra để tăng success rate, mỗi thread sẽ được chạy ở 1 cpu
Mình cũng làm tương tự lại như trên nhưng trong quá trình debug, mình chỉ lâu lâu mới trigger được 1 lần, các lần còn lại DeviceIoControl đều thực hiện trước sau đó mới free, dẫn đến UAF không được trigger. Mất một khoảng thời gian khá lâu mình mới để ý nguyên nhân là do field Overlapped trong DeviceIoControl. Lúc đầu mình để field này là null nên dẫn đến thread này sẽ wait nó thực thi xong, dẫn đến rate cực kỳ thấp. Ngược lại nếu field này được setup, thì nó sẽ được return liền. Khi setup xong, rate trigger được hầu như là 100%
Ngoài ra, khi ta gọi free, nó sẽ không free ngay lập tức mà được đưa vào lookaside list, khi vượt quá len list này là 4 thì nó mới free. Do đó ta cần free trước 4 handle.
Sau khi mình overwrite privileges của process hiện tại, mình đã có quyền ngang system nên mình tạo clean shell bằng cách chạy cmd.exe bằng cách inherit token của winlogon.exe dựa trên code snippet ở đây
d. The patch#
Trước update, refcount của FsContext2 đầu tiên sẽ được tăng lên 2 ở CClfsLogCcb::AddRef sau đó khi thực thi CClfsLogCcb::Cleanup refcount sẽ được giảm về 1 và cuối cùng được free trong CClfsLogCcb::Release
Sau update, hàm CClfsLogCcb::Cleanup thêm đoạn check nếu có mitigation thì nó sẽ không được release dẫn đến refcount được giữ nguyên từ đó nó sẽ không được free ngay trong cleanup mà được free sau đó ngay close. Điều này dẫn đến UAF không còn tồn tại.
III. Conclusion#
Khi thực thi ý tưởng trên mình đã có được shell với quyền system

Các kỹ thuật mình dùng để leak và spray này dễ bị AV flag là malware, tuy vẫn có cách để bypass behavior và heuristic detect trên AV nhưng vì mục đích poc nên ta sẽ dừng ở đây. Trong quá trình mình race, mình đã thử tạo rất nhiều pipe để spray với mục đích race đúng 1 lần duy nhất. Sau quá trình debug mình thấy nó khá unstable hơn so với việc cho nó try nếu loop này không được thì qua loop khác. Số lượng pipe giảm xuống và vẫn đảm bảo được độ stable Exploit LPE được test stable trên Win 10 19045.4651 và Win 11 22631.2715
IV. References:#
- https://learn.microsoft.com/en-us/windows-hardware/drivers/kernel/introduction-to-the-common-log-file-system
- https://forensics.wiki/common_log_file_system_%28clfs%29/
- https://msrc.microsoft.com/update-guide/vulnerability/CVE-2025-29824
- https://nvd.nist.gov/vuln/detail/CVE-2025-29824
- github.com/sam-b/windows_kernel_address_leaks/blob/master/README.md
- https://github.com/gavz/prefetch-tool_KASLR
- https://windows-internals.com/kaslr-leaks-restriction/
- https://www.alex-ionescu.com/?p=231
