[RE026] A Deep Dive into Zloader - the Silent Night



1. Giới thiệu

Zloader, một banking trojan còn biết đến với những tên gọi khác như Terdot hay Zbot. Dòng trojan này được phát hiện lần đầu tiên vào năm 2016, và theo thời gian số lượng phát tán của nó liên tục gia tăng. Code của Zloader được cho là xây dựng dựa trên mã nguồn bị rò rỉ của mã độc ZeuS nổi tiếng. Vào năm 2011, khi mã nguồn của ZeuS được công khai thì từ đó tới nay nó được sử dụng trong nhiều mẫu mã độc khác nhau. 

Zloader có đầy đủ chức năng tiêu chuẩn của một trojan như có thể lấy thông tin từ các trình duyệt, thu thập cookie và mật khẩu, chụp ảnh màn hình… đồng thời để gây khó khăn cho các nhà phân tích, nó áp dụng các kĩ thuật cao cấp, bao gồm code obfuscation và mã hóa chuỗi, che dấu các hàm APIs. Gần đây, các chuyên gia của CheckPoint đã công bố phân tích về một chiến dịch phát tán Zloader theo đó quá trình lây nhiễm đã khai thác quy trình kiểm tra chữ kí số của Microsoft. Bên cạnh đó, Zloader còn được dùng để phát tán các loại mã độc khác bao gồm cả ransomware như Ryuk và Egregor. Điều này cho thấy, những kẻ đứng sau mã độc này vẫn đang tìm các phương thức khác nhau để nâng cấp nhằm vượt qua các biện pháp phòng vệ. Dưới đây là bảng xếp hạng của Zloader theo đánh giá từ trang AnyRun:

Nguồn: https://any.run/malware-trends/zloader

Mới đây nhất, nhiều đơn vị cung cấp dịch vụ viễn thông và công ty an ninh mạng trên toàn thế giới, bao gồm ESET, Black Lotus Labs, Đơn vị 42 của Palo Alto Networks và Avast đã hợp tác với các chuyên gia nghiên cứu bảo mật của Microsoft trong suốt nỗ lực điều tra, đã thực hiện các bước pháp lý và kỹ thuật để phá vỡ mạng botnet ZLoader, chiếm quyền kiểm soát 65 tên miền được sử dụng để kiểm soát và giao tiếp với các máy chủ bị lây nhiễm.

Bài viết này sẽ cung cấp chi tiết quá trình phân tích và kĩ thuật mà Zloader sử dụng, bao gồm:

  • Cách thức unpack để dump Zloader Core Dll.
  • Cách thức mà Zloader gây khó khăn cũng như làm mất thời gian trong quá trình phân tích.
  • Giải mã các chuỗi được sử dụng bởi Zloader bằng cả hai phương pháp IDAPython và AppCall.
  • Áp dụng AppCall để khôi phục lại các hàm APIs mà Zloader sử dụng.
  • Kĩ thuật Process Injection mà Zloader áp dụng để inject vào tiến trình msiexec.exe.
  • Giải mã thông tin cấu hình liên quan tới các địa chỉ C2s.
  • Cách thức Zloader thu thập và lưu thông tin tại Registry.
  • Kĩ thuật tạo Persistence.

Sample được  sử dụng trong bài viết: 034f61d86de99210eb32a2dca27a3ad883f54750c46cdec4fcc53050b2f716eb


2. Unpacking Zloader Core Dll

Trước tiên, kiểm tra sample bằng Nauz File Detector:

Kết hợp các thông tin về sections từ ExeInfo, thông tin Entropy bằng DiE cũng như kích thước của file DLL thì có thể khẳng định DLL này bị packed:

Để unpack, sử dụng x64dbg load file Dll, đặt bp NtAllocateVirtualMemory. Sau đó, sửa lại điều kiện của breakpoint như sau:

Thực thi bằng F9 và đợi cho tới khi hit breakpoint thỏa điều kiện đã thiết lập (sau khoảng 1126120 hits):

Follow theo các vùng nhớ đã được cấp phát, sau lần hit bp thứ 3, core Dll của Zloader sẽ được unpack:

