Luận án: Kết hợp phân tích tĩnh và động để xây dựng CFG phân tích mã nhị phân

Khám phá phân tích tĩnh & động trong việc xây dựng đồ thị luồng điều khiển (CFG). Nắm vững kỹ thuật tạo CFG chính xác cho đánh giá mã nguồn.

Chuyên ngành

Khoa Học Máy Tính

Người đăng

Ẩn danh

Thể loại

Luận Án Tiến Sĩ

2019

169
1
0

Phí lưu trữ

45 Point

Tóm tắt

I. Hướng dẫn kết hợp phân tích tĩnh và động để xây dựng CFG

Việc kết hợp phân tích tĩnh và kiểm tra động là một hướng tiếp cận lai ghép tiên tiến, giải quyết những bài toán phức tạp trong lĩnh vực phân tích mã nhị phân. Phương pháp này đặc biệt quan trọng khi xây dựng đồ thị luồng điều khiển (Control Flow Graph - CFG), một cấu trúc nền tảng để hiểu rõ hành vi thực thi của chương trình. Trong bối cảnh các chương trình, đặc biệt là mã độc, ngày càng tinh vi, việc chỉ dựa vào một phương pháp phân tích duy nhất đã tỏ ra không còn hiệu quả. Luận án của Nguyễn Minh Hải (2019) đã chỉ ra rằng, phân tích tĩnh tuy có khả năng bao quát nhưng thường dẫn đến tình trạng "xấp xỉ quá mức", trong khi kiểm tra động cung cấp đường đi chính xác nhưng lại bị giới hạn bởi độ bao phủ của các ca kiểm thử. Hướng tiếp cận lai ghép này tận dụng ưu điểm của cả hai phương pháp: dùng phân tích tĩnh để khám phá cấu trúc chương trình và sử dụng kiểm tra thực thi ký hiệu động để xác định chính xác các đích đến của những lệnh nhảy phức tạp. Mục tiêu cuối cùng là tạo ra một đồ thị luồng điều khiển vừa đầy đủ vừa chính xác, làm tiền đề vững chắc cho các tác vụ phân tích chuyên sâu như phát hiện lỗ hổng, nhận diện mã độc, hay bảo vệ bản quyền phần mềm.

1.1. Tầm quan trọng của đồ thị luồng điều khiển trong luận văn

Đồ thị luồng điều khiển (CFG) là một biểu diễn trừu tượng, trực quan hóa tất cả các đường đi mà một chương trình có thể thực thi. Trong các nghiên cứu học thuật và luận văn, CFG đóng vai trò là một biểu diễn trung gian thiết yếu. Nó chuyển đổi mã nhị phân phức tạp, khó đọc thành một cấu trúc đồ thị có hướng, nơi các đỉnh đại diện cho các khối lệnh cơ bản và các cạnh thể hiện luồng điều khiển giữa chúng. Như được mô tả trong luận án, CFG là nền tảng cho nhiều kỹ thuật phân tích chương trình, từ việc kiểm tra tính đúng đắn của phần mềm đến việc phát hiện các hành vi độc hại. Một CFG chính xác cho phép các nhà nghiên cứu áp dụng các thuật toán đồ thị để xác định các vòng lặp, các nhánh điều kiện, và các đường thực thi bất thường, vốn là những dấu hiệu quan trọng trong phân tích mã độc.

1.2. Hạn chế của phân tích tĩnh hoặc kiểm tra động đơn thuần

Sử dụng riêng lẻ, cả hai phương pháp phân tích tĩnhkiểm tra động đều có những nhược điểm cố hữu. Phân tích tĩnh, như được thực hiện bởi các công cụ như JakStab, phân tích mã nguồn hoặc mã nhị phân mà không cần thực thi. Điều này giúp bao quát tất cả các đường đi có thể, nhưng lại gặp khó khăn lớn với lệnh nhảy không trực tiếp, dẫn đến hiện tượng xấp xỉ quá mức (over-approximation) và tạo ra nhiều đích nhảy sai. Ngược lại, kiểm tra động thực thi chương trình với các đầu vào cụ thể, cung cấp một đường đi thực thi (trace) chính xác. Tuy nhiên, phương pháp này phụ thuộc hoàn toàn vào các ca kiểm thử (test cases). Nó không thể đảm bảo bao phủ hết mọi nhánh thực thi, đặc biệt là những nhánh chỉ được kích hoạt bởi các điều kiện đầu vào hiếm gặp. Sự thiếu sót này làm cho việc xây dựng một đồ thị luồng điều khiển hoàn chỉnh trở nên bất khả thi.

