1. Giới
thiệu
Tổng quan khi biên dịch một chương trình, compiler sẽ thực hiện như sau:
Các bước cơ bản của một chương trình compiler |
Khi decompile một chương trình sang mã giả C, hexrays sẽ làm điều ngược lại:
Các bước cơ bản của một chương trình decompiler |
Một trong những bước quan trọng trong việc decompile một chương trình là ngôn ngữ trung gian (Intermediate Language/IL). IDA microcode là một IL, là thành phần chính của Hexrays decompiler. Microcode được Ilfak bắt đầu thiết kế vào năm 1999 nhưng mãi đến tận năm 2018, Hexrays mới cung cấp API cho người dùng để truy cập sử dụng microcode. Trong bài này, chúng ta sẽ cùng nhau tìm hiểu sơ lược qua IDA microcode và cách ứng dụng ở mức cơ bản nhất.
2. Kiến thức cơ bản
2.1 Tool cần dùng
- IDA Pro
- Plugin Lucid (Dùng để xem microcode một cách trực quan nhất)
- Python (Ngôn ngữ chính dùng để viết plugin)
2.2 Viết plugin sử dụng microcode
Để tiếp cận microcode, chúng ta có thể viết plugin và cài callback vào trong quá trình optimize. Có 2 callback mà chúng ta cần chú ý đến:
- Optinsn_t: Class cho phép chúng ta cài callback vào quá trình optimize instruction.
- Optblock_t: Class cho phép chúng ta cài callback vào quá trình optimize microcode block.
Code dưới đây ví dụ về cách install/remove hai loại callback:
Vậy là ở trong function callback, chúng ta đã có thể tiếp cận được với microcode trong quá trình optimize.
2.3 Các thành phần cơ bản
2.3.1 minsn_t : Microcode instruction
Một microcode instruction có cấu trúc như sau:
Trong đó:
- opcode: lệnh microcode tương ứng
- left, right, destination: các operation tương ứng
Ví dụ ta có lệnh “eax = eax ^ 0x11111111” có thể được biểu diễn như sau:
Microcode instruction được biểu diễn dưới dạng tree |
Trong đó:
- Opcode là m_xor
- Operation left là eax.4, type là mop_r (reg)
- Operation right là 0x11111111.4, type là mop_n (number)
- Operation destination là eax.4, type là mop_r
Một điểm hay của microcode là các operation có thể là các microcode instruction nhỏ hơn (sub instruction). Như vậy, theo lý thuyết khả năng diễn đạt ý nghĩa của microcode instruction là không giới hạn. Ví dụ, ta có lệnh “ecx = ~(arg_0 & arg_4)” được biểu diễn như sau:
Microcode instruction được biểu diễn dưới dạng tree |
Trong đó:
- Opcode là m_bnot
- Operation left là sub instruction m_and (arg_4.4, arg_0.4)
- Operation right không có
- Operation destination là ecx.4, type là mop_r (reg)
2.3.2 mblock_t: Microcode block
Tương tự microcode block chứa các microcode instruction. Cách tổ chức của microcode instruction như sau:
Sơ đồ tổ chức của instruction trong block (Link tham khảo) |
Trong đó:
- Microcode instruction list được tổ chức theo dạng doubly linked list
- Blk.head trỏ tới microcode instruction đầu tiên, blk.tail trỏ tới instruction cuối cùng trong list
- Blk.head.prev và blk.tail.next là NULL
Sự liên hệ của các microcode block có thể được mô tả như sau:
Sự liên hệ của microcode block (Link thảm khảo) |
Trong đó cần lưu ý các thuộc tính sau:
- Type: kiểu type của microcode block
- Predset: Chứa danh sách các microcode block trỏ tới block hiện tại
- Succset: Chứa danh sách các microcode block được block hiện tại trỏ tới
2.3.3 mbl_array_t: Microcode block array
Microcode block array là một array chứa các microcode block. Hình dưới đây mô tả cấu trúc của một microcode block array:
Microcode block array (Link tham khảo) |
Trong đó:
- Các block được lưu dưới dạng doubly linked list.
- Blk0.prevb và blkN.nextb là NULL.
- Dùng hàm get_mblock() trong mbl_array_t để lấy block tương ứng. Truy cập biến public qty trong mbl_array_t để lấy số block trong array.
Lưu ý: Khi modify instruction, block code hoặc cả block code array nên gọi hàm verify lại để đảm bảo chúng ta đã modify chuẩn xác, block code array được modify không để lại lỗi. Nếu quá trình modify để lại lỗi, hàm verify sẽ báo lại cho chúng ta error code. Các bạn có thể tra error code trong file hexrays_sdk\verifier\verify.cpp
2.3.4 maturity
Khi tiến hành decompile một function, về cơ bản chúng ta đang chuyển từ ngôn ngữ bậc thấp sang ngôn ngữ bậc cao. Để làm điều này, microcode có các level maturity để chuyển dần từ microcode level thấp (gần giống với mã máy) sang microcode level cao (gần giống với ngôn ngữ tự nhiên)
So sánh giữa microcode level MMAT_PREOPTIMIZED (gần với mã máy) và MMAT_GLBOPT1 (gần với ngôn ngữ tự nhiên) |
Mặc định có maturity có 8 level:
- MMAT_GENERATED
- MMAT_PREOPTIMIZED
- MMAT_LOCOPT
- MMAT_CALLS
- MMAT_GLBOPT1
- MMAT_GLBOPT2
- MMAT_GLBOPT3
- MMAT_LVARS
Trong đó MMAT_LVARS là bước optimize cuối cùng. Lúc này ngôn ngữ đã gần với ngôn ngữ tự nhiên, sẵn sàng cho bước tiếp theo là chuyển thành SSA Form
2.3.5 minsn_visitor_t: Microcode instruction visitor
Để thuận tiện trong việc thao tác với microcode, hexrays sdk còn cung cấp cho chúng ta một object là minsn_visitor_t (có lẽ là thiết kế theo design pattern visitor). Object này cho phép chúng ta cài 1 callback vào mỗi lần object này visit từng microcode instruction. Ví dụ sau sử dụng minsn_visitor_t để in ra toàn bộ instruction trong microcode block array
3. Viết plugin với microcode
Vậy là phần lý thuyết tạm xong. Chúng ta hãy thử viết plugin với microcode.
Lưu ý: Mục đích viết plugin chỉ ở mức thử nghiệm, do đó các code plugin dưới đây chỉ dừng lại ở mức POC (còn lỗi).
3.1 Plugin install instruction optimize callback
Trong ví dụ này chúng ta sẽ dùng file simplexor.exe. Function tại địa chỉ 0x401000 cơ bản có thể mô tả như sau:
~(a2 & a1) & (a2 | a1)
Function này nhìn khá rối rắm nhưng thực chất có thể optimize thành a2 ^ a1 (Bạn có thể dùng z3 để kiểm tra). Viết plugin đơn giản thực hiện optimize trên instruction bằng cách match and replace. Code ở hàm callback như sau:
Kết quả sau khi chạy plugin:
Kết quả sau khi chạy plugin |
Toàn bộ code của plugin xem tại file xor_optimizer.py.
3.2 Plugin install block optimize callback
Trong ví dụ này, chúng ta sẽ thực hiện trên một file phức tạp hơn: mẫu malware emotet. Malware emotet sử dụng kỹ thuật obfuscation control flow flatten. Chúng ta sẽ thực hiện deobfuscation trong callback optimize block. Thuật toán deobfuscate đơn giản như sau:
- Tìm dispatch variable. Thường dispatch variable của emotet được lưu vào một thanh ghi.
- Tìm các block và block status tương ứng được dispatch trỏ đến. Dùng cách đơn giản nhất là tìm theo lệnh so sánh reg, const.
- Thay đổi control flow graph. Thay vì nhảy đến dispatch, nhảy đến block kế tiếp
- Xóa dispatch.
Về cơ bản code hàm callback sẽ như sau:
Toàn bộ code của plugin xem tại file emotet_deobfuscator.py.
Thực hiện deobfuscate tại hàm 0x409E60 ta được kết quả như sau:
Pseudocode C trước và sau khi deobfuscated |
Bonus: Chúng ta đã thực hiện deobfuscate một hàm của malware emotet. Output là pseudocode C khá clean nhưng ở level assembly language vẫn chưa được deobfuscated. Tiến hành compile lại pseudocode C (chỉ cần sửa lại code C cho khéo là compile được) sau đó rewrite đè lại function gốc ta được kết quả là code assembly đã được deobfuscated. Dùng compiler explorer để compile ta có được code khá trực quan tại link
Hình ảnh Control Flow Graph trước và sau khi sử dụng plugin:
Control flow graph trước và sau khi deobfuscated |
4. Tài liệu tham khảo
- https://hex-rays.com/blog/hex-rays-microcode-api-vs-obfuscating-compiler/
- https://eshard.com/posts/d810-deobfuscation-ida-pro
- Decompiler internals: microcode
- https://hex-rays.com/blog/microcode-in-pictures/
- https://blog.amossys.fr/stage-2019-hexraysmicrocode.html
Dang Dinh Phuong
R&D Center - VinCSS (a member of Vingroup)
No comments:
Post a Comment