Dump Dll này ra disk, file có MD5: 9b5589fcd123a3533584a62956f2231b.


3. Anti-analysis

Để làm tốn thời gian của người phân tích, Zloader sử dụng các hàm vô nghĩa, hoặc viết lại các hàm nhìn rất rắc rối nhưng chỉ để thực hiện các tác vụ đơn giản như AND, OR, XOR, ADD, SUB, …

Ví dụ, một hàm thực hiện tác vụ vô nghĩa:

Ví dụ, hàm thực hiện tác vụ AND, OR:


4. Decrypt wide string

4.1. Sử dụng IDAPython

Các strings mà core Dll sử dụng đều đã bị mã hóa. Hàm giải mã wide string sẽ nhận hai tham số truyền vào:

  • Tham số thứ nhất: địa chỉ chứa chuỗi bị mã hóa.
  • Tham số thứ hai: địa chỉ lưu chuỗi sau giải mã.

Mã giả tại hàm giải mã f_zl_decrypt_wstring nhìn có vẻ khả rối, tuy nhiên nếu phân tích kĩ hàm này thực hiện vòng lặp xor đơn giản với khóa giải mã là "PgtrIPF-2ftOj00Ox":

Dựa vào mã giả trên, viết lại đoạn code thực hiện giải mã bằng python như sau:

Với sự hỗ trợ của IDAPython, ta có thể tự động hóa toàn bộ quá trình giải mã string và thêm các chú thích tại các hàm giải mã trong IDA để phục vụ cho việc phân tích. Toàn bộ code python như sau:

Kết quả trước và sau khi thực hiện script sẽ giúp công việc phân tích dễ dàng hơn:


4.2. Sử dụng IDA AppCall

Nếu không có thời gian để đào sâu vào quá trình thực hiện giải mã của hàm hoặc khi thuật toán thực hiện quá phức tạp, ta có thể sử dụng tính năng hữu ích của IDA là AppCall để giúp giải mã dữ liệu. Về cơ bản, Appcall là một cơ chế được sử dụng để thực thi các hàm bên trong chương trình đang được debug bởi trình debugger của IDA. Trước khi áp dụng AppCall, việc đầu tiên là cần định nghĩa chính xác prototype của hàm. Ví dụ, hàm f_zl_decrypt_wstring có protoype như sau:

wchar_t *__cdecl f_zl_decrypt_wstring(wchar_t *encString, wchar_t *decString);

Lưu ý một lần nữa là để sử dụng được AppCall thì phải thực hiện debug. Như hình dưới đây, IDA đang dừng ở breakpoint đã đặt DllEntryPoint:

Sau đó, thực thi python script dưới đây để giải mã và thêm chú thích các chuỗi đã giải mã được tại các hàm:

Kết quả cuối cùng sẽ tương tự như hình dưới đây:


5. Decrypt ansi string

5.1. Sử dụng IDAPython

Bên cạnh hàm giải mã các wide string, Zloader cũng sử dụng hàm giải mã các ansi string. Hàm này cũng nhận hai tham số truyền vào:

  • Tham số thứ nhất: địa chỉ chứa chuỗi bị mã hóa.
  • Tham số thứ hai: địa chỉ lưu chuỗi sau giải mã.

Cũng tương tự như hàm f_zl_decrypt_wstring, mã giả của hàm f_zl_decrypt_string nhìn cũng khá rối, tuy nhiên nó vẫn sử dụng vòng lặp xor để giải mã với khóa giải mã vẫn là "PgtrIPF-2ftOj00Ox":

Dưới đây là toàn bộ code python để tự động hóa toàn bộ quá trình giải mã các string và thêm các chú thích tại các hàm:

Kết quả trước và sau khi thực hiện script:


5.2. Sử dụng IDA AppCall

Để sử dụng AppCall, tương tự như trên, cần định nghĩa lại chính xác prototype cho hàm f_zl_decrypt_string như sau: char *__cdecl f_zl_decrypt_string(char *encString, char *decString);

Sửa lại một chút script đã sử dụng cho việc giải mã các wide string ở trên:

Kết quả sau khi chạy script:


6. Danh sách các Dlls được Zloader sử dụng