II. Thách thức lớn Lệnh nhảy không trực tiếp trong mã nhị phân

Một trong những trở ngại lớn nhất trong phân tích mã nhị phân là xử lý lệnh nhảy không trực tiếp (indirect jump). Đây là các lệnh mà địa chỉ đích không được mã hóa cứng mà được tính toán động tại thời điểm chạy, thường được lưu trong một thanh ghi hoặc một vị trí bộ nhớ. Các kỹ thuật phân tích tĩnh truyền thống thường bất lực trước các lệnh này, buộc phải giả định rằng lệnh nhảy có thể chuyển đến bất kỳ địa chỉ hợp lệ nào, gây ra bùng nổ trạng thái và làm cho đồ thị luồng điều khiển trở nên vô dụng. Các tác giả mã độc thường xuyên lợi dụng đặc điểm này, kết hợp với các kỹ thuật làm rối (obfuscation) như mã tự thay đổi (self-modifying code) hoặc sử dụng chương trình đóng gói (packer) để che giấu luồng thực thi thật sự. Việc giải quyết triệt để vấn đề lệnh nhảy động là yếu tố then chốt để xây dựng một CFG chính xác, phản ánh đúng hành vi của chương trình. Đây chính là bài toán trung tâm mà phương pháp kết hợp giữa phân tích tĩnh và động hướng đến giải quyết, nhằm giảm thiểu sự không chính xác và tăng độ tin cậy của kết quả phân tích.

2.1. Vấn đề xấp xỉ quá mức khi phân tích tĩnh mã nhị phân

Hiện tượng xấp xỉ quá mức (over-approximation) là điểm yếu chí mạng của phương pháp phân tích tĩnh khi đối mặt với các cấu trúc động. Luận án đã minh họa rõ vấn đề này qua Hình 1-4: một biến được gán giá trị trong một khoảng, ví dụ [10, 30]. Khi biến này được dùng làm địa chỉ đích cho một lệnh nhảy gián tiếp, phân tích tĩnh sẽ coi tất cả các địa chỉ trong khoảng [10, 30] đều là đích đến tiềm năng. Điều này tạo ra vô số cạnh và đỉnh không tồn tại trong thực tế vào đồ thị luồng điều khiển, gây nhiễu loạn và làm tăng độ phức tạp phân tích một cách không cần thiết. Hậu quả là kết quả phân tích có thể chứa nhiều cảnh báo sai (false positives) và bỏ lỡ các luồng thực thi thực tế bị che lấp giữa các luồng giả.

2.2. Kỹ thuật làm rối mã và các trở ngại trong phân tích mã độc

Các kỹ thuật làm rối (obfuscation) được các chương trình đóng gói như UPX, PECOMPACT sử dụng triệt để nhằm gây khó khăn cho quá trình dịch ngược và phân tích. Những kỹ thuật này bao gồm mã tự thay đổi, khối trùng lắp, phân mảnh mã, và đặc biệt là lạm dụng lệnh nhảy không trực tiếp. Ví dụ, một chương trình có thể sử dụng các phép toán số học phức tạp để tính toán địa chỉ nhảy, hoặc dùng các trình xử lý ngoại lệ có cấu trúc (SEH) để chuyển hướng luồng điều khiển một cách bất thường. Những kỹ thuật này làm cho việc xây dựng đồ thị luồng điều khiển bằng các công cụ truyền thống như IDA Pro trở nên thiếu chính xác, vì chúng không thể mô phỏng và dự đoán được những thay đổi động này. Việc nhận diện và vô hiệu hóa các kỹ thuật này là một yêu cầu bắt buộc đối với một hệ thống phân tích hiện đại.

III. Giải pháp đột phá Kiểm tra thực thi ký hiệu động hiệu quả

