Driver gõ cửa, Service mở - Giao tiếp giữa Kernel-mode và User-mode qua Device Handle

Published By: RD TEAM

Published On: 02/03/2026

Published in:

Mở đầu

Hãy tưởng tượng một tòa nhà có tầng hầm và tầng trên. Tầng hầm chứa toàn bộ hệ thống điện nước, camera an ninh, cầu dao tổng — mọi thứ quan trọng nhất đều ở đó. Tầng trên là nơi mọi người làm việc bình thường.

Vấn đề là tầng hầm quá nguy hiểm để ai cũng được xuống. Chỉ cần một sai lầm nhỏ là cả tòa nhà mất điện ngay lập tức. Những thông tin từ tầng hầm lại cần được đưa lên tầng trên để xử lý. Windows hoạt động theo đúng mô hình đó — và bài viết này kể câu chuyện về cái cầu thang nối hai tầng đó lại.

Lưu ý: Bài viết này nhằm mục đích giáo dục, giúp người đọc hiểu cơ chế hoạt động để xây dựng và bảo vệ phần mềm đúng cách.

Kernel Mode và User Mode — Hai tầng không được lẫn lộn

Kernel Mode và User Mode — Hai tầng không được lẫn lộn

“Tầng hầm” trong Windows chính là Kernel Mode — nơi hệ điều hành và các driver hoạt động. Ở đây, chương trình có quyền truy cập gần như tuyệt đối: có thể đọc bộ nhớ của mọi tiến trình, can thiệp vào lời gọi hệ thống, quan sát hầu như toàn bộ những gì đang diễn ra.

Nhưng quyền lực luôn đi kèm rủi ro. Chỉ một lỗi nhỏ cũng đủ làm toàn bộ hệ thống sập ngay lập tức — không cảnh báo, không cơ hội xử lý. Kết quả là màn hình xanh quen thuộc: BSOD.

“Tầng trên” là User Mode — nơi các service và ứng dụng thông thường chạy. Môi trường này an toàn hơn vì bị giới hạn quyền và được cách ly khỏi kernel. Nếu một ứng dụng lỗi, thường chỉ ứng dụng đó bị ảnh hưởng, không kéo sập cả hệ thống.

Muốn tương tác với tầng hầm? Không thể tự ý đi xuống. Mọi thứ đều phải thông qua cơ chế kiểm soát chính thức của hệ điều hành.

Trong các giải pháp bảo mật endpoint, driver cần chạy ở Kernel Mode để quan sát hệ thống ở mức thấp nhất. Những phần phân tích, xử lý logic và phản hồi nên đặt ở User Mode để đảm bảo ổn định và dễ kiểm soát.

Hai môi trường tách biệt hoàn toàn. Vì vậy, cần một cây cầu nối chúng lại.

Device Handle — Cây cầu chính thức của Windows

Windows giải quyết vấn đề này bằng cách sử dụng Device Object kết hợp với Symbolic Link. Driver tạo ra một Device Object — giống như mở một ô cửa từ tầng hầm. Sau đó, nó gắn cho ô cửa đó một tên gọi thông qua Symbolic Link, để service ở tầng trên có thể tìm thấy và kết nối vào.

Từ phía service, mọi thứ đơn giản hơn nhiều: chỉ cần gọi CreateFile() với đúng tên thiết bị là kết nối được thiết lập. Điều thú vị là CreateFile() vốn được thiết kế để mở file thông thường. Nhưng Windows xây dựng API này đủ linh hoạt để nó cũng có thể dùng cho device driver. Nhìn bên ngoài thì giống nhau, nhưng phía sau là hai cơ chế hoàn toàn khác.

Khi kết nối được mở, mọi thao tác đọc và ghi dữ liệu sẽ đi qua IRP (I/O Request Packet).

Mỗi lần service gọi ReadFile(), hệ điều hành sẽ tạo một IRP, chuyển xuống driver xử lý, sau đó driver trả dữ liệu ngược lên. Toàn bộ quá trình diễn ra âm thầm và liền mạch — service chỉ cần đọc dữ liệu nhận được và xử lý theo logic của mình.

service gọi ReadFile

Bảo mật channel — Đừng để cửa mở cho tất cả

Khi Device Handle trở thành bề mặt tấn công

Trước khi nói về cách bảo vệ, hãy thử nhìn từ phía kẻ tấn công.

Nếu một Device Handle không được bảo vệ đúng cách, họ thấy gì?

Về bản chất, Device Handle là một điểm truy cập có tên. Bất kỳ tiến trình nào biết tên đó đều có thể gọi CreateFile() để thử mở kết nối. Không cần đặc quyền đặc biệt. Không cần hiểu bên trong driver đang làm gì.

Từ góc nhìn của kẻ tấn công, có bốn hướng khai thác rõ ràng.