Trong danh sách các string được giải mã bởi hàm f_zl_decrypt_string ở trên, có một string sau giải mã khá vô nghĩa. Đi tới địa chỉ này, sau khi phân tích tôi nhận thấy tham số đầu tiên được truyền vào cho hàm là mảng chứa địa chỉ của các chuỗi bị mã hóa. Dựa vào giá trị index tương ứng của mảng sẽ truy cập tới địa chỉ chứa chuỗi bị mã hóa tương ứng:

Đi tới mảng g_ptr_enc_dll_str (đã đổi tên ở trên) sẽ thấy được danh sách các địa chỉ như hình dưới đây:

Sửa lại script để giải mã các riêng cho các chuỗi Dll, kết quả có được khi thực hiện script như sau:

Tổng kết lại, ta có bảng danh sách index tương ứng với các Dll mà có thể Zloader sẽ sử dụng để từ đó phục vụ việc lấy địa chỉ của các hàm APIs:

Index

Dll Name

0

kernel32.dll

1

user32.dll

2

ntdll.dll

3

shlwapi.dll

4

iphlpapi.dll

5

urlmon.dll

6

ws2_32.dll

7

crypt32.dll

8

shell32.dll

9

advapi32.dll

10

gdiplus.dll

11

gdi32.dll

12

ole32.dll

13

psapi.dll

14

cabinet.dll

15

imagehlp.dll

16

netapi32.dll

17

wtsapi32.dll

18

mpr.dll

19

wininet.dll

20

userenv.dll

21

bcrypt.dll

7. Dynamic APIs resolve

Tương tự như các dòng mã độc cao cấp khác… Zloader cũng sẽ lấy địa chỉ các hàm API(s) thông qua việc tìm kiếm theo giá trị hash được tính toán trước dựa vào tên hàm API.

Như trên hình, hàm f_zl_resolve_api_func_ex nhận hai tham số truyền vào:

  • (1): Tham số thứ nhất là dll_index. Dựa vào tham số này, hàm sẽ thực hiện giải mã ra tên của của Dll tương ứng, từ đó gọi hàm LoadLibraryA để lấy địa chỉ base của Dll này.

  • (2): Tham số thứ hai là pre_api_hash. Tham số này là hash được tính toán trước theo tên hàm API. Hàm f_zl_resolve_api_func_ex sẽ gọi tới f_zl_resolve_api_func để tìm kiếm địa chỉ API tương ứng:

Mã giả tại hàm f_zl_resolve_api_func thực hiện tìm kiếm địa chỉ hàm API như sau:

Toàn bộ mã giả của hàm thực hiện tính toán hash theo tên hàm API như sau:

Dựa vào mã giả trên, viết lại bằng Python code dưới đây:

Kết quả khi sử dụng hàm trên để tìm các hàm API tương ứng với các giá trị hash 0xFDA8B77, 0xB1C1FE3, 0x8ADF2D1:

Với những gì đã phân tích ở trên, hoàn toàn có thể viết IDAPython script để khôi phục lại toàn bộ các hàm APIs mà Zloader sử dụng. Tuy nhiên, để tránh việc phải đào sâu vào thuật toán tính toán hash của Zloader cho mỗi lần phân tích, ở đây tôi sẽ sử dụng AppCall thực hiện nhiệm vụ này. Code python sử dụng AppCall như sau:

Lưu ý, Zloader có nhiều vùng code gọi tới hàm f_zl_resolve_api_func_ex, tuy nhiên sẽ có những vùng code mà không có bất kì tham chiếu nào tới nó cũng như vùng code đó chưa được tạo thành hàm hoàn chỉnh. Do đó, để có thể chạy được script trên, trước tiên cần phải thực hiện tạo hàm cho những đó. Kết quả sau khi thực thi script sẽ như sau:

Tuy nhiên, như trên hình vẫn còn những vị trí không khôi phục lại được hàm API, đó là bởi vì Zloader đã thực hiện việc tính toán giá trị các giá trị dll_index pre_api_hash trước đó và lưu vào thanh ghi. Sau đó mới thực hiện gọi hàm f_zl_resolve_api_func_ex:


