[RE011] Unpack crypter của malware Netwire bằng x64dbg


Gần đây, chúng tôi có tiếp cận một mẫu malware lạ. Kết quả trả về của sandbox cho thấy đây là một biến thể của dòng malware Netwire được pack bằng một loại crypter viết bằng VB6. Chúng tôi đã tiến hành phân tích mẫu này nhằm hiểu sâu về kỹ thuật của malware, đồng thời xây dựng thành tài liệu tham khảo cho những mẫu malware sử dụng kĩ thuật tương tự.

1. Công cụ sử dụng

Danh sách các công cụ được sử dụng trong bài viết:

2. Thông tin cơ bản về sample


File Hash (SHA-256): d381c5a5eeb46759bb5ebce67eb50cc61f91a75c204d6ec1c7750937f7f4c3f1
Nguồn: Any.run 


Phân tích sơ bộ bằng các chương trình PE Scanner cho thấy mã độc được viết bằng Visual Basic 6.0
Hình 1. Kết quả scan bằng Exeinfo

3. Phân tích crypter

Một mẹo khi dịch ngược chương trình được biên dịch từ VB6 là sẽ có một loạt các lệnh jump nhảy đến các function được viết trong chương trình. Như trong trường hợp này, các lệnh jump nằm ở địa chỉ 0x40DF3C. Có thể tìm các lệnh jump này bằng mẫu “81 6C 24 04 ?? ?? ?? ?? E9

Hình 2. Các lệnh jump đến function của VB6
Có thể thấy mã độc chỉ có hai function chính. Chọn function tại địa chỉ 0x40E9DE để phân tích vì function này có kích thước lớn nhất. Tiếp tục trace dần sẽ tới một hàm call như sau:

Hình 3. Lệnh call đến shellcode
Với những ai đã quen với việc dịch ngược các chương trình được viết bằng VB6 sẽ nhận thấy điểm bất thường khi trong code của chương trình VB6 gọi thẳng đến một thanh ghi. Tiếp tục debug vào lệnh call sẽ tới đoạn shellcode:

Hình 4. Đoạn code cấp phát memory và chạy shellcode
Toàn bộ đoạn shellcode này đã được obfuscated. Tuy nhiên, kích thước của nó cũng khá nhỏ nên nếu trace dần từng bước và tổng hợp thông tin, chúng ta sẽ thấy shellcode thực hiện những bước sau:
·        Gọi hàm VirtualAlloc để cấp phát bộ nhớ.
·        Giải mã shellcode và copy vào vùng bộ nhớ vừa được cấp phát.
·        Tiến hành gọi shellcode mới.

Tới đây, có thể dùng x64dbg để dump toàn bộ shellcode mới và tiến hành phân tích. Để dump shellcode, lựa chọn đoạn cần dump trong cửa sổ Hex, chuột phải và chọn Binary > Save To a File:
Hình 5. Dump shellcode với x64dbg

Với shellcode đã dump, có nhiều công cụ hỗ trợ để phân tích. Trong bài này, chúng tôi sử dụng jmp2it để load shellcode lên và debug bằng x64dbg. Chạy jmp2it với command như sau:


Jmp2it.exe shellcode.bin 0x0 pause

Tiến hành attach x64dbg vào process jmp2it.exe để debug shellcode. Shellcode lúc này được load ở địa chỉ 0x30000.


Hình 6. Shellcode thực hiện tác vụ độc hại
Như trên hình, shellcode mới này cũng bị obfuscate. Lúc này, có hai lựa chọn:
·        Trace từng lệnh để debug.
·        Viết tool/script để deobfuscate đoạn shellcode.

Vì shellcode là khá lớn nên ở đây chúng tôi lựa chọn phương án viết script để thực hiện deobfuscate đoạn shellcode này.


3.1. Deobfuscate shellcode


