Anti-Debugging

Một số cách chống debug:

  • Kiểm tra sự hiện diện của debugger: malware tìm kiếm các tiến trình hoặc file liên quan đến debugger chẳng hạn như dùng hàm IsDebuggerPresent của Window API.
  • Malware có thể chỉnh sửa hoặc làm hư hỏng debug log để debugger không thể điều khiển được quá trình thực thi của code.
  • Sử dụng code tự chỉnh sửa: malware tự động thay đổi chính nó trong khi chạy để debugger khó theo kịp luồng thực thi.

Anti-Debugging Using Suspend Thread

Mã nguồn của một malware sử dụng hàm SuspendThread thuộc Window API để treo luồng khi phát hiện ra debugger:

#include <windows.h>
#include <string.h>
#include <wchar.h>
#include <tlhelp32.h>
#include <stdio.h>
 
DWORD g_dwDebuggerProcessId = -1;
 
BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM dwProcessId)
{
    DWORD dwWindowProcessId;
    GetWindowThreadProcessId(hwnd, &dwWindowProcessId);
 
    if (dwProcessId == dwWindowProcessId)
    {
		int windowTitleSize = GetWindowTextLengthW(hwnd);
		if ( windowTitleSize <= 0 )
		{
			return TRUE;
		}
		wchar_t* windowTitle = (wchar_t*)malloc((windowTitleSize + 1) * sizeof(wchar_t));
		
        GetWindowTextW(hwnd, windowTitle, windowTitleSize + 1);
 
		if (wcsstr(windowTitle, L"dbg") != 0 ||
			wcsstr(windowTitle, L"debugger") != 0 )
		{
            g_dwDebuggerProcessId = dwProcessId;
			return FALSE;
		}
 
       return FALSE;
    }
 
    return TRUE;
}
 
DWORD IsDebuggerProcess(DWORD dwProcessId)
{
    EnumWindows(EnumWindowsProc, (LPARAM)dwProcessId);
    return g_dwDebuggerProcessId == dwProcessId;
}
 
DWORD SuspendDebuggerThread()
{
	HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
    if (hSnapshot == INVALID_HANDLE_VALUE)
	{
        printf("Failed to create snapshot\n");
        return 1;
    }
 
    THREADENTRY32 te32;
    te32.dwSize = sizeof(THREADENTRY32);
 
    if (!Thread32First(hSnapshot, &te32))
	{
        printf("Failed to get first thread\n");
        CloseHandle(hSnapshot);
        return 1;
    }
 
    do
	{
        HANDLE hThread = OpenThread(THREAD_QUERY_INFORMATION | THREAD_SUSPEND_RESUME, FALSE, te32.th32ThreadID);
        if (hThread != NULL)
		{
            DWORD dwProcessId = GetProcessIdOfThread(hThread);
			if ( IsDebuggerProcess(dwProcessId) )
			{
				printf("Debugger found with pid %i! Suspending!\n", dwProcessId);
				DWORD result = SuspendThread(hThread);
 				if ( result == -1 )
				{
					printf("Last error: %i\n", GetLastError());
				}
			}
            CloseHandle(hThread);
        }
    } while (Thread32Next(hSnapshot, &te32));
 
    CloseHandle(hSnapshot);
 
    return 0;
}
 
int main(void)
{
	SuspendDebuggerThread();
 
	printf("Continuing malicious operation...");
	getchar();
}

Giải thích cách hoạt động:

  • Malware lặp qua tất cả các luồng trong hệ thống thông qua hàm CreateToolhelp32Snapshot, hàm Thread32First và hàm Thread32Next.
  • Với mỗi thread, malware dùng hàm EnumWindows để lặp qua các cửa sổ được hiển thị trong hệ thống.
  • Nếu tên của cửa sổ hiển thị có chứa chuỗi debugger, dbg hoặc debug thì malware biết được có debugger đang chạy.
  • Nếu có sự hiện diện của debugger, malware treo luồng của debugger và khiến nó bị crash.
  • Malware tiếp tục thực hiện hành vi độc hại.

Để giải quyết điều này thì cần sử dụng patching1. Cụ thể, sau khi sử dụng x32dbg để debug malware, ta cần tìm kiếm lời gọi hàm SuspendThread thông qua context menu: “Search for > Current Module > Intermodular calls”.