Phương pháp kiểm tra thực thi ký hiệu động (concolic testing) là trọng tâm của giải pháp lai ghép, kết hợp sức mạnh của thực thi ký hiệu và thực thi cụ thể. Đây là một kỹ thuật mạnh mẽ để xây dựng đồ thị luồng điều khiển một cách chính xác. Quá trình bắt đầu bằng việc phân tích tĩnh một đoạn mã. Thay vì sử dụng giá trị cụ thể, các đầu vào được biểu diễn bằng các ký hiệu toán học. Khi đi qua các nhánh điều kiện, hệ thống sẽ xây dựng một tập hợp các ràng buộc logic gọi là điều kiện đường đi (path condition). Khi gặp một lệnh nhảy không trực tiếp, hệ thống sẽ dừng lại. Nó sử dụng một bộ giải SMT (Satisfiability Modulo Theories) để giải các điều kiện đường đi, từ đó sinh ra các giá trị đầu vào cụ thể (test cases) để có thể đi vào từng nhánh chưa được khám phá. Sau đó, kiểm tra động được thực thi với các giá trị này, giúp xác định chính xác địa chỉ đích của lệnh nhảy. Cách tiếp cận này đảm bảo cả độ bao phủ của phân tích tĩnh và tính chính xác của kiểm tra động.

3.1. Quy trình tạo điều kiện đường đi bằng thực thi ký hiệu

Thực thi ký hiệu (symbolic execution) là bước đầu tiên trong phương pháp lai ghép. Chương trình không chạy với các giá trị số cụ thể mà với các biến ký hiệu (ví dụ: x = α). Khi chương trình thực thi, trạng thái của các biến và bộ nhớ được biểu diễn dưới dạng các biểu thức toán học liên quan đến các ký hiệu đầu vào này. Mỗi khi gặp một lệnh rẽ nhánh (ví dụ: if (x > 10)), một ràng buộc mới (α > 10) được thêm vào điều kiện đường đi hiện tại. Quá trình này tiếp tục cho đến khi kết thúc một đường thực thi hoặc gặp một lệnh phức tạp như lệnh gọi hệ thống hoặc lệnh nhảy động. Kết quả là một tập hợp các điều kiện đường đi, mỗi điều kiện tương ứng với một đường thực thi duy nhất trong chương trình.

3.2. Vai trò của giả lập nhị phân trong kiểm tra động mục tiêu

Sau khi bộ giải SMT sinh ra các giá trị đầu vào cụ thể từ điều kiện đường đi, khối giả lập nhị phân (Binary Emulation) sẽ tiếp nhận vai trò. Khối này tạo ra một môi trường ảo để thực thi mã nhị phân với các giá trị đầu vào đó. Quá trình này được gọi là kiểm tra động có mục tiêu. Vì đầu vào đã được thiết kế để dẫn chương trình đi theo một đường cụ thể, trình giả lập có thể theo dõi chính xác giá trị của các thanh ghi và bộ nhớ tại thời điểm thực thi lệnh nhảy không trực tiếp. Nhờ đó, địa chỉ đích thực sự được xác định và thêm vào đồ thị luồng điều khiển. Quá trình này lặp lại cho đến khi tất cả các nhánh khả thi từ lệnh nhảy động đều được khám phá.

IV. Tăng tốc phân tích Bí quyết song song hóa và xử lý đa luồng

Một thách thức lớn của kiểm tra thực thi ký hiệu động là bùng nổ đường đi (path explosion), khiến thời gian phân tích kéo dài. Để giải quyết vấn đề này, luận án đề xuất một giải pháp đột phá là áp dụng giải thuật song song hóa với tính toán đa luồng. Ý tưởng chính là phân chia công việc khám phá các trạng thái và đường đi của chương trình cho nhiều luồng xử lý hoạt động đồng thời. Mỗi luồng sẽ chịu trách nhiệm phân tích một tập hợp các đường đi khác nhau. Để tránh lãng phí tài nguyên do phân tích trùng lặp các trạng thái đã được khám phá bởi luồng khác, một kỹ thuật băm được sử dụng để lưu trữ và tra cứu nhanh thông tin về các nút đã xử lý. Hơn nữa, giải thuật di truyền cũng được tích hợp để tối ưu hóa việc lựa chọn các đường đi cần ưu tiên phân tích, giúp ngăn chặn sự phân tích dư thừa. Sự kết hợp này không chỉ giúp giảm đáng kể thời gian xây dựng đồ thị luồng điều khiển mà còn tối ưu hóa việc sử dụng tài nguyên hệ thống, giúp phương pháp này trở nên khả thi khi áp dụng trên các chương trình lớn và phức tạp.