Để viết được script thực hiện deobfuscate đoạn shellcode, chúng ta cần hiểu một số pattern của đoạn shellcode. Ví dụ như sau:
·        Pattern Push Reg/Pop Reg
·        Pattern Inc Reg/Dec Reg
·        Pattern Add Reg, Const/ Sub Reg, Const
·        Pattern Push Reg/Xor Reg, Const/ Pop Reg
·        Pattern CLD, CLC
·       

Các pattern trên tương ứng với lệnh NOP. Sau khi xác định được các pattern này, dùng python để viết script deobfuscate. Chi tiết script xem tại đây. Để sử dụng, lựa chọn đoạn mã cần deobfuscate và chạy script.
Hình 7. Trước và sau khi deobfuscated
*Lưu ý: script trên tạm thời bỏ qua tìm kiếm pattern 2 bytes vì pattern ngắn, không có hiệu quả nhiều trong việc obfuscate và rất dễ nhầm lẫn khi deobfuscate bằng cách search/replace byte pattern. Để xác định pattern 2 bytes một cách chính xác thì nên dùng pattern bằng asm. Hiện tại x64dbg chưa support tốt các pattern bằng asm.

3.2. Heaven’s gate


Shellcode áp dụng kỹ thuật heaven’s gate[1] để làm rối trong quá trình debug. Đây là kỹ thuật thực thi mã từ x86 sang x64 bằng lệnh far jmp. Bằng cách đơn giản check ở địa chỉ FS:[0xC0] để xem hệ thống có phải là x64 hay không? Nếu là x64, shellcode dùng kỹ thuật heaven’s call. Để debug tiếp được trơn tru trên x86, chúng tôi tiến hành patch lệnh nhảy sau lệnh so sánh để "lừa" shellcode thực thi trên x86.
Hình 8. Đoạn code thực thi Heaven’s gate

3.3. Resolve API


Có thể nói GetProcAddress là một API quan trọng để shellcode có thể tìm và gọi các hàm API khác. Để tìm địa chỉ của hàm GetProcAddress cần có địa chỉ của kernel32.dll. Shellcode ở trên sẽ tiến hành truy xuất tới PEB->Ldr->InMemoryOrderModuleList và lấy địa chỉ của module tương ứng với tên kernel32.dll.
Hình 9. Truy cập vào PEB lấy base tương ứng

Sau khi có địa chỉ của kernel32.dll, shellcode tiếp tục tìm địa chỉ của API bằng API hash. Ở đây hash được sử dụng là DJB hash và giá trị hash của hàm “GetProcAddress” là 0xCF31BB1F[2].
Hình 10. Đoạn code hash tên API và tiến hành so sánh

Dựa vào địa chỉ của kernel32.dll và API GetProcAddress, shellcode tiến hành resolve một loạt các API sau:
·        LoadLibraryA
·        TerminateProcess
·        EnumWindows
·        ZwProtectVirtualMemory
·        DbgBreakPoint
·        DbgUIRemoteBreakin


3.4. Anti attach


Shellcode gọi hàm ZwProtectVirtualMemory để đặt quyền PAGE_EXECUTE_READWRITE cho section .text của ntdll, sau đó tiến hành patch các hàm API DbgBreakPoint DbgUIRemoteBreakin để anti attach.
Hình 11. Code patch hàm DbgBreakPoint thành NOP
Hình 12. Code patch hàm DbgUIRemoteBreakin
Hình 13. Trước và sau khi patch hàm DbgUIRemoteBreakin

3.5. Restore hook/breakpoint tại các hàm Zw*/Nt*


Shellcode thực hiện scan pattern “B9 ?? ?? ?? ?? 8D 54 24 04” và “33 C9 8D 54 24 04” trong code của thư viện ntdll. Đây là pattern nằm trong các hàm Zw*/Nt* gọi đến system call. Sau khi tìm thấy pattern này, shellcode sẽ khôi phục lại 5 bytes đầu tiên của các hàm:
Hình 14. Đoạn code search pattern
Hình 15. Đoạn code khôi phục lại 5 byte đầu tiên của API

