Malware

Frida 103: Cobalt Strike Beacon Extraction

category
Malware
date
May 24, 2021
slug
frida-103-cobalt-strike-beacon-extraction
author
status
Public
tags
frida
malware
summary
Sử dụng Frida để unpacking windows malware
type
Post
thumbnail
updatedAt
Mar 1, 2023 08:52 AM

1. Giới thiệu

Trong phân tích Malware thì phương pháp phân tích động được ứng dụng rất nhiều để kiểm tra một chương trình trong quá trình nó thực thi. Thông thường chúng ta nghĩ ngay đến kỹ thuật Debugging, sử dụng một số debugger như là: OllyDbg, x64dbg, WinDbg, GDB, EDB,.v.v.. để tiến hành phân tích. Một cách tiếp cận khác với phương pháp phân tích động đó là sử dụng các Dynamic Binary Instrumentation Frameworks cho phép chúng ta chèn và thực thi các Instrumentation Code (Hook Code) bên trong tiến trình cần phân tích.
Có nhiều Framework như vậy, nổi bật có: Pin, DynamoRIOFrida. Chúng cho phép hook vào các APIs để quan sát, sửa đổi hay đánh giá đầu vào, đầu ra của chúng. Ưu điểm của các Framework này là tính linh động, hỗ trợ đa nền tảng (Windows, macOS, Linux, Android, iOS,.v.v..). Bài này tôi sẽ sử dụng Frida Framework để tự động hóa một phần trong quá trình Phân tích Malware.

2. Bắt đầu với Frida

Mục tiêu phần này là biết được cách Frida có thể quan sát được các APIs mà Cobalt Strike malware sử dụng. Mẫu được sử dụng có sẵn trên VirusTotal:
$ sha256sum sample1.exe fe5585dfda44ca136bb2fb383052d03452f34c371a2349be0d0cbb6b07437865 *sample1.exe $ file sample1.exe sample1.exe: PE32+ executable (GUI) x86-64 (stripped to external PDB), for MS Windows
Chúng ta bắt đầu với các APIs liên quan đến thao tác với tệp tin. Qua kiểm tra với CFF Explorer, biết được mẫu có import hàm CreateFileA(), WriteFile(), ReadFile() trong thư viện KERNEL32.dll. Theo tài liệu của Microsoft thì các hàm này được định nghĩa như sau:
Hàm CreateFileA
HANDLE CreateFileA( LPCSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile );
Hàm WriteFile
BOOL WriteFile( HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite, LPDWORD lpNumberOfBytesWritten, LPOVERLAPPED lpOverlapped );
Hàm ReadFile
BOOL ReadFile( HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead, LPDWORD lpNumberOfBytesRead, LPOVERLAPPED lpOverlapped );
Các hàm WriteFile(), ReadFile() chỉ sử dụng được sau khi mà đã có được HANDLE của file. Handle này có được khi gọi hàm CreateFileA() và tham số lpFileName trong hàm này chỉ ra tên của tệp. Như vậy để biết được chương trình đang có các hành động thao tác với tệp tin nào thì ta cần hook vào hàm CreateFileA().
var hookCreateFileA = Module.getExportByName(null, "CreateFileA"); Interceptor.attach(hookCreateFileA, { onEnter: function(args) { console.log("\nCreateFileA at: " + hookCreateFileA); console.log(" Name of the File or Device: " + args[0].readAnsiString()); } });
$ frida -l .\hooking\hookCreateFileA.js .\sample1.exe --no-pause ... Spawned `.\sample1.exe`. Resuming main thread! [Local::sample1.exe]-> CreateFileA at: 0x7ffb08631d20 Name of the File or Device: \\.\pipe\MSSE-9610-server
Đầu ra này cho chúng ta biết được Malware cài đặt một PIPE (đường ống) để giao tiếp giữa các tiến trình. Định dạng này thường gặp khi phân tích các mẫu Cobalt Strike. Ví dụ: Link1, Link2. Thông thường các mẫu Cobalt Strike được phát hiện chỉ là một Loader, nó sẽ tiến hành tải Beacon Payload từ máy chủ điều khiển hoặc cũng có thể Beacon Payload được Obfuscate và nhúng ngay trong chính tệp thực thi Loader.

3. Cobalt Strike Beacon Extraction

