1. Giới thiệu
Bài viết này cung cấp một phân tích kỹ thuật về số lượng Windows kernel mode drivers có thể tương tác từ user mode mà không cần phần cứng gốc mà chúng được phát triển để hỗ trợ. Công việc này được thúc đẩy bởi hoạt động nghiên cứu lỗ hổng hướng vào driver và nhu cầu đánh giá khả năng khai thác của các phát hiện cá biệt, vốn thường ảnh hưởng đến mã nguồn có khả năng tiếp cận bị giới hạn bởi phần cứng (hardware-gated). Phương pháp trình bày ở đây sẽ giúp bất kỳ ai xác định liệu một lỗ hổng Windows kernel mode driver cụ thể có thể tiếp cận được hay không — và do đó có khả năng khai thác được hay không — ngay cả khi không có phần cứng đi kèm.
Người đọc được kỳ vọng có kiến thức cơ bản về Windows driver, đặc biệt là về các device objects. Phần còn lại của bài viết được viết với giả định rằng người đọc đã quen thuộc với các khái niệm được mô tả trong bài viết giới thiệu: Anatomy of Access: Windows Device Objects from a Security Perspective.
Giống như bài viết giới thiệu, tài liệu này không tập trung vào bất kỳ loại lỗi cụ thể nào, mà tập trung vào bề mặt tấn công (attack surface) và kiến trúc Windows Plug and Play.
Tất cả các thử nghiệm được trình bày ở đây đều được thực hiện trên Windows 11 23H2 (winver 10.0.22631.3007).
Để biết thêm các nghiên cứu về mối đe dọa và tư vấn lỗ hổng mới nhất, vui lòng đăng ký Atos Cyber Shield blogs.
2. Giá trị tấn công của các kernel mode drivers
Ngoài tiềm năng leo thang đặc quyền cục bộ (Local Privilege Escalation) rõ rệt, các driver lỗi thường bị lạm dụng trong các cuộc tấn công BYOVD (Bring Your Own Vulnerable Driver) — một kỹ thuật hậu khai thác được những kẻ tấn công sử dụng để phá vỡ các hệ thống phòng thủ như các thành phần EDR.
Hai tiêu chí chính xác định liệu một lỗ hổng driver có phải là ứng cử viên sáng giá cho các cuộc tấn công BYOVD hay không: 1. Việc khai thác cho phép phá vỡ một cách có ý nghĩa các thành phần bảo mật chống giả mạo. Ví dụ bao gồm các lỗ hổng cấp kernel cho phép đọc/ghi bộ nhớ tùy ý, thực thi mã tùy ý hoặc lạm dụng tài nguyên tùy ý (ví dụ: ghi đè tệp, đóng handles hoặc chấm dứt tiến trình). 2. Khả năng khai thác của nó độc lập với các điều kiện hệ thống hiếm gặp, chẳng hạn như sự hiện diện của phần cứng cụ thể.
Mặc dù các cuộc tấn công kiểu BYOVD đã được ghi nhận đầy đủ trong nhiều năm qua với vô số báo cáo công khai, nhưng chưa có báo cáo nào xem xét cụ thể vai trò của việc giới hạn bởi phần cứng (hardware-gating) trong khả năng tiếp cận lỗ hổng driver.
3. Tạo và duy trì device object - Các mô hình phổ biến
Phân tích trong tài liệu này được cấu trúc xoay quanh các device objects, vì chúng là vector tấn công khả thi nhất. Tuy nhiên, các kỹ thuật được trình bày ở đây thực tế có tác động đến khả năng tiếp cận mã nguồn driver từ userland nói chung, không chỉ thông qua IRP.
Những trở ngại phổ biến nhất khi tấn công một driver thông qua device object của nó là: 1. Device object không được tạo. 2. Trạng thái nội bộ của driver không cho phép thực hiện hành vi lỗi mặc dù device object có thể truy cập được.
Cả hai kịch bản đều rất phổ biến khi xử lý một device driver được triển khai trên một hệ thống không có phần cứng vật lý tương ứng.
Trong phần còn lại của bài viết, tôi thường đề cập đến device stacks và device nodes. Mặc dù device node và device stack không giống nhau, nhưng các thuật ngữ này thường được sử dụng thay thế cho nhau vì mỗi device node có chính xác một device stack.
3.1 Tạo vô điều kiện khi load driver
Nhiều driver, đặc biệt là các driver không phải PnP, tạo các device objects của chúng trực tiếp từ bên trong hàm DriverEntry, hoặc từ một hàm khác được gọi trong chuỗi lệnh trực tiếp bắt nguồn từ DriverEntry.
Driver cũng xóa device object bằng cách gọi IoDeleteDevice, nhưng điều đó chỉ xảy ra khi DriverUnload được gọi (khi driver đang bị gỡ bỏ):
Các driver được xây dựng theo cách này có thể tương tác sau khi triển khai đơn giản chỉ với hai bước:
- Tạo dịch vụ cho driver:
sc.exe create SampleDrv type= kernel start= demand binPath= System32\drivers\SampleDrv.sys - Khởi chạy dịch vụ (driver sẽ load):
sc.exe start SampleDrv
Nếu chúng ta nhìn vào một driver ngẫu nhiên từ loldrivers.io, chúng ta sẽ thấy lệnh triển khai của nó khớp với mô hình này:
3.2 Tạo và duy trì thiết bị có điều kiện
Thông thường, các thủ tục khởi tạo driver thực hiện các kiểm tra bổ sung. Ví dụ, các thành phần kernel mode của phần mềm bảo mật (EDR, anti-virus, giám sát, xác thực nâng cao, v.v.) có xu hướng kiểm tra các registry keys đặc thù của sản phẩm.
Các device driver thực tế (được tạo để điều khiển phần cứng vật lý) có xu hướng chỉ tạo các device objects của chúng khi có sự hiện diện của phần cứng đó. Nếu không có phần cứng, chúng sẽ: - Không cố gắng tạo bất kỳ device object nào, - Hoặc chúng sẽ xóa bất kỳ device object nào ngay sau khi tạo bằng cách gọi IoDeleteDevice.
3.3 Các callback đặc thù của PnP là vị trí chính của logic khởi tạo PnP driver
Trong các driver tương thích với PnP, logic khởi tạo mở rộng ra ngoài DriverEntry vào các routine sau: AddDevice và trình xử lý IRP_MJ_PNP.
3.3.1 AddDevice
Tất cả các driver tương thích PnP phải định nghĩa routine này. Nó chịu trách nhiệm tạo các Functional Device Objects (FDO) và Filter Device Objects (filter DO) cho các thiết bị được liệt kê bởi trình quản lý PnP. AddDevice không được gọi từ bên trong DriverEntry, nghĩa là nó không tự động thực thi khi load driver. Thay vào đó, trình quản lý PnP chỉ gọi nó sau khi phát hiện một device node mới.
Việc gọi AddDevice là tối quan trọng vì:
- Nó cần thiết để driver khởi tạo đúng cách, giúp mã nguồn lỗi có thể tiếp cận được từ userland.
- Quan trọng hơn, mục đích của AddDevice là tạo một device object mới tương thích PnP và đính kèm nó vào device stack trên cùng của PDO.
3.3.2 IRP_MJ_PNP
IRP_MJ_PNP là mã IRP MajorFunction dành riêng cho các tương tác liên quan đến PnP. Các driver WDF (KMDF) đăng ký các callback thay đổi trạng thái PnP/Power thông qua WdfDeviceInitSetPnpPowerEventCallbacks.
3.4 Tương tác và thăm dò phần cứng chủ động
Chỉ một phần nhỏ mã nguồn driver thực sự tương tác với phần cứng vật lý thông qua các cơ chế như: Port I/O, Memory-Mapped I/O (MMIO), PCI configuration space, ACPI, DMA, và ngắt (interrupts).
4. Cách tiếp cận triển khai driver từ góc độ BYOVD
Trong phần này, chúng ta sẽ đánh giá mức độ ảnh hưởng đối với việc khởi tạo driver chỉ bằng cách vận hành từ userland (với quyền quản trị).
4.1 Triển khai đơn giản bằng sc.exe
Đây là bước tối thiểu để kích hoạt việc load driver. Tuy nhiên, cách này thường không đủ cho các PnP device objects vốn là bề mặt tấn công phổ biến hơn.
4.2 Tạo các thiết bị giả lập phần mềm với Hardware ID giả mạo
Sử dụng devcon.exe để tạo các device nodes với Hardware ID tùy ý để kích hoạt callback AddDevice. Điều này cho phép làm cho một số driver có thể tiếp cận được mà không cần phần cứng thực tế.
4.3 Nhảy stack thiết bị (Jumping device stacks)
Việc làm cho một driver tương thích PnP có thể tiếp cận được bằng một thiết bị giả lập phần mềm là một ví dụ về việc xây dựng và truy cập một custom device stack. Điều này mở ra cách tương tác qua IRP với các driver chưa bao giờ được dự định để tương tác từ userland.
4.3.1 Filter restacking
Chúng ta có thể triển khai một filter driver bằng cách đặt nó lên trên một device stack của ổ đĩa (Disk Drive class) để có thể mở một handle và tương tác với nó.
4.4 Thay thế driver cưỡng bức
Thay vì tạo thiết bị mới, chúng ta có thể ép buộc driver của mình được cài đặt cho một phần cứng hiện có trong hệ thống bằng cách sửa đổi các cấu trúc registry, bỏ qua cơ chế tệp INF.
5. Lời kết
Mặc dù các lỗ hổng nghiêm trọng vẫn còn phổ biến trong Windows kernel mode drivers, không phải tất cả driver lỗi đều có giá trị thực tế cho BYOVD do các rào cản về phần cứng. Tuy nhiên, bằng cách sử dụng các thiết bị giả lập hoặc thay thế driver cưỡng bức, nhiều rào cản này có thể bị vượt qua chỉ từ userland. Các nhà phòng thủ cần chú ý đến dấu vết forensic liên quan đến các kỹ thuật này để bảo vệ hệ thống tốt hơn.
Ghi chú: Bài viết này được đóng góp bởi Julian Horoszkiewicz từ Atos Threat Research Center.