Một điểm hay cần học ở kỹ thuật này là các hàm Zw*/Nt* có các system call number tăng dần theo luồng từ trên xuống dưới. Nhờ đặc điểm này, shellcode có thể khôi phục lại lệnh “mov eax, system call number” một cách chính xác:
Hình 16. Pattern được khoanh màu đỏ và system call number được khoanh màu xanh

Bug: Nếu hàm API với system call number là 0 hoặc dòng lệnh “lea edx, dword ptr ss:[esp+4]” bị thay đổi (hook, breakpoint,…), shellcode sẽ hủy toàn bộ system call number của ntdll.

3.6. Set hidden thread


Shellcode gọi hàm ZwSetInformationThread với tham số 0x11 để hide thread[3] trước debugger:
Hình 17. Đoạn mã shellcode set hidden thread

3.7. Kiểm tra breakpoint


Shellcode sử dụng một hàm check trước khi gọi API. Nội dung hàm check như sau:
·        Gọi NtGetThreadContext để check hardware breakpoint[3].
·        Kiểm tra opcode 0xCC (int 3) để phát hiện software breakpoint[3].


Hình 18. Code phát hiện hardware breakpoint
Hình 19. Code phát hiện software breakpoint

3.8. Create process và code injection


Cuối cùng shellcode thực hiện code injection bằng cách:
·        Gọi CreateProcessInternalW với flag là CREATE_SUSPENDED.
·        Gọi ZwUnmapViewOfSection với base là 0x400000.
·        Sử dụng ZwCreateSection/NtMapViewOfSection để cấp phát bộ nhớ.
·    Nếu ZwCreateSection/NtMapViewOfSection bị lỗi, chuyển sang dùng API ZwAllocVirtualMemory để cấp phát bộ nhớ.
·        Gọi NtWriteVirtualMemory để inject shellcode mới vào bộ nhớ.
· Cuối cùng gọi hàm NtSetThreadContext/NtSetThreadContext/ NtResumeThread để chạy shellcode và TerminateProcess để exit.

3.9. Phân tích shellcode thứ hai


Đoạn shellcode thứ hai thực chất là đoạn shellcode thứ nhất với config khác. Thay vì inject shellcode như đoạn shellcode thứ nhất, mục đích của đoạn shellcode này là tải về payload, decrypt và thực thi. Công việc chính nó thực hiện bao gồm:
·   Sử dụng hàm của thư viện wininet.dll để download payload từ địa chỉ https://drive[.]google[.]com/uc?export=download&id=1zEuX2HZcVvTYp7wzGtD1IXOSVLTBWVUe
·    Giải mã payload và map vào memory để thực thi. Payload này có kích thước 0x1AA40, 40 bytes đầu tiên là phần header của payload.


Hình 20. Payload được decrypt trong memory
Dump toàn bộ với size là 0x1AA00 sẽ thu được payload cuối cùng là Netwire.


3.10. Bonus


Khi load payload Netwire đã dump bằng x64dbg và đặt breakpoint tại 0x409E8A, sẽ thu được config đã decrypted:
Hình 21. Config được decrypt trong memory

4. Tài liệu tham khảo


Các nguồn tham khảo được sử dụng làm tư liệu cho bài viết:
2.      ASMRunPE

----------------------------------
Để tiện theo dõi, chúng tôi cung cấp bài phân tích dưới dạng PDF:

File Name: CSS-RD-ADV-200304-011_Unpack crypter của malware Netwire bằng x64dbg.pdf
File Hash (SHA-256): 717efd6b8dd9a8a40ee34386311ab0f5689eb1f5f8fbd6df30b9cfdd8abe02c0



Dang Dinh Phuong  
R&D Center - VinCSS (a member of Vingroup) 




1 comment: