Phân tích BSOD: Kỹ thuật debug dump file trong thế giới kernel

Published By: RD TEAM

Published On: 03/09/2025

Published in:

Nếu mọi người thường xuyên theo dõi thông tin thì vào tháng 7 năm 2024, vụ việc màn hình xanh nổi tiếng đã xảy ra bắt nguồn từ một bản cập nhật phần mềm lỗi của CrowdStrike, một công ty an ninh mạng. Vụ việc đã gây ra lỗi hệ thống BSOD (Màn hình Xanh chết chóc) trên hàng triệu máy windows trên toàn thế giới. Sự cố này đã làm gián đoạn các dịch vụ thiết yếu như hàng không, y tế và ngân hàng, gây ra thiệt hại kinh tế ước tính lên đến hơn 1 tỷ USD.

crowd_strike_bsod

Là 1 driver developer, việc gặp tình trạng màn hình xanh là chuyện thường ngày và không thể tránh khỏi.

Và nối tiếp chuỗi bài về BSOD: “Blue Screen of Death vô tận ” và cách xử lý, thì hôm nay mình sẽ chia sẻ sâu hơn về các loại lỗi có thể gặp trong thế giới kernel.

Trước khi bắt đầu, ta cần xác định làm thế nào để có thể phân tích 1 file dump từ BSOD. 

Câu lệnh: !analyze -v 

analyze_v

Khi WinDbg đã kết nối với PC và xảy ra BSOD, công cụ sẽ thường gợi ý chạy lệnh !analyze -v để phân tích nhanh minidump. Việc phân tích minidump là cách hiệu quả để nắm bắt nguyên nhân sự cố ở mức kernel. Sau khi khôi phục hệ thống, nên thu thập file minidump để có cơ sở dữ liệu phục vụ quá trình phân tích lỗi chi tiết.

1. Các lưu ý khi đọc log Bucheck Analysis

1.1 BSOD Code

Mỗi mã BSOD (bug check code) luôn đi kèm với một tập hợp arguments. Các arguments này mang ý nghĩa khác nhau tùy vào từng loại BSOD. Ví dụ, bốn arguments của BSOD X sẽ có ý nghĩa khác hoàn toàn với bốn arguments của BSOD Y. Thậm chí, cùng một argument nhưng trong các tình huống khác nhau cũng có thể biểu thị ý nghĩa khác nhau. Vì vậy, việc xác định đúng BSOD code là bước đầu tiên để biết nên tập trung phân tích vào tham số nào.

Tham khảo: Bug Check Code Refernce 

bsod_code

1.2 Process Name

Tên process có liên quan đến BSOD. Một số sự cố không phát sinh trực tiếp từ driver trong Kernel Mode (KM) mà bắt nguồn từ process trong User Mode (UM). Trong các trường hợp này, trường Process Name sẽ cho biết tên process liên quan.

process_name

1.3 Module Name

Tên module gây ra sự cố. Module có thể nằm ở User Mode hoặc Kernel Mode. Thông tin này thường dùng để khoanh vùng nhanh thủ phạm gây BSOD.

module_name

1.4 Stack Text

Stack call tại thời điểm ngay trước khi hệ thống gặp BSOD. Từ stack này, ta có thể lần theo để xác định instruction gây lỗi, đặc biệt khi có thêm thông tin từ file .pdb được tạo trong quá trình build driver.

Stack_text

1.5 Context Record

Lưu trữ thông tin ngữ cảnh (context) tại thời điểm trước khi BSOD xảy ra. Thông thường, khu vực này chứa trạng thái của các thanh ghi (registers), giúp việc phân tích nguyên nhân gốc rễ trở nên chi tiết hơn.

Context_Record

2. Một số lỗi BSOD thường gặp

Tham khảo: Windows - hardware drivers debugger

2.1 KERNEL_SECURITY_CHECK_FAILURE

KERNEL_SECURITY_CHECK_FAILURE
  • Nguyên nhân: Một cấu trúc dữ liệu nào đó tại kernel mode đã bị sử dụng sai cách. Thông thường (và cũng khó debug nhất) là linked list. Khi đó, param1 = 3

  • Khi param1 = 3, một số nguyên nhân có thể nghĩ tới như sau:

    • Double-free một entry trong linked-list

    • Free một entry trong linked list nhưng không remove nó khỏi linked list

    • Traverse vượt ra khỏi giới hạn của linked-list (đối với singly linked list) và traverse vô tận trong linked list (đối với doubly linked list - mặc định)

    • List head chưa được khởi tạo

    • Thêm một entry vào list 2 lần

  • Start point:

    • Đọc stack trace để xác định hàm gây lỗi, từ đó xác định các vị trí trong code có khả năng gây lỗi

    • Đôi khi, stack trace không trực tiếp trỏ tới hàm gây lỗi mà sẽ tới trước hàm gây lỗi → từ đây cũng có thể suy đoán vị trí gây lỗi

2.2 PAGE_FAULT_IN_NON_PAGED_AREA