Phân tích các mẫu Cobalt Strike thường sẽ phải trải qua nhiều giai đoạn deobfuscate để có được payload cuối cùng, có thể là một executable hoặc là shellcode và các payload cuối này thường sẽ được thực thi trên bộ nhớ tiến trình chứ không được ghi ra tệp trên ổ cứng. Quá trình này có thể được mô tả ngắn gọn như sau:
  1. Cấp phát vùng nhớ
  1. Tiến hành giải mã Payload
  1. Sao chép Payload đã giải mã vào vùng nhớ mới cấp phát
  1. Chuyển luồng thực thi đến vùng nhớ chứa Payload độc.
Để có thể hiểu và nắm được quá trình giải mã dữ liệu sau cùng là có được Beacon Payload đòi hỏi người phân tích phải có kiến thức, kỹ năng về Phân tích tĩnh mã Assembly và Debugging nhất định. Khi nắm được cơ chế giải mã của Malware, chúng ta có thể tự động hóa quy trình này với Frida.
Hệ điều hành Windows hỗ trợ nhiều APIs sử dụng cho việc cấp phát và thao tác với các vùng nhớ. Ở đây tôi sẽ tập chung vào VirtualAlloc()VirtualProtect(). Hàm VirtualAlloc được dùng để cấp phát bộ nhớ trong một tiến trình còn hàm VirtualProtect dùng để thay đổi thuộc tính vùng nhớ như là các quyền: đọc, ghi, thực thi. Mô tả của 2 hàm này như sau:
Hàm VirtualAlloc
LPVOID VirtualAlloc( LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect );
Trong đó:
  • lpAddress: Địa chỉ vùng nhớ sẽ cấp phát. Nếu là NULL thì hệ thống tự động xác định địa chỉ này.
  • dwSize: Kích thước vùng nhớ sẽ cấp phát được tính bằng byte.
  • flAllocationType: Loại bộ nhớ sẽ cấp phát. Ví dụ: MEM_COMMIT, MEM_RESERVE, MEM_RESET,.v.v.. Xem thêm tại: Link
  • flProtect: Thuộc tính bảo vệ vùng nhớ sẽ được cấp phát, được định nghĩa bằng các hằng số. Xem thêm tại: Link
Hàm VirtualProtect
BOOL VirtualProtect( LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect, PDWORD lpflOldProtect );
Trong đó:
  • lpAddress: Địa chỉ vùng nhớ cần thay đổi thuộc tính
  • dwSize: Kích thước vùng nhớ cần thay đổi, được tính bằng byte
  • flNewProtect: Thuộc tính mới sẽ áp dụng, là các hằng số được định nghĩa tại: Link
  • lpflOldProtect: Một con trỏ trỏ đến giá trị thuộc tính cũ trước đó.
Nếu phân tích bằng một trình debugger như Ollydbg hay x64dbg, chúng ta có thể đặt các breakpoint tại các hàm VirtualAllocVirtualProtect để giám sát sự thay đổi. Trong phần này chúng ta sẽ không làm vậy, thay vào đó tôi sử dụng Frida để làm việc này. Đầu tiên hãy thử nghiệm monitor hai hàm này:
$ frida-trace -i "VirtualAlloc" -i "VirtualProtect" sample1.exe Instrumenting... ... Started tracing 4 functions. Press Ctrl+C to stop. /* TID 0x3e0 */ 1812 ms VirtualAlloc() 1874 ms | VirtualAlloc() 1874 ms VirtualProtect() 1874 ms | VirtualProtect() /* TID 0x88c */ 1874 ms VirtualAlloc() 1874 ms | VirtualAlloc()
Frida-Trace tự động sinh ra các handler cho mỗi API, với mỗi API sẽ có một tệp JavaScript. Tiến hành mở các tệp này và sửa nội dung bên trong như sau:
Đối với tệp VirtualAlloc.js
{ onEnter(log, args, state) { log('[*] VirtualAlloc Hooked!'); log(" Size (bytes): " + args[1].toInt32()); log(" Protect: " + args[3]); }, onLeave(log, retval, state) { log(" VirtualAlloc Returned: " + retval); } }
Đối với tệp VirtualProtect.js
{ onEnter(log, args, state) { log('[*] VirtualProtect Hooked!'); log(" Address: " + args[0]); log(" Size: " + args[1].toInt32()); log(" NewProtect: " + args[2]); }, onLeave(log, retval, state) { } }
Chạy lại và kiểm tra kết quả
$ frida-trace -i "VirtualAlloc" -i "VirtualProtect" sample1.exe Instrumenting... ... Started tracing 4 functions. Press Ctrl+C to stop. /* TID 0xc90 */ 1157 ms [*] VirtualAlloc Hooked! 1157 ms Size (bytes): 260608 1157 ms Protect: 0x4 1157 ms | [*] VirtualAlloc Hooked! 1157 ms | Size (bytes): 260608 1157 ms | Protect: 0x4 1157 ms | VirtualAlloc Returned: 0x3590000 1157 ms VirtualAlloc Returned: 0x3590000 1157 ms [*] VirtualProtect Hooked! 1157 ms Address: 0x3590000 1157 ms Size: 260608 1157 ms NewProtect: 0x20 1157 ms | [*] VirtualProtect Hooked! 1157 ms | Address: 0x3590000 1157 ms | Size: 260608 1157 ms | NewProtect: 0x20 /* TID 0xc6c */ 1282 ms [*] VirtualAlloc Hooked! 1282 ms Size (bytes): 311296 1282 ms Protect: 0x40 1282 ms | [*] VirtualAlloc Hooked! 1282 ms | Size (bytes): 311296 1282 ms | Protect: 0x40 1282 ms | VirtualAlloc Returned: 0x3290000 1282 ms VirtualAlloc Returned: 0x3290000
Nhìn vào kết quả trên có thể thấy được chương trình cấp phát thành công 260608 bytes tại 0x3590000, vùng nhớ này ban đầu được khởi tạo thuộc tính với hằng số 0x4 (PAGE_READWRITE) sau đó được thay đổi thành 0x20 (PAGE_EXECUTE_READ). Vùng nhớ thứ 2 có kích thước 311296 bytes được cấp phát tại 0x3290000 và được khởi tạo với thuộc tính 0x40 (PAGE_EXECUTE_READWRITE). Có một điều dễ nhận ra là đặc điểm chung của các vùng nhớ này khi được cấp phát hay sau khi được thay đổi thuộc tính vùng nhớ thì đều có quyền EXECUTE, đây là một quyền có thể nói là "nhạy cảm". Thông thường một chương trình khi hoạt động, nó xin cấp phát vùng nhớ với các quyền như READ, WRITE là rất bình thường, nhưng với quyền EXECUTE thì cần phải xem xét vì rất có thể là Malware, các vùng nhớ này sau khi được cấp với quyền EXECUTE rất có thể được dùng để ghi một Executable hay Shellcode lên đó và cuối cùng là chuyển luồng thực thi đến vùng nhớ đã ghi Shellcode hoặc Executable. Dựa vào đặc điểm này chúng ta có thể sử dụng Frida để dump vùng nhớ này ra disk, phục vụ mục đích kiểm tra, phân tích sâu hơn ở các giai đoạn sau đó. Trước tiên, chúng ta thử kiểm tra các vùng nhớ này bằng cách sửa mã JavaScript trong các handle file như sau:
Tệp VirtualProtect.js:
{ onEnter(log, args, state) { log('[*] VirtualProtect Hooked!'); log(" Address: " + args[0]); log(" Size: " + args[1].toInt32()); log(" NewProtect: " + args[2]); log(" Hexdump:\n" + hexdump(args[0])); }, onLeave(log, retval, state) { } }
$ frida-trace -i "VirtualAlloc" -i "VirtualProtect" sample1.exe Instrumenting... ... Started tracing 4 functions. Press Ctrl+C to stop. /* TID 0x374 */ 1157 ms [*] VirtualAlloc Hooked! 1157 ms Size (bytes): 260608 1157 ms Protect: 0x4 1157 ms | [*] VirtualAlloc Hooked! 1157 ms | Size (bytes): 260608 1157 ms | Protect: 0x4 1157 ms | VirtualAlloc Returned: 0x3550000 1157 ms VirtualAlloc Returned: 0x3550000 1157 ms [*] VirtualProtect Hooked! 1157 ms Address: 0x3550000 1157 ms Size: 260608 1157 ms NewProtect: 0x20 1157 ms Hexdump: 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF 03550000 4d 5a 41 52 55 48 89 e5 48 81 ec 20 00 00 00 48 MZARUH..H.. ...H 03550010 8d 1d ea ff ff ff 48 89 df 48 81 c3 f4 63 01 00 ......H..H...c.. 03550020 ff d3 41 b8 f0 b5 a2 56 68 04 00 00 00 5a 48 89 ..A....Vh....ZH. 03550030 f9 ff d0 00 00 00 00 00 00 00 00 00 f8 00 00 00 ................ 03550040 0e 1f ba 0e 00 b4 09 cd 21 b8 01 4c cd 21 54 68 ........!..L.!Th 03550050 69 73 20 70 72 6f 67 72 61 6d 20 63 61 6e 6e 6f is program canno 03550060 74 20 62 65 20 72 75 6e 20 69 6e 20 44 4f 53 20 t be run in DOS 03550070 6d 6f 64 65 2e 0d 0d 0a 24 00 00 00 00 00 00 00 mode....$....... 03550080 8c 6b 6e 52 c8 0a 00 01 c8 0a 00 01 c8 0a 00 01 .knR............ 03550090 ae e4 d2 01 50 0a 00 01 56 aa c7 01 c9 0a 00 01 ....P...V....... 035500a0 39 cc cf 01 e1 0a 00 01 39 cc ce 01 40 0a 00 01 9.......9...@... 035500b0 39 cc cd 01 c2 0a 00 01 c1 72 93 01 c3 0a 00 01 9........r...... 035500c0 c8 0a 01 01 14 0a 00 01 ae e4 ce 01 fd 0a 00 01 ................ 035500d0 ae e4 ca 01 c9 0a 00 01 ae e4 cc 01 c9 0a 00 01 ................ 035500e0 52 69 63 68 c8 0a 00 01 00 00 00 00 00 00 00 00 Rich............ 035500f0 00 00 00 00 00 00 00 00 50 45 00 00 64 86 05 00 ........PE..d...
Cách này có thể áp dụng được với cả hàm VirtualProtectVirtualAlloc. Nhìn vào kết quả dễ dàng thấy được một số chuỗi như: MZ, This program cannot be run in DOS mode, PE,.v.v.. Điều này cho biết đây là một tệp Executable, ta phỏng đoán đây chính là Cobalt Strike Beacon sau khi đã được giải mã. Thử tiến hành dump vùng nhớ này ra tệp để thu lại mẫu:
Tệp VirtualProtect.js:
{ onEnter(log, args, state) { log('[*] VirtualProtect Hooked!'); log(" Address: " + args[0]); log(" Size: " + args[1].toInt32()); log(" NewProtect: " + args[2]); log(" Hexdump:\n" + hexdump(args[0])); if (args[0].readAnsiString(2) == "MZ") { log(" Found an MZ!"); var exeContent = args[0].readByteArray(args[1].toInt32()); var filename = args[0] + "_dump.bin"; var file = new File(filename, "wb"); file.write(exeContent); file.flush(); file.close(); log(" Success dump file: " + filename); } }, onLeave(log, retval, state) { } }
Chạy lại và quan sát kết quả:
$ frida-trace -i "VirtualAlloc" -i "VirtualProtect" sample1.exe Instrumenting... ... 1157 ms | [*] VirtualProtect Hooked! 1157 ms | Address: 0x3550000 1157 ms | Size: 260608 1157 ms | NewProtect: 0x20 1157 ms | Hexdump: 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF 03550000 4d 5a 41 52 55 48 89 e5 48 81 ec 20 00 00 00 48 MZARUH..H.. ...H 03550010 8d 1d ea ff ff ff 48 89 df 48 81 c3 f4 63 01 00 ......H..H...c.. 03550020 ff d3 41 b8 f0 b5 a2 56 68 04 00 00 00 5a 48 89 ..A....Vh....ZH. 03550030 f9 ff d0 00 00 00 00 00 00 00 00 00 f8 00 00 00 ................ 03550040 0e 1f ba 0e 00 b4 09 cd 21 b8 01 4c cd 21 54 68 ........!..L.!Th 03550050 69 73 20 70 72 6f 67 72 61 6d 20 63 61 6e 6e 6f is program canno 03550060 74 20 62 65 20 72 75 6e 20 69 6e 20 44 4f 53 20 t be run in DOS 03550070 6d 6f 64 65 2e 0d 0d 0a 24 00 00 00 00 00 00 00 mode....$....... 03550080 8c 6b 6e 52 c8 0a 00 01 c8 0a 00 01 c8 0a 00 01 .knR............ 03550090 ae e4 d2 01 50 0a 00 01 56 aa c7 01 c9 0a 00 01 ....P...V....... 035500a0 39 cc cf 01 e1 0a 00 01 39 cc ce 01 40 0a 00 01 9.......9...@... 035500b0 39 cc cd 01 c2 0a 00 01 c1 72 93 01 c3 0a 00 01 9........r...... 035500c0 c8 0a 01 01 14 0a 00 01 ae e4 ce 01 fd 0a 00 01 ................ 035500d0 ae e4 ca 01 c9 0a 00 01 ae e4 cc 01 c9 0a 00 01 ................ 035500e0 52 69 63 68 c8 0a 00 01 00 00 00 00 00 00 00 00 Rich............ 035500f0 00 00 00 00 00 00 00 00 50 45 00 00 64 86 05 00 ........PE..d... 1157 ms | Found an MZ! 1157 ms | Success dump file: 0x3550000_dump.bin ...
Đây thực chất là một Cobalt Strike Beacon DLL:
$ file 0x3550000_dump.bin 0x3550000_dump.bin: PE32+ executable (DLL) (GUI) x86-64, for MS Windows $ sha256sum 0x3550000_dump.bin 5becedab08e72cb27d0e1f83e666a04109e694883876b1e9c5e718cbca5730f0 *0x3550000_dump.bin
Kết quả kiểm tra sau khi upload mẫu lên VirusTotal cho thấy có khoảng 18/49 engine cũng phát hiện là Cobalt Strike/Beacon:
notion image
Và mẫu cũng bị phát hiện bởi nhiều YARA Rules:
notion image
Có một lưu ý là các tệp PE được dump từ bộ nhớ xuống thì thường sẽ phải fix lại IAT để có thể hoạt động đúng đắn. Phần này không đi phân tích thêm mẫu Cobalt Strike Beacon DLL đã thu được, mà mục đích chỉ tập chung vào việc ứng dụng Frida để unpacking mẫu, tự động hóa một phần trong quá trình phân tích Malware.