1. Kết nối trái phép vào kênh giao tiếp

Nếu driver không giới hạn quyền truy cập, malware có thể mở một kết nối song song với service hợp lệ.

Khi đó, nó có thể:

  • Đọc toàn bộ luồng dữ liệu đi qua

  • Theo dõi thông tin tiến trình

  • Quan sát hành vi hệ thống

  • Xem log nội bộ mà service đang nhận

Những gì service thấy, malware cũng thấy.

2. Chèn dữ liệu giả vào kênh (Data Injection)

Không chỉ nghe lén, malware còn có thể gửi dữ liệu ngược lại.

Ví dụ:

  • Báo tiến trình độc hại là “an toàn”

  • Báo tiến trình bình thường là “đáng ngờ”

Service nhận thông tin và xử lý như dữ liệu thật. Hệ thống bảo mật bị thao túng từ bên trong mà không cần kỹ thuật phức tạp nào.

Đây không phải kiểu tấn công ồn ào. Nó âm thầm và nguy hiểm vì mọi thứ vẫn “trông có vẻ hợp lệ”.

3. Làm sập driver từ xa

Driver chạy ở Kernel Mode. Một lỗi nhỏ cũng đủ gây ra màn hình xanh.

Nếu driver không kiểm tra chặt chẽ dữ liệu nhận từ user-mode, kẻ tấn công có thể gửi:

  • Bộ đệm quá lớn

  • Giá trị không hợp lệ

  • Thứ tự gọi bất thường

Chỉ một yêu cầu được thiết kế đúng cách cũng có thể làm sập toàn bộ hệ thống — vô hiệu hóa lớp bảo vệ trước khi bắt đầu cuộc tấn công thật sự.

4. Chiếm quyền mở handle trước (Handle Hijacking)

Malware không cần kỹ thuật cao. Chỉ cần nhanh hơn.

Nó gọi CreateFile() ngay khi hệ thống khởi động — trước cả khi security service kịp chạy.

Khi service bắt đầu và cố mở kết nối, nó nhận thông báo thất bại vì handle đã bị giữ. Service không hoạt động được, lớp bảo vệ biến mất, nhưng người dùng không hề hay biết.

Tính năng “truy cập độc quyền” vốn để tăng bảo mật, giờ bị lợi dụng ngược lại thành công cụ tấn công.

Có cửa rồi, nhưng nếu không có khóa thì vô nghĩa

Tạo Device Handle thôi là chưa đủ. Quan trọng là ai được phép sử dụng nó.

Nếu một cánh cửa không có khóa, bất kỳ ai đi ngang qua cũng có thể mở vào — không chỉ người được phép mà cả kẻ xấu.

Đó chính xác là điều xảy ra khi driver được tạo mà không có Security Descriptor rõ ràng.

Cách làm đúng là sử dụng IoCreateDeviceSecure() và cấu hình quyền truy cập cụ thể, ví dụ chỉ cho phép tài khoản SYSTEM và nhóm Administrators mở handle.

Một lớp bảo vệ bổ sung là chế độ truy cập độc quyền (exclusive access) — chỉ một kết nối được phép tồn tại tại một thời điểm.

Tuy nhiên, đây là con dao hai lưỡi. Nếu service bị crash mà chưa đóng handle đúng cách, driver có thể bị khóa cho đến khi khởi động lại hệ thống.

Bảo mật không chỉ là thêm cơ chế kiểm soát. Bảo mật là hiểu rõ cơ chế đó có thể bị lợi dụng như thế nào — và thiết kế để không tạo ra lỗ hổng mới trong quá trình tự bảo vệ mình.

Security descriptor-ai được phép gỡ cửa

Security Descriptor — Cơ chế khóa cửa hoạt động như thế nào?

Bốn bề mặt tấn công trên đều có cùng một điểm khởi đầu: driver không kiểm soát ai được phép mở handle. Security Descriptor là cơ chế Windows cung cấp để giải quyết đúng vấn đề đó.

Security Descriptor là một cấu trúc dữ liệu được gắn vào Device Object ngay lúc tạo ra, chứa một danh sách quy tắc gọi là DACL — Discretionary Access Control List. Mỗi quy tắc trong DACL là một ACE — Access Control Entry, chỉ định rõ: đối tượng nào, được làm gì, hay bị từ chối làm gì.

Khi một process gọi CreateFile() để mở handle, Windows không mở ngay — nó chạy qua một bước kiểm tra gọi là access check:

  1. Lấy Security Token của process đang gọi — token này chứa danh tính của process: chạy với tài khoản nào, thuộc nhóm nào, có đặc quyền gì.

  2. Đối chiếu token đó với DACL của Device Object.

  3. Nếu có ACE cho phép — CreateFile() thành công, handle được cấp.

  4. Nếu không có ACE phù hợp hoặc bị từ chối rõ ràng — CreateFile() thất bại, trả về ERROR_ACCESS_DENIED.