4.1. Áp dụng tính toán đa luồng để tăng tốc khám phá trạng thái

Cơ chế tính toán đa luồng cho phép hệ thống phân tích nhiều đường thực thi một cách đồng thời. Thay vì một tiến trình duy nhất duyệt qua từng đường đi một cách tuần tự, nhiều luồng được tạo ra để khám phá song song không gian trạng thái của chương trình. Cách tiếp cận này đặc biệt hiệu quả tại các điểm rẽ nhánh phức tạp hoặc các lệnh nhảy động có nhiều đích đến tiềm năng. Bằng cách phân chia công việc, tổng thời gian cần thiết để bao phủ toàn bộ các đường đi quan trọng trong phân tích mã nhị phân được rút ngắn đáng kể, giúp tăng hiệu năng tổng thể của hệ thống.

4.2. Giảm phân tích dư thừa bằng kỹ thuật băm và di truyền

Để đảm bảo các luồng không thực hiện lại công việc của nhau, luận án đề xuất sử dụng bảng băm để lưu trữ các trạng thái đã được xử lý. Trước khi một luồng bắt đầu phân tích một trạng thái mới, nó sẽ kiểm tra trong bảng băm. Nếu trạng thái đã tồn tại, luồng sẽ bỏ qua và chuyển sang trạng thái khác. Ngoài ra, giải thuật di truyền được sử dụng để ưu tiên các đường đi có khả năng dẫn đến các vùng mã chưa được khám phá. Sự kết hợp giữa kỹ thuật băm và thuật toán di truyền giúp tối ưu hóa quá trình khám phá, tập trung tài nguyên vào những phần quan trọng nhất của chương trình và giảm thiểu tối đa sự dư thừa trong phân tích mã độc.

V. Ứng dụng thực tiễn Phân tích mã độc và nhận diện packer

Việc xây dựng đồ thị luồng điều khiển chính xác mở ra nhiều ứng dụng thực tiễn giá trị, đặc biệt trong lĩnh vực an ninh mạng. Luận án đã chứng minh hiệu quả của phương pháp lai ghép thông qua việc áp dụng vào bài toán phân tích mã độc. Một trong những ứng dụng nổi bật là nhận diện chương trình đóng gói (packer). Dựa trên CFG thu được, hệ thống có thể trích xuất các đặc trưng hành vi và cấu trúc, chẳng hạn như chuỗi các kỹ thuật làm rối được sử dụng. Sau đó, các mô hình học máy như kiểm tra Chi bình phươngMô hình Markov ẩn (HMM) được áp dụng để phân loại packer. Một hướng đi sáng tạo khác là chuyển đổi CFG thành dạng hình ảnh và sử dụng các mô hình học sâu (deep learning), cụ thể là mạng nơ-ron tích chập (CNN), để phân loại các họ mã độc. Tất cả những nghiên cứu này được hiện thực hóa qua công cụ BE-PUM, một hệ thống hoàn chỉnh cho phép tự động xây dựng CFG từ mã nhị phân và hỗ trợ các phân tích nâng cao.

5.1. Nhận diện chương trình đóng gói qua Chi bình phương và HMM

Dựa trên đồ thị luồng điều khiển đã xây dựng, hệ thống có thể phát hiện các dấu hiệu của kỹ thuật làm rối mã. Mỗi packer thường có một “chữ ký” hành vi riêng, thể hiện qua tần suất và thứ tự sử dụng các kỹ thuật này. Luận án đề xuất hai phương pháp thống kê để nhận diện packer: kiểm tra Chi bình phương để so sánh vector tần suất của các kỹ thuật làm rối với các mẫu đã biết, và Mô hình Markov ẩn (HMM) để nắm bắt trình tự chuyển đổi giữa các kỹ thuật. Các thực nghiệm cho thấy phương pháp này có khả năng nhận diện chính xác các packer phổ biến và thậm chí cả những packer tự xây dựng, vượt trội hơn các công cụ như PEiD hay VirusTotal trong nhiều trường hợp.

5.2. Phân loại mã độc bằng học sâu trên ảnh hóa đồ thị CFG

