What is Packing?
Packing có nghĩa là nén (compressed), hoặc xáo trộn (obfuscated) file thực thi để làm cho file thực thi khó phát hiện, khó phân tích tĩnh, khó dịch ngược. Trong ngữ cảnh của malware, vì mã độc chủ yếu được compressed, obfuscated trong các mẫu đóng gói, các công cụ dùng để phân tích tĩnh sẽ gặp vấn đề khi xác định liệu tệp nhị phân này có phải là mã độc hay không. Bên cạnh nó, một thuật toán packing tốt sẽ làm khó khăn qúa trình phân tích của các nhà phân tích mã độc, từ đó giúp cho vòng đời của mã độc được kéo dài lâu hơn.
Một cách nôm na, chúng ta có thể hiểu packer gồm: compressing packers và encrypting packers. Trong đó compressing packers mục đích là đưa file thực thi về dạng nén, làm giảm kích thước của file, còn encrypting packers có mục đích mã hóa hoặc xáo trộn file đã được nén, nhằm ngăn chặn người dùng dịch ngược. Như đã nói, cách hiểu trên chỉ là hiểu một cách nôm na, bởi vì có nhiều packer kết hợp cả hai tính chất trên, còn có nhưng packer (như UPX), nó chỉ compressing mà không có encrypting.
How does packing work?
Chúng ta sẽ nói về kiến trúc “stub-payload”, một trong những cơ chế được sử dụng nhiều bởi packer, kể cả UPX.
Trong kiến trúc “stub-payload”, một file thực thi sẽ gồn hai thành phần chính: nội dung đã được nén / mã hóa từ file gốc và một đoạn mã dùng để giải nén / giải mã file này về file gốc và thực thi nó. Đoạn mã này được gọi là stub. Về bản chất, file ban đầu được nến / mã hóa, sau đó được bọc trong tệp thực thi mới có chứa mã để đưa nó về trạng thái ban đầu
Indication of Packing
Lacking of Import in Import Address Table (IAT)
Để một file thực thi có thể tương tác với hệ điều hành thì file thực thi cần phải có các hàm được tích hợp vào thư viện hệ thống như kernel32.dll và user32.dll. Khi phân tích một tệp tin đã được giải nén hoàn toàn, ta sẽ thấy một số lượng lớn các import vì mã độc rõ ràng muốn tương tác khá nhiều với hệ điều hành. Tuy nhiên, một stub của một chương trình bị packed không có nhiều chức năng ngoài việc giải nén và thực thi file gốc, do đó, nó sẽ có số lượng import ít hơn rất nhiều so với file chuẩn.
Non-standard Section Names:
Trong những file thực thi bình thường, chúng thường có các sections giống nhau mọi lúc (.text, .data, .rsrc,..). Tuy nhiên, những paker sẽ tự định nghĩa section cho riêng nó, điều này cũng cho thấy những file có section như vậy sẽ không phải là tiêu chuẩn và có thể được đóng gói. Ví dụ như UPX packer sẽ thực thi với các file có tên section là UPX0 và UPX1.
Sections with a small raw size but a large virtual size
Khi chúng ta gặp một file mà có raw size rất nhỏ (đôi khi bằng 0), điều này muốn nói file thực thi không có bất kì dữ liệu nào trong section. Tuy nhiên, khi file thực thi được load vào bộ nhớ, raw size không còn liên quan, và nó được thay bởi virtual size của từng phần cụ thể được cấp phát trong memory. Nếu một file có section được cấp phát với virtual space lớn và raw data rất nhỏ, nó có khả năng cao là những đoạn code dùng để unpacked.
Sections with very high entropy:
Từ entroy đề cập đến phương sai và tính ngẫu nhiên của một phần dữ liệu. Những thứ như ngôn ngữ Tiếng Anh, assembly code, các cấu trúc giao tiếp được xác định rõ ràng khác thường có entropy thấp vì ngôn ngữ có xu hướng tuần theo các mẫu có thể đoán trước được. Tuy nhiên, encrypted data và compressed data không có khả năng dự đoán trước, do đó có entropy cao hơn nhiều. Do đó, khi kiểm tra nếu thấy có entropy cao thì khả năng dữ liệu này đã được nén hoặc mã hóa.
Low number of discernible strings:
Trong file không bị nén, chúng ta sẽ nhận thấy có một lượng nhất định các chuỗi có thể đọc được vì hầu hết các ứng dụng (kể cả malware) sử dụng những protocol được hiện thực bởi ngôn ngữ con người. Do đó, trong một file bình thường, những chuỗi này nên xuất hiện, và khi thấy những chuỗi có thể đọc được ở một file quá ít hoặc không có, khả năng cao file này đã bị nén hoặc bị mã hóa.
Sections with RWX privileges:
Trong các file bình thường, một section sẽ rất ít khi được cho phép cả đọc và ghi, vì chúng ta hiếm khi muốn viết đè file thực thi trong ứng dụng của mình. Ngoài ra, hiếm khi viết trực tiếp vào file thực thi trong lúc nó đang chạy. Do đó, không có lý do nào để section được cấp quyền vừa ghi và vừa đọc ngoại trừ packer. Với packer, dữ liệu sau khi giải nén sẽ được ghi vào section rồi thực thi.
jmp or call Instructions to registers/strange memory addresses:
Trên nhiều packers, địa chỉ tới nơi dữ liệu đã được giải nén thường được lưu trên các thanh ghi và địa chỉ đó thường nằm trong một section khác, do dó chúng ta sẽ thấy bước này nhảy rất xa. Các bước nhảy xa như thế thường không phổ biến, vì tất cả các mã thực thi trong hệ nhị phân thường được chứa trong một section duy nhất. Nếu chúng ta thấy một lệnh jump / call đến một địa chỉ mà:
1. Không có trong section hiện tại
2. Không có trong không gian địa chỉ của một thư viện đã tải.
thì khả năng cao bước nhảy này là bước giải nén.
Nhận diện với file được mã hóa bằng UPX
Các bước giúp chúng ta nhận diện và giải nén file bị mã hóa bằng UPX:
- Kiểm tra PE header và xác định xem chúng có thực sự bị đóng gói không. Tuy nhiên bước xác định này chúng ta có thể dùng peid.
- Tìm nơi giải nén sẽ được ghi.
- Tìm kiếm lệnh jmp hoặc call tham chiếu đến một thanh ghi hoặc không gian bộ nhớ nơi lưu dữ liệu giải nén có thể được ghi vào trong mã gốc.
- Đặt breakpoint ở những lệnh này.
- Dump data chứa trong không gian bộ nhớ đó, sửa import resolutions và xuất nó dưới dạng tệp thực thi mới.
Áp dụng.
Ta áp dụng với bài garbage của flareon-2020.