quá trình access check

Toàn bộ quá trình này xảy ra trong kernel, trước khi IRP đầu tiên được tạo ra. Process không có quyền thậm chí không bao giờ chạm tới driver.

Nếu driver không chỉ định Security Descriptor rõ ràng, Device Object sẽ nhận security mặc định do hệ thống gán. Trong nhiều trường hợp mặc định này rộng hơn mức cần thiết, và driver không kiểm soát được chính xác ai được phép mở handle. 

Đây là lý do một dòng code khác biệt giữa IoCreateDevice() và IoCreateDeviceSecure() lại tạo ra khoảng cách lớn đến vậy về mặt bảo mật.

Kẻ xấu có thể làm gì nếu channel không được bảo vệ?

Nói rằng “malware có thể làm mọi thứ” nghe thì đáng sợ, nhưng lại quá chung chung. Khi nhìn cụ thể từng tình huống, vấn đề còn đáng lo hơn nhiều. Dưới đây là ba kịch bản thực tế.

Data Injection — Nhét thông tin giả vào pipeline

Malware không cần phá khóa — chỉ cần mở thêm kết nối song song rồi gửi lên những event giả. Báo process độc hại là "sạch", báo process bình thường là "đáng ngờ". Service nhận dữ liệu, xử lý, đưa ra quyết định — mà không hề biết mình đang dựa trên thông tin bị bóp méo. Nguy hiểm ở chỗ: hệ thống bảo mật không bị tắt. Nó vẫn hoạt động, vẫn phân tích, vẫn phản hồi. Chỉ là mọi kết luận đều bị dẫn dắt sai hướng. Không cần kỹ thuật cao siêu. Chỉ cần kênh giao tiếp không được kiểm soát.

Driver Crash Attack — Làm sập tầng hầm từ tầng trên

Driver chạy ở Kernel Mode. Ở mức này, một lỗi nhỏ cũng có thể khiến hệ thống xuất hiện màn hình xanh. Kẻ tấn công có thể lợi dụng điều đó bằng cách gửi những yêu cầu được thiết kế đặc biệt để gây lỗi, chẳng hạn: dữ liệu quá lớn, giá trị không hợp lệ, thứ tự thao tác bất thường. Nếu driver không kiểm tra chặt chẽ dữ liệu nhận từ user-mode, chỉ một yêu cầu cũng đủ làm toàn bộ hệ thống sập xuống. Khi driver bảo vệ bị vô hiệu hóa, những lớp phòng thủ phía trên gần như không còn ý nghĩa.

Handle Hijacking — Cướp chỗ trước khi service kịp vào

Malware không cần kỹ thuật cao, chỉ cần đến sớm hơn. Nó gọi CreateFile() tới device ngay khi máy khởi động — trước cả khi security service chạy lên. Khi service chính thức khởi động và cố mở kết nối, nó nhận thông báo thất bại vì handle đã bị chiếm. Service không hoạt động được, nhưng bên ngoài hệ thống vẫn trông “bình thường”. Người dùng không thấy cảnh báo. Không có thông báo lỗi rõ ràng. Chỉ đơn giản là lớp bảo vệ đã bị vô hiệu hóa từ trước đó. Đây chính là lúc cơ chế truy cập độc quyền — vốn để tăng cường bảo mật — bị lợi dụng ngược lại như một công cụ tấn công.

Ba kịch bản, ba cách tiếp cận khác nhau. Nhưng tất cả đều bắt đầu từ cùng một nguyên nhân: driver được tạo ra mà không cấu hình Security Descriptor rõ ràng. Chỉ một dòng cấu hình bị bỏ qua khi viết code có thể khiến toàn bộ lớp bảo vệ phía trên sụp đổ — mà không cần bất kỳ kỹ thuật tấn công phức tạp nào. Bảo mật đôi khi không thất bại vì thiếu công nghệ. Nó thất bại vì một chi tiết tưởng như nhỏ bị xem nhẹ ngay từ đầu.

Graceful Shutdown — Thứ tự quan trọng hơn bạn nghĩ

Ở góc độ kỹ thuật, dừng service sai cách có thể gây lỗi truy cập bộ nhớ hoặc khiến driver bị khóa. Nhưng nếu nhìn từ góc độ bảo mật, việc shutdown không đúng quy trình còn mở ra những kịch bản tấn công mà ít người để ý đến.

Kịch bản 1 — Race condition tạo cửa sổ tấn công: Khi service bắt đầu quá trình dừng nhưng chưa gọi CloseHandle(), sẽ tồn tại một khoảng thời gian ngắn: service đã ngừng xử lý dữ liệu nhưng handle vẫn còn mở. Nếu kẻ tấn công quan sát được trạng thái service và tận dụng đúng thời điểm đó, họ có thể gửi dữ liệu vào kênh giao tiếp trong khoảng trống này. Dữ liệu sẽ nằm lại trong bộ đệm của driver, chờ đến lần service khởi động lại để được xử lý. Về bản chất, đây là một dạng tấn công “kiểm tra rồi mới sử dụng” (TOCTOU) nhắm vào vòng đời của handle — lợi dụng sự chênh lệch giữa thời điểm dừng xử lý và thời điểm đóng kết nối.

Kịch bản 2 — Forced crash để trigger restart không an toàn: Nếu kẻ tấn công có thể khiến service sập đột ngột — ví dụ bằng cách khai thác lỗi trong driver — service sẽ tắt mà không kịp đóng handle. Kết quả: driver vẫn bị giữ kết nối, service khởi động lại, không mở được handle, báo lỗi. Tùy cách hệ thống xử lý, có hai hậu quả: một là service liên tục khởi động lại và thất bại — tạo ra tình trạng từ chối dịch vụ, hai là đội vận hành vì áp lực mà “sửa tạm” bằng cách nới lỏng kiểm tra bảo mật để service chạy lại được. Và đó chính xác là điều kẻ tấn công muốn: không phải chỉ làm sập hệ thống, mà làm bạn tự giảm mức bảo vệ của mình.

Kịch bản 3 — Zombie handle: Trong một số trường hợp, nếu process exit không sạch, Windows có thể không kịp dọn dẹp handle ngay lập tức. Handle tồn tại trong trạng thái "zombie" — không thuộc về process nào nhưng vẫn giữ lock trên driver. Kẻ tấn công có thể lợi dụng trạng thái này để giữ driver bị lock vô thời hạn mà không cần duy trì bất kỳ process nào đang chạy.

Ba kịch bản trên đều bắt nguồn từ cùng một nguyên nhân: shutdown không được xử lý như một phần của threat model, mà chỉ được xử lý như một bài toán kỹ thuật thuần túy. Graceful shutdown đúng cách — signal trước, wait sau, close handle cuối cùng — không chỉ tránh crash mà còn đóng các cửa sổ tấn công này lại.

Vì sao thứ tự lại quan trọng?

Tưởng tượng bạn đang đứng ở cửa sổ nhận hàng, tay đang cầm gói hàng chưa nhận xong — và đột nhiên có người đóng sầm cửa sổ lại. Gói hàng kẹt, cửa sổ hỏng, cả hai bên không xử lý được tiếp.

Đó chính xác là điều xảy ra khi service tắt không đúng thứ tự. Khi Service Control Manager (SCM) gửi lệnh dừng, bên trong đang có một thread block bên trong ReadFile() chờ data. Nếu gọi CloseHandle() lúc đó — access violation ngay lập tức. Nếu exit mà không gọi CloseHandle() — driver bị lock đến khi reboot.

thứ tự đúng

Thứ tự đúng: signal thread dừng bằng stop flag hoặc CancelIo(), chờ thread thoát bằng WaitForSingleObject(), rồi mới gọi CloseHandle(), cuối cùng báo SCM là SERVICE_STOPPED. Không đảo ngược, không bỏ bước nào.

Thứ tự đúng - không đảo ngược, không bỏ bước

 

Kết

Cơ chế giao tiếp giữa kernel và user-mode không hào nhoáng. Nó không có UI đẹp, không có dashboard bắt mắt. Người dùng cuối không bao giờ nhìn thấy nó. Nhưng nó là nền móng. Và nền móng thì chỉ cần sai một chút là cả tòa nhà rung chuyển.

Chúng ta thường nói về bảo mật bằng những từ lớn: AI detection, behavioral analysis, zero-day defense. Nhưng trước tất cả những thứ đó là một câu hỏi đơn giản hơn nhiều: ai được phép nói chuyện với kernel? Nói bằng cách nào? Và khi cuộc nói chuyện kết thúc, có chắc nó đã thực sự kết thúc?

Một Device Handle cấu hình đúng có thể không bao giờ được nhắc đến trong báo cáo thành tích. Một shutdown được thiết kế cẩn thận sẽ không được ai khen ngợi. Nhưng chính những thứ âm thầm đó quyết định hệ thống có đứng vững khi chịu áp lực hay không.

Kiến trúc tốt không chỉ là làm cho hệ thống chạy được. Kiến trúc tốt là làm cho nó không sụp đổ khi mọi thứ đi sai hướng.

Hiểu được điều đó, bạn không chỉ đang viết driver hay service — bạn đang đặt những viên gạch đầu tiên cho sự ổn định và tin cậy lâu dài của toàn bộ hệ thống.

195 lượt xem