4. Identifying and Extracting Shellcode

Trong phần này sẽ đến với một mẫu khác cũng là Cobalt Strike nhưng Beacon Payload lúc này không phải là một DLL Executable như trước nữa mà là Shellcode. Mẫu có sẵn trên VirusTotal. Đầu tiên thử kiểm tra mẫu với các APIs như mẫu trước là VirtualAllocVirtualProtect
notion image
Kết quả trên cho biết tại địa chỉ 0x400000 thuộc tính vùng nhớ được thay đổi thành 0x40 (PAGE_EXECUTE_READWRITE). Đây là vùng nhớ chưa tệp executable của mẫu gốc ban đầu. Vùng nhớ có địa chỉ 0x24d0000 ban đầu có thuộc tính 0x4 (PAGE_READWRITE) nghĩa là nó không có quyền thực thi, sau đó nó được thay đổi thành 0x20 (PAGE_EXECUTE_READ) và lúc này nó đã có quyền thực thi.
Tiến hành Dump vùng nhớ này tương tự như mẫu số 1 trước ta được:
notion image
Các bytes FC E8 thường được tìm thấy ở đầu shellcode (Metasploit và Cobalt Strike). Thông thường để phát hiện được vùng nhớ này là Shellcode thì ta phải có các signatures hay nói cách khác là cần có một bộ rules để phát hiện, dạng tự như YARA vậy. Một vài signature có thể áp dụng trong trường hợp này như:
  • FC E8: CLD (clear direction flag) và CALL opcode
  • 55 8B EC: Là các lệnh push ebpmov ebp, esp (function prologue)
  • EB: Lệnh jump
  • E8: CALL instruction
