18/09/2018, 15:07

PHÁT HIỆN CẤU TRÚC CODE C TRONG HỢP NGỮ PHẦN 2

Được sự hưởng ứng của bạn đọc với bài viết Phát hiện cấu trúc code C trong hợp ngữ Phần 1, tiếp nối chủ đề này tôi xin gửi đến các bạn các phân tích: Phát hiện lệnh if, Phát hiện vòng lặp, Tìm kiếm vòng lặp while, Phân tích lệnh switch, Phát hiện các câu lệnh if lồng nhau. Dịch ngược các phép ...

Được sự hưởng ứng của bạn đọc với bài viết Phát hiện cấu trúc code C trong hợp ngữ Phần 1, tiếp nối chủ đề này tôi xin gửi đến các bạn các phân tích: Phát hiện lệnh if, Phát hiện vòng lặp, Tìm kiếm vòng lặp while, Phân tích lệnh switch, Phát hiện các câu lệnh if lồng nhau.

Dịch ngược các phép tính số học

Code 3 minh họa một chương trình C với 2 biến và một số phép tính số học, trong đó có — và ++, được dùng để giảm/tăng 1 giá trị. Phép toán % thực hiện phép chia modulo giữa 2 biến, kết quả là phần dư sau khi thực hiện phép chia.

Code 3: Code C và hợp ngữ tương ứng với 2 biến và một số phép tính số học

Code 3: Code C và hợp ngữ tương ứng với 2 biến và một số phép tính số học

Trong ví dụ này, a b là các biến cục bộ vì chúng được tham chiếu bởi stack, có địa chỉ lần lượt là [rbp+var_4] và [rbp+var_8], nhận giá trị khởi tạo tương ứng là 0 và 1. Phép cộng a = a + 11 được được thực hiện trực tiếp tại ô nhớ [rbp+var_4] làm giá trị a tăng 11 (0Bh). Giá trị của b sau đó được truyền vào eax  và phép trừ a = a – b là thực hiện trừ trực tiếp giá trị của eax trên ô nhớ tại địa chỉ [rbp+var_4]. Trình biên dịch lựa chọn sử dụng lệnh sub  và add  thay vì lệnh inc dec  để thực hiện phép toán — và ++.