Đây là một ứng dụng đột phá, kết hợp phân tích mã nhị phân với thị giác máy tính. Mỗi đồ thị luồng điều khiển được chuyển đổi thành một ma trận kề, sau đó được trực quan hóa thành một hình ảnh trắng đen. Hình ảnh này giữ lại các đặc trưng cấu trúc quan trọng của luồng thực thi chương trình. Các mô hình học sâu như YOLO hay các mạng CNN khác sau đó được huấn luyện trên tập dữ liệu lớn các hình ảnh CFG này để học cách phân biệt giữa các họ mã độc khác nhau. Phương pháp này cho phép hệ thống học được các mẫu phức tạp và trừu tượng mà các phương pháp dựa trên chữ ký truyền thống không thể phát hiện, mang lại hiệu quả cao trong việc phân loại các biến thể mã độc mới.

5.3. Công cụ BE PUM Hiện thực hóa phương pháp nghiên cứu

BE-PUM (Binary Emulator for Pushdown Model generation) là công cụ hoàn chỉnh được phát triển trong luận án, hiện thực hóa toàn bộ khung thức lai ghép. Công cụ này tự động hóa quá trình kết hợp phân tích tĩnh và kiểm tra động, xử lý hiệu quả các lệnh nhảy không trực tiếp và các kỹ thuật làm rối. So sánh với các công cụ hàng đầu như IDA Pro và JakStab, BE-PUM đã chứng tỏ khả năng xây dựng đồ thị luồng điều khiển chính xác hơn, đặc biệt trên các mẫu mã độc phức tạp như EMDIVI và WannaCry. BE-PUM không chỉ là một sản phẩm nghiên cứu mà còn là một công cụ thực tiễn, có giá trị cho cộng đồng an ninh mạng.

VI. Kết luận và tương lai của phân tích mã nhị phân lai ghép

Phương pháp kết hợp phân tích tĩnh và kiểm tra động đã chứng tỏ là một hướng đi hiệu quả và cần thiết để vượt qua các thách thức trong phân tích mã nhị phân hiện đại. Bằng cách giải quyết triệt để vấn đề lệnh nhảy không trực tiếp, kỹ thuật này cho phép xây dựng đồ thị luồng điều khiển với độ chính xác và đầy đủ cao, điều mà các phương pháp đơn lẻ không thể đạt được. Các ứng dụng trong phân tích mã độc, từ nhận diện packer đến phân loại bằng học sâu, đã khẳng định tính thực tiễn và tiềm năng to lớn của hướng nghiên cứu này. Công cụ BE-PUM là minh chứng rõ ràng cho sự thành công trong việc chuyển hóa lý thuyết thành một giải pháp hoạt động. Tương lai của lĩnh vực này hứa hẹn sẽ tiếp tục phát triển, tập trung vào việc mở rộng hỗ trợ cho nhiều kiến trúc phần cứng hơn, cải tiến các thuật toán học máy và tích hợp sâu hơn vào các quy trình phòng thủ an ninh mạng tự động.

6.1. Đánh giá ưu điểm của việc kết hợp phân tích tĩnh và động

Ưu điểm lớn nhất của phương pháp lai ghép là sự cân bằng giữa độ bao phủ và tính chính xác. Phân tích tĩnh cung cấp cái nhìn tổng quan về cấu trúc chương trình, trong khi kiểm tra động xác minh các đường đi quan trọng, đặc biệt là các luồng điều khiển bị che giấu. Cách tiếp cận này giúp giảm thiểu đáng kể các kết quả dương tính giả từ phân tích tĩnh và khắc phục hạn chế về độ bao phủ của kiểm tra động. Kết quả là một đồ thị luồng điều khiển đáng tin cậy, làm cơ sở vững chắc cho các phân tích bảo mật sau đó.

6.2. Hướng nghiên cứu mở rộng cho công cụ BE PUM và xa hơn

Tương lai của công cụ BE-PUM và các hệ thống tương tự nằm ở việc mở rộng khả năng. Các hướng nghiên cứu tiềm năng bao gồm: hỗ trợ các kiến trúc mới như 64-bit và ARM, tích hợp các bộ giải SMT mạnh hơn để xử lý các điều kiện đường đi phức tạp hơn, và áp dụng các mô hình học máy tiên tiến hơn để tự động trích xuất các đặc trưng an ninh từ CFG. Hơn nữa, việc tích hợp các kỹ thuật này vào các hệ thống phát hiện và phản ứng xâm nhập (IDPS) thời gian thực sẽ là một bước tiến quan trọng, giúp tự động hóa quá trình phân tích mã độc và tăng cường khả năng phòng thủ của hệ thống.