8. Process Injection

Zloader khi thực thi sẽ tiến hành inject Core Dll vào tiến trình msiexec.exe. Toàn bộ quá trình thực hiện như sau:

  • Sử dụng hàm API CreateProcessA để tạo tiến trình msiexec.exe ở trạng thái SUSPENDED.

  • Lấy thông tin SizeOfImage của Zloader Dll đang load bởi rundll32.exe/regsvr32.exe. Sử dụng hàm API VirtualAllocEx để cấp phát vùng nhớ mới bên trong tiến trình msiexec.exe:

  • Cấp phát vùng nhớ heap, thực hiện copy toàn bộ nội dung của Dll vào vùng nhớ heap này:

  • Tạo một số ngẫu nhiên và sử dụng nó vào quá trình mã hóa toàn bộ payload đã lưu tại vùng nhớ heap:

  • Sử dụng hàm API WriteProcessMemory để ghi toàn bộ encrypted payload từ vùng nhớ heap vào vùng nhớ đã cấp phát trước đó trong tiến trình msiexec.exe:

  • Tiếp tục sử dụng hàm API VirtualAllocEx để cấp phát vùng nhớ thứ 2 có kích thước 66 bytes trong tiến trình msiexec.exe. Vùng nhớ này sẽ được sử dụng để làm nhiệm vụ giải mã toàn bộ Dll đã mã hóa ở trên. Thực hiện cập nhật lại cấu trúc STARTUPINFO đã tạo bởi hàm CreateProcessA trước đó, dữ liệu tại đây là những lệnh sẽ được dùng để giải mã encrypted Dll. Sau đó, gọi hàm WriteProcessMemory để ghi nội dung đã cập nhật của STARTUPINFO vào vùng nhớ vừa tạo.

  • Cuối cùng, sử dụng các hàm API GetThreadContext, SetThreadContext, ResumeThread hoặc CreateRemoteThread để thực thi tiến trình msiexec.exe. Lúc này đỉa chỉ entry point thực thi tại msiexec.exe sẽ chính là vùng nhớ chứa đoạn code thực hiện giải mã ở trên:

  • Sau khi thực hiện decrypt lại toàn bộ Zloader Dll, sẽ nhảy tới địa chỉ RVA là 0xF270 (File offset: 0xE670) để thực thi các tác vụ chính của mã độc:


9. Giải mã cấu hình

Thông tin cấu hình của Zloader đã bị mã hóa và lưu tại section .rdata. Hàm giải mã nhận hai tham số truyền vào là cấu hình đã mã hóa và key dùng để giải mã:

Bên trong hàm f_zl_decrypt_config sẽ sử dụng thuật toán RC4 để giải mã dữ liệu:

Với toàn bộ thông tin đã phân tích được, viết lại bằng đoạn code IDAPython dưới đây để thực hiện giải mã:

Kết quả thu được sau khi thực hiện script như sau:


10. Lưu thông tin thu thập và cấu hình tại Registry

Khi lần đầu thực thi, Zloader sẽ thực hiện thu thập thông tin về victim gồm volume_GUID, Computer_Name, Windows version, Install Date, tạo các thư mục ngẫu nhiên tại %APPDATA%, tạo registry key ngẫu nhiên tại nhánh HKEY_CURRENT_USER\Software\Microsoft, sau đó mã hóa toàn bộ thông tin liên quan rồi lưu vào registry đã tạo:

Thông tin lưu tại Registry tương tự như sau:

Để giải mã data lưu tại Registry trên, sử dụng embedded RC4 key đã giải mã được ở trên. Với sự hỗ trợ của CyberChef ta có thể dễ dàng thực hiện việc giải mã ra dữ liệu tương tư như hình dưới đây:


11. Tạo persistence key

Zloader thực hiện đọc toàn bộ nội dung của core Dll từ disk vào vùng nhớ, sau đó thực hiện ghi vào một random dll trong một thư mục đã tạo ở trên tại %APPDATA%:

Thực hiện tạo persistence key tại HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run:


12. Tham khảo


Tran Trung Kien (aka m4n0w4r) 

Malware Analysis Expert

 

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




No comments:

Post a Comment