Kết quả tìm kiếm có thể có dạng như sau:

Double-click để đi đến chỗ gọi hàm. Sau đó, thay instruction này thành các nop instruction:

Tip

Có thể xuất bản vá ra để áp dụng cho những lần debug sau thông qua option “View > Patch file > Export”.

Cuối cùng, nhấn F9 lần nữa để chạy malware thì ta thấy malware không còn khiến debugger bị treo nữa.

VM Detection

Malware sẽ kiểm tra các mục tiêu sau để phát hiện máy ảo:

  • Các tiến trình đang chạy: các máy ảo thường có các tiến trình dễ nhận diện. Ví dụ, VMWare có tiến trình của vmtools còn VirtualBox có tiến trình của vboxservice. Malware sẽ sử dụng hàm EnumProcess của Window API để lặp qua các tiến trình nhằm tìm kiếm sự hiện diện của những công cụ này.
  • Các phần mềm phân tích được cài đặt chẳng hạn như debugger, decompiler, … Malware sẽ tìm kiếm các phần mềm này trong registry key SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall.
  • Các địa chỉ MAC tương ứng với các máy ảo. Ví dụ, một số máy ảo sẽ tự sinh ra địa chỉ MAC có các số sau: 00-05-6900-0c-2900-1c-14 hoặc 00-50-56 (VMWare).
  • Tài nguyên của máy chẳng hạn như CPU và RAM. Ví dụ, một máy mà có RAM ít hơn 8GB thì rất có thể là máy ảo.
  • Kết nối của các thiết bị ngoại vi chẳng hạn như máy in bởi vì máy ảo thường không cấu hình các thiết bị này.
  • Active Directory domain thông qua các biến môi trường chẳng hạn như LoggonServerComputerName.
  • Thời gian thực hiện một chỉ thị hoặc truy cập vào một tài nguyên cụ thể. Thông thường, thời gian để thực hiện những hành động này trong máy ảo sẽ chậm hơn thông thường.

Khi malware phát hiện ra nó đang được thực thi trong máy ảo, nó sẽ thay đổi hành vi theo những cách sau:

  • Chỉ thực thi một số ít các tính năng.
  • Tự hủy hoặc tự xóa chính nó bằng cách tự ghi đè một phần mã nguồn.
  • Gây thiệt hại cho hệ thống chẳng hạn như xóa hoặc mã hóa tập tin.
  • Không chạy gì cả.

Để ngăn cản việc phát hiện của malware, ta có thể làm cho máy ảo giống một máy vật lý thông thường chẳng hạn như giấu registry của các phần mềm phân tích, đổi địa chỉ MAC hoặc cấu hình để máy ảo trông có vẻ như là đang kết nối đến máy in. Có thể sử dụng các script có sẵn để tự động hóa việc này chẳng hạn như VMwareCloak hoặc VBoxCloak.

VM Detection by Checking the Temperature

Giá trị Win32_TemperatureProbe thuộc lớp Windows Management Instrumentation (WMI) chứa dữ liệu thời gian thực về nhiệt độ của phần cứng được cung cấp bởi cấu trúc dữ liệu SMBIOS (System Management BIOS). Trong môi trường máy ảo, giá trị trả về sẽ là Not Supported. Malware có thể lợi dụng điều này để kiểm tra xem nó có đang được chạy trong máy ảo hay không.

Note

Trong máy vật lý thì giá trị này vẫn có thể là Not Supported nếu phần cứng không hỗ trợ tính năng SMBIOS.

Mã nguồn mà malware sử dụng:

#include <stdio.h>
#include <windows.h>
#include <wbemidl.h>
#include <combaseapi.h>
 
#pragma comment(lib, "wbemuuid.lib")
#pragma comment(lib, "user32.lib")
#pragma comment(lib, "ole32.lib")
#pragma comment(lib, "oleaut32.lib")
 
BOOL hasThermalZoneTemp();
 
int main()
{
	if ( !hasThermalZoneTemp() )
	{
		MessageBox(NULL, "Proceeding with non-malicious activities...", "VM Detected" , MB_OK);
		return 0;
	}
 
	MessageBox(NULL, "Proceeding with malicious activities...", "Starting malware", MB_OK);
	return 0;
}
 