05/10/2025
Kết hợp phân tích tĩnh và kiểm tra động trong việc xây dựng đồ thị luồng điều khiển phục vụ phân tích mã nhị phân luận văn luận án theses

Trích đoạn nội dung tài liệu

CHƯƠNG 1.1 Giới thiệu Hiện nay, với sự phát triển của phần mềm, cả về số lượng, chất lượng và độ phức tạp, vấn đề phân tích và kiểm tra tính đúng đắn của chương trình nổi lên như một nhu cầu cấp thiết. Những thiệt hại gây ra do lỗi chương trình là rất to lớn, không chỉ về mặt kinh tế mà còn tác động rất sâu rộng trên nhiều lĩnh vực. Năm 1996, lỗi phần mềm trong tên lửa European Ariane 5 đã làm hệ thống rơi khoảng 40 giây sau khi cất cánh và gây ra thiệt hại ước tính khoảng nửa tỷ đô la [1]. Năm 2016, theo một thống kê của Tricentis [2], khoảng 1.1 nghìn tỷ đã được tiêu tốn để khắc phục những thiệt hại do lỗi chương trình.

Những lỗi này được tìm thấy trong hơn 363 công ty, ảnh hưởng đến hơn 4 tỷ khách hàng và làm mất tổng thời gian hơn 315 năm để khắc phục. Trong phân tích chương trình, kiểm tra (testing) là một khâu quan trọng và không thể thiếu. Kiểm thử là một qui trình bao gồm các bước từ xét duyệt và kiểm tra một cách thủ công [3] cho đến sinh ra những trường hợp kiểm tra (test case) phù hợp dựa trên việc phân tích sự bao phủ [4]. Tuy nhiên, phương pháp này tồn tại lỗ hổng, do không thể sinh đầy đủ các trường hợp kiểm tra cần thiết để đảm bảo chương trình không có lỗi.

Mặt khác, phương pháp kiểm thử đòi hỏi thực thi đoạn mã (code) cần kiểm tra trong môi trường thực. Điều này trong thực tế khá khó khăn. Một giải pháp được đề xuất là xây dựng môi trường giả lập, thông qua các công cụ ảo hóa. Tuy nhiên, các công cụ này rất tốn kém và không phản ánh chính xác môi trường thực tế.

Chính từ những vấn đề này của kiểm thử đã nảy sinh phương pháp kiểm chứng phần mềm (software verification), trong đó tính đúng của phần mềm sẽ được kiểm chứng dựa trên luận lí học (logic). Hướng tiếp cận này chủ yếu dựa vào các phương pháp hình thức (formal method). Ý tưởng chính của phương pháp hình thức là biểu diễn phần mềm và hệ thống thành các đối tượng toán học, từ đó suy luận và xác định các hành vi của chúng. Trong lĩnh vực áp dụng phương pháp hình thức để kiểm chứng 1 phần mềm, có hai hướng tiếp cận chủ yếu: chứng minh lí thuyết (theorem proving) và kiểm tra mô hình (model checking) [5].

Các phương pháp hình thức có thể được sử dụng để kiểm tra phần mềm ở nhiều mức độ khác nhau, từ các đặc tả (specification), thiết kế (design), mã nguồn (source code) cho đến mã nhị phân (binary code) [6]. Hiện nay, hướng nghiên cứu sử dụng phương pháp hình thức cho mã nhị phân ngày càng được sử dụng rộng rãi trong giới học thuật lẫn cộng đồng doanh nghiệp do một số nguyên nhân sau.1 Những lợi ích của việc phân tích mã nhị phân Thứ nhất, hầu hết các mã nguồn hiện nay thường đóng nên việc kiểm tra là khó khả thi, đặc biệt là đối với các mã độc. Trên thực tế, phân tích mã độc là một ví dụ điển hình và rõ ràng của vấn đề phân tích mã nhị phân không có mã nguồn. Thứ hai, mã nhị phân phụ thuộc vào quá trình biên dịch của chương trình.