13 lệnh hợp ngữ từ 0x40159D tới 0x4015BA thực hiện phép chia modulo a cho 3 bằng thuật toán nhân với magic number tương ứng với 3 (55555556h hay 1431655766 trong hệ cơ số thập phân), sau đó dịch phải 31 bit trên giá trị thu được (https://stackoverflow.com/questions/2071004/write-a-function-to-divide-a-number-by-3-without-using-and-operators-ito). Giá trị sau cùng được lưu trong thanh ghi eax nên kết quả của phép tính được truyền từ eax tới b ([rbp+var_8]).

Xem Phần 1 của chủ đề Phát hiện cấu trúc Code C trong hợp ngữ TẠI ĐÂY

Phát hiện lệnh if

Lệnh if được dùng để thay đổi luồng thực thi chương trình dựa trên những điều kiện cụ thể. Code 4 thể hiện một lệnh if đơn giản trong code C và hợp ngữ tương ứng. Chú ý tới lệnh nhảy có điều kiện jnz ở dòng 0x401591. Một lệnh if phải dẫn đến một lệnh nhảy có điều kiện, nhưng không phải lệnh nhảy có điều kiện nào cũng được gọi từ một lệnh if nào đó.

Code 4: Ví dụ về lệnh if trong Code C và hợp ngữ tương ứng

Code 4: Ví dụ về lệnh if trong Code C và hợp ngữ tương ứng

code

Như thấy trong phần code hợp ngữ, một quyết định phải được thực hiện trước khi code trong khối lệnh if tại code C được thực hiện. Quyết định này tương ứng với lệnh nhảy có điều kiện jnz tại dòng 0x401591. Quyết định nhảy được thực hiện dựa trên kết quả lệnh so sánh (cmp), kiểm tra xem [rbp+var_8] có bằng [rbp+var_4] không (hay x có bằng y không, trong code C) tại dòng 0x40158E. Nếu 2 giá trị không bằng nhau, lệnh nhảy được thực hiện và chạy lệnh in ra màn hình thông báo “x is not equal to y.”; trái lại, nếu 2 giá trị bằng nhau, code tiếp tục thực thi nhánh ban đầu và in ra màn hình thông báo “x equals to y.”.

Cũng chú ý đến lệnh nhảy jmp tại dòng 0x40159F nhảy tới một đoạn code khác (loc_4015AD). Trong ví dụ này, đoạn code loc_4015AD đơn thuần là đoạn kết thúc hàm của hàm main.

Như vậy, bản chất lệnh if được cấu thành từ một lệnh nhảy có điều kiện, các tập lệnh phía sau có thể được thực thi hoặc không được thực thi, phụ thuộc vào điều kiện trong câu lệnh so sánh. Như vậy, lệnh if cũng có thể được sử dụng cho việc tạo các mã lệnh rác.

Phát hiện các câu lệnh if lồng nhau

Code 5 là một chương trình C bao gồm các lệnh if lồng nhau, tương tự code 4 nhưng có thêm 2 lệnh if nữa được thêm vào bên trong lệnh if ban đầu. Các lệnh if thêm vào này kiểm tra xem z có bằng 0 hay không.

code 1.1

Code 5: Code C và hợp ngữ tương ứng với những lệnh if lồng nhau

Code 5: Code C và hợp ngữ tương ứng với những lệnh if lồng nhau

Mặc dù chỉ là một thay đổi rất nhỏ trong code C, code hợp ngữ đã trở nên phức tạp hơn nhiều sau khi thêm một lệnh if. Do đó, như đã đề cập ở trên, lệnh if cũng là câu lệnh rất thú vị sử dụng làm code rác và obfucate chương trình.

Phát hiện vòng lặp

Các vòng lặp rất phổ biến trong mọi chương trình và phát hiện chúng trong phân tích mã độc là một bước quan trọng.

Tìm kiếm vòng lặp for

for là cơ chế lặp cơ bản dùng trong lập trình C, một vòng lặp for luôn có 4 thành phần: khởi đầu vòng lặp, so sánh, thực thi lệnh, và bộ đếm tăng hoặc giảm.

Đoạn code dưới đây minh họa một vòng lặp for đơn giản.

Code 7: Một vòng lặp for trong code C và hợp ngữ tương ứng

Code 7: Một vòng lặp for trong code C và hợp ngữ tương ứng

Trong ví dụ này, điểm khởi đầu vòng lặp được gán giá trị 0 (zero), bộ so sánh kiểm tra xem i có nhỏ hơn 100 không. Nếu i nhỏ hơn 100, thực hiện lệnh printf, bộ đếm tăng sẽ thêm 1 vào i, và tiến trình sẽ lại kiểm tra xem i có nhỏ hơn 100 không. Các bước này sẽ lặp lại cho tới khi i lớn hơn hoặc bằng 100.

Trong hợp ngữ, vòng lặp for có thể được phát hiện bằng cách tìm ra vị trí của 4 thành phần của nó – khởi đầu vòng lặp, so sánh, thực thi lệnh và bộ đếm tăng hoặc giảm. Trong đoạn code bên dưới, dòng 0x40157D chính là điểm khởi đầu vòng lặp. Đoạn code dòng 0x401597 ứng với bộ đếm tăng. Phép so sánh điều kiện được thực hiện tại dòng 0x40159B, và tại dòng 0x40159F thì một quyết định được thực hiện bởi lệnh nhảy có điều kiện. Nếu lệnh nhảy jle được thực hiện, lệnh printf sẽ được gọi (loc_401586).

Một vòng lặp for có thể được phát hiện bằng cách dùng chế độ biểu đồ của IDA Pro. Trong hình, mũi tên hướng ngược lên sau một lệnh tăng 1 (add [rbp+var_4], 1) là dấu hiệu của một vòng lặp. Các mũi tên này làm cho vòng lặp dễ được phát hiện trong chế độ biểu đồ hơn là những dòng code thiếu trực quan trong chế độ mặc định của cửa sổ IDA view. Biểu đồ này có 4 khối code:  Khối code đầu tiên, trên cùng, với 2 lệnh cuối chính là bộ khởi đầu vòng lặp for. Khối code nhỏ ngay dưới đó là bộ so sánh giá trị [rbp+var_4] với 63h (99 trong hệ thập phân). Khối dưới cùng bên góc trái là thành phần thực thi lệnh và bao gồm cả bộ đếm tăng lên 1 và nhảy lại bộ so sánh. Khối code nhỏ bên góc phải chỉ là kết thúc hàm (function epilogue), đảm nhiệm giải phóng stack và trả về.

code 1.4

Như vậy, chúng ta có thể thấy bản chất vòng lặp for là một chuỗi các lệnh trong đó được cấu thành chính bởi một lệnh nhảy có điều kiện, lệnh nhảy này trỏ tới nội dung thực thi chính trong vòng lặp.

Tìm kiếm vòng lặp while

Vòng lặp while thường xuyên được sử dụng trong mã độc để lặp cho tới khi thỏa mãn một điều kiện, chẳng hạn như nhận được một gói tin hoặc một lệnh điều khiển. Trong hợp ngữ, các vòng lặp while nhìn giống như các vòng lặp for, nhưng chúng thường dễ hiểu hơn. Vòng lặp while được mô tả trong Code 6 sẽ tiếp tục lặp cho tới khi kết quả trả về từ  hàm checkResult khác 0.

Code 6: Một vòng lặp while trong C và trong hợp ngữ tương ứng

Code 6: Một vòng lặp while trong C và trong hợp ngữ tương ứng

Code hợp ngữ trong Code 6 nhìn giống như một vòng lặp for, ngoại trừ việc nó thiếu bộ đếm tăng. Một lệnh nhảy có điều kiện xảy ra tại dòng 0x4015BD và một lệnh nhảy không điều kiện tại dòng 0x4015A2, nhưng cách duy nhất để code dừng vòng lặp là thỏa mãn điều kiện của lệnh nhảy jz (dòng 0x4015BD). Như vậy, về bản chất hai vòng lặp for và while hay do-while tương đối giống nhau. Điều kiện dừng được quyết định bởi một lệnh nhảy có điều kiện.

Phân tích lệnh switch

Lệnh switch được sử dụng để thực hiện quyết định dựa trên một ký tự hay một số nguyên. Các backdoors thường chọn từ một chuỗi các hành vi bằng cách sử dụng một giá trị byte đơn.

Code 7 minh họa một lệnh switch sử dụng biến i. Tùy vào giá trị biến i, đoạn code phía dưới đây sẽ thực hiện một hành động tương ứng.

code 1.6

Code 7: Code C và hợp ngữ tương ứng cho một lệnh switch với 3 lựa chọn và hợp ngữ tương ứng

Code 7: Code C và hợp ngữ tương ứng cho một lệnh switch với 3 lựa chọn và hợp ngữ tương ứng

Khối lệnh switch chứa một loạt các lệnh nhảy có điều kiện ở đoạn code giữa dòng 0x401580 và 0x40158D. Việc xác định lệnh nhảy có điều kiện nào được thực thi dựa trên phép so sánh xảy ra ngay trước mỗi lệnh nhảy.

Lệnh switch này có 3 lựa chọn, tại loc_401591, loc_4015A7loc_4015BD. Các đoạn code này là độc lập với nhau vì có một lệnh nhảy không điều kiện tại cuối đoạn code chính (dòng 0x40158F).

Hình bên dưới là sơ đồ các lựa chọn switch bằng cách chia nhỏ các code thực thi từ vị trí các phép so sánh để đưa ra quyết định tiếp theo. Các khối code được gán nhãn (1), (2)(3) tương ứng với các trường hợp khác nhau trong lệnh switch. Tất cả các khối code này đều dẫn đến khối code dưới cùng, chính là kết thúc hàm (epilouge).

Từ những dòng code dịch ngược này, rất khó phát hiện đâu là code nguyên thủy của lệnh switch hay là tập các lệnh if liên tiếp vì cả hai đều có thể chứa một nhóm các lệnh cmp jmp. Khi thực hiện dịch ngược, có thể không phải lúc nào ta cũng quay lại mã nguồn nguyên thủy được vì có nhiều cách để trình bày cùng một cấu trúc code trong hợp ngữ, tất cả các cách đó đều có giá trị tương đương.

code 1.8

Tuy khó phân biệt giữa switch và các lệnh if lồng nhau nhưng việc này không ảnh hưởng quá lớn đến quá trình phân tích.

Đón đọc thêm nhiều bài viết của Securitybox tại: https://securitybox.vn/blog/

0