BOOL hasThermalZoneTemp()
{
	IWbemLocator* pLoc = NULL;
	IWbemServices* pSvc = NULL;
	IEnumWbemClassObject* pEnumerator = NULL;
	IWbemClassObject* pclsObj = (IWbemClassObject*)malloc(sizeof(IWbemClassObject));
	
	ULONG uReturn = 0;
 
	HRESULT hr = CoInitializeEx(0, COINIT_MULTITHREADED);
	hr = CoInitializeSecurity(NULL, -1,	NULL, NULL,	RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE,	NULL, EOAC_NONE, NULL);
	hr = CoCreateInstance(CLSID_WbemLocator, NULL, CLSCTX_INPROC_SERVER, IID_IWbemLocator, (LPVOID*)&pLoc);
	hr = pLoc->ConnectServer(L"root\\wmi", NULL, NULL, 0, NULL,	0, 0, &pSvc);
	hr = CoSetProxyBlanket(pSvc, RPC_C_AUTHN_WINNT,	RPC_C_AUTHZ_NONE, NULL,	RPC_C_AUTHN_LEVEL_CALL,	RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE);
	hr = pSvc->ExecQuery(L"WQL", L"SELECT * FROM MSAcpi_ThermalZoneTemperature", WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,	NULL, &pEnumerator);
 
	while (pEnumerator)
	{
		hr = pEnumerator->Next(WBEM_INFINITE, 1, &pclsObj, &uReturn);
		if (uReturn == 0)
		{
			return 0;
		}
 
		VARIANT vtProp;
 
		hr = pclsObj->Get(L"CurrentTemperature", 0, &vtProp, 0, 0);
		if (SUCCEEDED(hr))
		{
			printf("Thermal Zone Temperature: %d\n", vtProp.intVal);
			return 1;
		}
 
		VariantClear(&vtProp);
		pclsObj->Release();
	}
 
	pEnumerator->Release();
	pSvc->Release();
	pLoc->Release();
	
	CoUninitialize();
 
    return 0;
}

Để ngăn cản malware phát hiện ra máy ảo thì có thể sử dụng patching. Cụ thể, sau khi mở malware bằng x32dbg thì nhấn CTRL + G để đi đến địa chỉ 0x004010E0 ở trong code.

Từ địa chỉ 0x004010E0 đến 0x004010F8 tương đương với dòng code hr = pEnumerator->Next(WBEM_INFINITE, 1, &pclsObj, &uReturn);.

Tại địa chỉ 004010FD là một phép so sánh [ebp-18] (uReturn) với số 0 và nó tương đương với dòng code if (uReturn == 0).

Nếu giá trị của uReturn là 0 thì hàm hasThermalZoneTemp sẽ có giá trị là false, đồng nghĩa với việc máy đang chạy malware không có thông tin về nhiệt độ.

Sau khi chọn “Follow in dump > Address: EBP-18” thì debugger chuyển dump view đến vị trí vùng nhớ 0019FF08:

Double-click vào byte 00 cuối cùng rồi chọn “Modify Value” để sửa thành 01 nhằm bypass được điều kiện so sánh uReturn == 0:

Nhấn F8 để step into và ta thấy debugger nhảy từ địa chỉ 00401101 đến 0040110A. Việc này sẽ gây ra exception bởi vì đáng lẽ chương trình phải thoát sau khi gọi hàm hasThermalZoneTemp.

Thay vì chạy tiếp, ta có thể nhảy đến một vùng code nào đó chẳng hạn như dòng code printf("Thermal Zone Temperature: %d\n", vtProp.intVal); có địa chỉ từ 00401130 đến 00401139:

Để nhảy đến địa chỉ 00401134, ta có thể chỉnh sửa giá trị của thanh ghi eip (instruction pointer) bằng cách chuột phải vào EIP rồi chọn “Modify value”.

Cuối cùng, nhấn F9 để chương trình tiếp tục chạy.

Packers

Một số kỹ thuật làm rối mà malware sử dụng:

  • Encode dữ liệu chẳng hạn như các chuỗi CLI, tên miền, … sử dụng các kỹ thuật encode chẳng hạn như XOR hoặc Base64.
  • Sử dụng các kỹ thuật mã hóa để mã hóa các giao tiếp đến C2, định dạng file hoặc là network traffic.
  • Làm rối code bằng cách đổi cú pháp, tên hàm, tên biến hoặc chia code ra nhiều file.