Mà quá trình biên dịch có khả năng làm thay đổi hành vi của chương trình do quá trình tối ưu hóa mã [7]. Chính vì nguyên nhân này, việc kiểm tra trên mã nhị phân sẽ cho ra các kết quả đáng tin cậy vì máy tính sẽ thực thi mã nhị phân thay vì mã nguồn như là kết quả duy nhất và cuối cùng. Thứ ba, khi chương trình được phát triển từ nhiều ngôn ngữ khác nhau và được tích hợp nhiều module khác nhau, vấn đề phân tích mã nguồn sẽ trở nên hết sức khó khăn do vấn đề thiếu hụt đặc tả chi tiết các module.2 Những thách thức trong phân tích mã nhị phân Tuy nhiên, trong phương pháp phân tích mã nhị phân, cũng nổi lên rất nhiều những thách thức:  Thách thức đầu tiên là độ phức tạp của mã nhị phân. Những kiến trúc CISC như x86 có rất nhiều các câu lệnh khác nhau để phục vụ các tác vụ hệ thống.

Chẳng hạn, trong kiến trúc x86, có hàng trăm câu lệnh và hàng ngàn khả năng kết hợp giữa các toán hạng [8]. Ví dụ như, hơn 300 câu lệnh SIMD được sử dụng trong nền tảng x86 như MMX, SSE, SSE2, SSE3, SSSE3, và SSE4 [9] để hỗ trợ những tác vụ tính toán vector đồng thời trên nhiều bytes hoặc từ (word). Mặt khác, mỗi câu 2 lệnh lại có ngữ nghĩa phức tạp. Một chương trình phân tích mã nhị phân phải nhận biết và biểu diễn một cách chính xác các câu lệnh này.

Đây là một vấn đề khó khăn.  Thách thức thứ hai đến từ vấn đề thiếu hụt các cấu trúc ngữ nghĩa bậc cao (high level semantic structure) trong mã nhị phân. Mã nhị phân không chứa các hàm và các kiểu dữ liệu. Trong mã nhị phân, biến toàn cục, biến cục bộ, kiểu mảng, và bảng ghi đều được biểu diễn thành một dãy liên tục các giá trị trong bộ nhớ.

Các biến có thể được lưu trữ trong các thanh ghi. Kiểu dữ liệu khác nhau được biểu diễn bởi số lượng byte khác nhau. Do đó, khi một giá trị mới được gán vào thanh ghi hay ô nhớ, giá trị của nó có thể ghi đè một phần hay toàn bộ những byte thuộc về các biến khác.  Thứ ba, chương trình phân tích mã nhị phân phải giải quyết các vấn đề về sự nhập nhằng giữa mã chương trình và dữ liệu (code and data ambituity).

Có nhiều định dạng khác nhau để lưu trữ chương trình nhị phân trên máy tính, cho phép các mã nhị phân này có thể được nạp và thực thi dễ dàng bởi hệ điều hành. Đối với hệ thống x86, định dạng thông dụng nhất là PE [10] được sử dụng trong Windows và ELF [11] được sử dụng trong Linux. Cả hai định dạng này cho phép nhóm chương trình thực thi thành các phân vùng (section), nhằm lưu trữ mã thực thi hay dữ liệu. Các phân vùng này có thể được thiết lập quyền đọc, ghi hay thực thi.

Tuy nhiên, sự phân chia giữa vùng dành cho mã thực thi và dữ liệu là không chặt chẽ. Do đó, phân vùng chứa mã thực thi có thể chứa dữ liệu ví dụ như các chuỗi, hằng số. Chỉ trong quá trình thực thi của chương trình, vi xử lý (processor) mới xác định chính xác những byte dành cho câu lệnh và những bye dành cho dữ liệu. Vị trí điểm thực thi ban đầu của chương trình được gọi là điểm vào (entry point) và được đặc tả trong tiêu đề (header) của chương trình.

 Thứ tư là vấn đề câu lệnh nhảy không trực tiếp (indirect jump). Một trong những vấn đề lớn khi phân tích mã nhị phân trong x86 là vấn đề xử lý các câu lệnh nhảy không trực tiếp. Các lệnh này tương ứng với những lệnh Goto mà địa chỉ nhảy đến 3 chỉ được tính toán tại thời điểm chạy của chương trình như câu lệnh JMP EAX. Địa chỉ nhảy của câu lệnh được lưu trong thanh ghi EAX và chỉ được xác định trong thời điểm thực thi của chương trình.

Trong phân tích mã nhị phân, mọi địa chỉ trong mã lệnh đều là những địa chỉ tiềm năng của của câu lệnh nhảy không trực tiếp. Do đó, việc xử lý địa chỉ nhảy của những câu lệnh này không thành công trong có thể dẫn đến (i) phân tích chương trình không chính xác; (ii) vấn đề xấp xỉ quá mức (over-approximation) khi các câu lệnh nhảy không trực tiếp sẽ nhảy đến mọi địa chỉ có thể của chương trình. Trong trường hợp này, độ phức tạp của chương trình phân tích sẽ bùng nổ.  Thứ năm là vấn đề các câu lệnh trùng lắp (overlapping instructions).

Trong môi trường x86, các câu lệnh có độ dài thay đổi. Mỗi câu lệnh chứa mã thực thi (opcode), định nghĩa nội dung câu lệnh, kiểu dữ liệu của câu lệnh và danh sách các toán hạng. Các toán hạng có thể là thanh ghi, các giá trị trung gian hay vị trí bộ nhớ. Bản chất độ dài thay đổi của các lệnh x86 dẫn đến vấn đề các câu lệnh trùng nhau.

Cùng một chuỗi byte có thể được diễn dịch thành những câu lệnh khác nhau bởi bộ vi xử lý của chương trình. Xem xét ví dụ trong Hình 1-1, chương trình bắt đầu tại địa chỉ 0000 và được dịch ngược (disassemble) thành câu lệnh mov eax, 0xBBC10300. Tại địa chỉ 000C, câu lệnh được giải mã thành jmp $-10. Câu lệnh jmp $-10 chiếm 2 bytes.

Do đó, câu lệnh kế tiếp sẽ được bắt đầu tại địa chỉ 000E và được giải mã thành add eax, ebx. Tuy nhiên, câu lệnh jmp $-10 sẽ chuyển luồng thực thi của chương trình đến vị trí 0002. Do đó, câu lệnh kế tiếp sẽ bắt đầu tại địa chỉ 0002 và được biểu diễn thành add eax, ecx. Câu lệnh mới này lồng với câu lệnh gốc ban đầu tại địa chỉ 0000 như mô tả trong Hình 1-2.

4 0000: B8 00 03 C1 BB mov eax, 0xBBC10300 0005: B9 00 00 00 05 mov ecx, 0x05000000 000A: 03 C1 add eax, ecx 000C: EB F4 jmp $-10 000E: 03 C3 add eax, ebx 0010: C3 ret Hình 1-1 Ví dụ về vấn đề các câu lệnh trùng lắp trong phân tích mã nhị phân 0000: B8 00 03 C1 BB mov eax, 0xBBC10300 0005: B9 00 00 00 05 mov ecx, 0x05000000 000A: 03 C1 add eax, ecx 000C: EB F4 jmp $-10 0002: 03 C1 add eax, ecx 0004: BB B9 00 00 00 mov ebx, 0xB9 0009: 05 03 C1 EB F4 add eax, 0xF4EBC103 000E: 03 C3 add eax, ebx 0010: C3 ret Hình 1-2 Luồng thực thi đúng của chương trình Những khó khăn trên đã dẫn đến vấn đề bùng nổ độ phức tạp trong phân tích mã nhị phân của các chương trình lớn. Vì thế có rất ít các công cụ phân tích mã nhị phân có khả năng phân tích các chương trình thực tế với độ phức tạp cao. Đây là một nhu cầu bức thiết đặt ra trong thời điểm hiện tại và là nguyên nhân thúc đẩy của đề tài này.3 Bài toán xây dựng đồ thị luồng điều khiển. Trong ngành công nghiệp phần mềm, xu hướng kiểm tra phần mềm dựa trên mã nhị phân đang phát triển một cách mạnh mẽ.

Hình 1-3 mô tả bốn bước chính trong quá trình phân tích mã nhị phân. Trong bước đầu tiên, chương trình tiến hành dịch ngược mã nhị phân. Bước thứ hai là quá trình xây dựng biểu diễn trung gian (intermediate 5 representation) dựa trên kết quả bước 1. Trong bước 3, đồ thị luồng điều khiển (control flow graph) được xây dựng dựa trên kết quả của bước 2.

Nội dung được bảo vệ bản quyền — Tải xuống đầy đủ