Kết quả ta được như sau
notion image
Chạy lại và kiểm tra kết quả, chúng ta dump được shellcode ra disk
notion image
Đến bước này để phân tích Shellcode cần một công cụ có thể giả lập như: Qiling Framework, Libemu, Speakeasy của FireEye,.v.v.. Để đơn giản trong phần này tôi sử dụng scdbg và Speakeasy.
Kết quả khi sử dụng scdbg
notion image
Kết quả khi sử dụng Speakeasy, chi tiết hơn scdbg
notion image

5. Tổng kết

Sử dụng Frida trong phân tích Malware ngày càng phổ biến, Frida có nhiều ưu điểm mà các Debugger không có được song nó ko thể thay thế hoàn toàn được các Debugger. Chỉ nên sử dụng Frida để hỗ trợ tự động một số các bước trong quá trình phân tích để tăng tốc. Bài này chỉ ra một số phương pháp để xác định được Shellcode và các Executable được giải mã trên bộ nhớ của các tiến trình độc hại. Tóm tắt lại một số ý chính nhu sau:
  • Xác định các APIs cần theo dõi
  • Hooking vào các APIs như VirtualAlloc, VirtualProtect để theo dõi các vùng nhớ, đặc biệt là kiểm tra thuộc tính vùng nhớ (các quyền)
  • Xác định dữ liệu trên vùng nhớ sau khi giải mã: có thể là executable hoặc shellcode
  • Trích xuất vùng nhớ này ra disk để phục vụ giai đoạn phân tích sau.

6. Tham khảo