Packer là công cụ giúp nén và mã hóa file thực thi. File thực thi gốc sẽ được nén lại và nhúng vào một file thực thi khác mà đóng vai trò như là wrapper hay container. Việc nén file làm giảm kích thước của file gốc, giúp cho việc phân phối và cài đặt nó dễ dàng hơn. Một số packer có một số tính năng chẳng hạn như làm rối code, runtime packing hoặc anti-debugging.

Một số packer phổ biến:

Không phải chương trình nào sử dụng packer cũng là mã độc bởi vì một số nhà phân phối phần mềm cần nó để bảo vệ khỏi việc ăn cắp chất xám.

Identifying Packer

Bước đầu tiên để đối phó với malware có sử dụng packer là xác định xem loại packer mà nó sử dụng. Có thể sử dụng công cụ DetectItEasy hoặc PEStudio để làm việc này.

DetectItEasy có một danh sách các signature của các packer phổ biến giúp phát hiện ra packer mà file sử dụng. Nếu nó có thể nhận diện được packer thì nó sẽ hiện như sau:

Có thể click vào “Entropy” để xem section nào bị pack.

Nếu sử dụng PEStudio, ta có thể vào phần “sections (self-modifying)” để xem các section trong file:

Thông thường thì một file thực thi sẽ có section .text, .data.rsrc. Tuy nhiên, trong hình minh họa trên, tên của các section đã bị thay đổi thành UPX0UPX1, và UPX2. Đây có thể là dấu hiệu của packer UPX.

Important

Mỗi packer sẽ sử dụng một kỹ thuật riêng biệt để pack file nên những cách làm trên không phải lúc nào cũng có thể áp dụng được.

Automated Unpacking

Sau khi nhận diện được loại packer thì ta cần dùng unpacker tương ứng. Một số packer chẳng hạn như UPX sử dụng cùng một công cụ cho việc packing và unpacking. Một số tool thương mại chẳng hạn như Themida thì ta cần sử dụng các unpacker script từ bên thứ 3.

Một số script:

Cũng có thể sử dụng trang web unpac.me để unpack tự động.

Manual Unpacking and Dumping

Tuy nhiên, cách tốt nhất để unpack là thực thi nó. Khi một file bị pack được thực thi, wrapper hoặc container code sẽ giải mã và gỡ rối file thực thi gốc. Khi đó, chúng ta có thể truy xuất malware đã được unpack ra để tiện phân tích về sau.

Giả sử địa chỉ 004172D4 trong malware là nơi gọi đến entry point của file thực thi gốc, ta có thể đặt breakpoint của x32dbg ở đây:

Nhấn F7 để thực thi chỉ thị jmp. Chỉ thị này sẽ nhảy đến địa chỉ 00401262 và đây chính là entry point của file thực thi gốc:

Giải thích hai bước trên: khi chương trình chạy đến 004172D4 thì có nghĩa là file đã được unpack thành công và nó sẽ sao chép code của file thực thi gốc vào địa chỉ 00401262.

Để trích xuất file thực thi gốc sau khi unpack, chọn “Plugins > Scylla > Dump” và lưu cùng vị trí với file chưa được unpack.

Scylla là một công cụ giúp dump bộ nhớ của tiến trình và xây dựng lại Import Address Table (IAT)2.

Sau khi trích xuất file thì nhấn “IAT Autosearch” để nó quét qua bộ nhớ của tiến trình nhằm tìm import address table. Sau đó, nhấn “Get Imports” để cập nhật import list section:

Có thể xóa dòng cuối cùng vì nó không hợp lệ.

Nhấn vào nút “Fix Dump” rồi chọn file dump đã xuất để sửa IAT.

Kiểm tra lại bằng cách dùng DetectItEasy:

Có thể thấy, nó không tìm thấy packer có trong file nữa.

list
from outgoing([[TryHackMe - Anti-Reversing]])
sort file.ctime asc

Resources

Footnotes

  1. xem thêm Bypassing Unwanted Execution Path

  2. xem thêm TryHackMe - Dissecting PE Headers