PAGE_FAULT_IN_NON_PAGED_AREA
  • Nguyên nhân: Truy cập một vùng nhớ non-paged không hợp lệ

  • Các nguyên nhân thông thường

    • Truy cập null pointer

    • Truy cập vào một vùng nhớ đã được release (ExFreePool)

    • Truy cập vào vùng nhớ vượt ra khỏi vùng nhớ đã được cấp phát. Trong trường hợp này, lỗi có thể xảy ra ngẫu nhiên nên sẽ khó xác định nguyên nhân hơn

    • Khởi tạo một string với một buffer không có null terminator

  • Start point:

    • Đọc stack trace để xác định hàm gây lỗi, từ đó xác định các vị trí trong code có khả năng gây lỗi

    • Đôi khi, stack trace không trực tiếp trỏ tới hàm gây lỗi mà sẽ tới trước hàm gây lỗi → từ đây cũng có thể suy đoán vị trí gây lỗi

    • Kiểm tra logic của tất cả các thủ tục khởi tạo string: Đã có null terminator hay chưa? Duyệt buffer có kiểm tra độ dài buffer không? Có đi vượt quá buffer không?

2.3 SYSTEM_SERVICE_EXCEPTION

SYSTEM_SERVICE_EXCEPTION
  • Nguyên nhân: Thông thường do đọc vào vùng nhớ bị access-denied, null pointer hoặc truy cập ngẫu nhiên vùng nhớ không hợp lệ (do đọc buffer vượt quá vùng cấp phát)

  • Start point:

    • Kiểm tra param 1 để xem loại lỗi (Đối chiếu bảng NtToDos)

    • Kiểm tra stack trace để tìm dòng code gây lỗi

Tham khảo: NtToDos.xlsx

2.4 SYSTEM_THREAD_EXCEPTION_NOT_HANDLED

SYSTEM_THREAD_EXCEPTION_NOT_HANDLED

Tương tự như SYSTEM_SERVICE_EXCEPTION

3. Kỹ thuật Debug, xác định nguyên nhân và sửa lỗi

Trong nội dung bài viết này, tôi sẽ demo một case thực tế của đội driver developer gặp phải và cách debug trong trường hợp thực tế.

  • Đầu tiên, ta cần xác định là WinDbg đã kết nối với máy ảo, đồng thời chứa đầy đủ các file .pdb và driver đã được cài đặt trên máy.

    windbg_debug_win7
  • Ta đặt break point tại các điểm nghi ngờ xảy ra lỗi, và chạy driver đến các điểm đã nêu trên. Khi đã vào hàm, ta sẽ sử dụng Step Out, Step Into, Step Over để có thể đến được vị trí của điểm lỗi.

  • Ta sẽ sử dụng các kỹ thuật tra cứu Memory để xác định cụ thể nguyên nhân, vị trí gây lỗi.

    memory_debug

3.1 MEMORY

Kỹ thuật debug memory có liên quan chặt chẽ tới việc tính toán offset. Do vậy cần một chiếc máy tính có thể tính toán HEX.

WinDbg có hỗ trợ giao diện xem Memory. Khi nhập vào một địa chỉ của một biến, ta có thể thấy được giá trị tại vị trí địa chỉ này và các giá trị theo sau nó. Việc debug memory là cần thiết để kiểm tra và phát hiện khả năng đọc/ghi sai vị trí, đọc/ghi vượt quá vùng nhớ cấp phát.

Memory_debug_2

3.2 NHẮC LẠI về kỹ thuât Dynamic Allocation sử dụng Offset và Length

Một struct có thể được allocate động bằng cách tính toán total size bao gồm phần base và phần dynamic. Các phần dynamic thường liên quan tới dữ liệu raw bytes hoặc string. Để xác định vị trí bắt đầu và kết thúc của các thành phần dynamic, cần sử dụng offset và length.

  • Mỗi thành phần sẽ có 1 cặp offset - length tương ứng. Offset có thể được chọn tính từ base start hoặc base end, tùy ý người viết. Length là độ dài của thành phần

  • Khi xác định vị trí của một thành phần trong Memory view của WinDbg, cần tính vị trí bắt đầu của nó bằng cách tính base + offset

  • Dữ liệu được xác định là đúng khi

    • Bắt đầu từ đúng vị trí base + offset mong muốn

    • Có độ dài mong muốn. Lưu ý đối với string: tùy theo ANSI hoặc UNICODE_STRING để xác định length này cho hợp lý

    • Được kết thúc theo mong muốn. Lưu ý đối với string: ANSI kết thúc bằng 00 trong khi UNICODE_STRING kết thúc bằng 00 00

    • Tổng khối dữ liệu không vượt quá độ dài đã cấp phát cho khối dữ liệu: Dữ liệu được ghi không đượt vượt qua base + totalSize

Từ đây, ra sẽ tính được vị trí dữ liệu sẽ được sử dụng và xác định nguyên do gây lỗi

Memory_debug_3
dynamic_allocation_2

Ngoài ra, ta có thể xác định được các nguyên do tại sao không lấy được dữ liệu trả về từ các status code lỗi từ bảng NtDos bên trên.

checkcode_ntdos
Status_object_name_not_found

4. Kết luận

Phân tích BSOD là một công việc đòi hỏi sự kết hợp giữa kiến thức nền tảng về kernel, kỹ năng sử dụng WinDbg, cùng khả năng suy luận từ các dấu hiệu trong log. Từ những thông tin cơ bản như Bug Check CodeProcess/Module NameStack Text hay Context Record, ta có thể lần ra nguyên nhân gốc rễ của sự cố.

Điều quan trọng là mỗi BSOD không chỉ là một sự cố tạm thời, mà còn là một cơ hội để cải thiện chất lượng driver. Một quy trình phân tích chặt chẽ không chỉ giúp khắc phục sự cố tức thời, mà còn đóng vai trò như một vòng phản hồi (feedback loop) để nâng cao độ ổn định và an toàn của toàn bộ hệ thống.

Bài viết đến đây đã dài, cảm ơn mọi người đã theo dõi!

7 lượt xem