EnumProcesses

Như đã biết, ta có thể enum các tiến trình sử dụng CreateToolHelp32Snapshot. Tuy nhiên, còn một cách khác là sử dụng EnumProcesses.

Important

Việc hiện thực một kỹ thuật bằng nhiều cách khác nhau ở trong malware sẽ giúp cho nó trở nên khó đoán hơn.

Theo tài liệu của Microsoft, hàm EnumProcesses sẽ chỉ trả về một mảng các PID và ta cần dùng các hàm sau để xác định xem tiến trình nào là tiến trình mà ta cần tìm:

  1. OpenProcess: được dùng để mở handle đến tiến trình với quyền truy cập là PROCESS_QUERY_INFORMATION và PROCESS_VM_READ.
  2. EnumProcessModules: enum tất cả các module có trong tiến trình. Cần thiết cho bước 3.
  3. GetModuleBaseName: xác định tên của tiến trình từ danh sách các module có được ở bước 2.

Seealso

Chúng ta sẽ xây dựng hàm GetRemoteProcessHandle để in ra tên và PID của các tiến trình cũng như là trả về handle đến tiến trình đó:

BOOL GetRemoteProcessHandle(LPCWSTR szProcName, DWORD* pdwPid, HANDLE* phProcess) {
 
	DWORD		adwProcesses	[1024 * 2],
			    dwReturnLen1		= NULL,
			    dwReturnLen2		= NULL,
			    dwNmbrOfPids		= NULL;
 
	HANDLE		hProcess		= NULL;
	HMODULE		hModule			= NULL;
 
	WCHAR		szProc			[MAX_PATH];
	
	// Get the array of PIDs
	if (!EnumProcesses(adwProcesses, sizeof(adwProcesses), &dwReturnLen1)) {
		printf("[!] EnumProcesses Failed With Error : %d \n", GetLastError());
		return FALSE;
	}
 
	// Calculating the number of elements in the array 
	dwNmbrOfPids = dwReturnLen1 / sizeof(DWORD);
 
	printf("[i] Number Of Processes Detected : %d \n", dwNmbrOfPids);
 
	for (int i = 0; i < dwNmbrOfPids; i++) {
 
		// If process is not NULL
		if (adwProcesses[i] != NULL) {
 
			// Open a process handle 
			if ((hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, adwProcesses[i])) != NULL) {
 
				// If handle is valid
				// Get a handle of a module in the process 'hProcess'.
				// The module handle is needed for 'GetModuleBaseName'
				if (!EnumProcessModules(hProcess, &hModule, sizeof(HMODULE), &dwReturnLen2)) {
					printf("[!] EnumProcessModules Failed [ At Pid: %d ] With Error : %d \n", adwProcesses[i], GetLastError());
				}
				else {
					// If EnumProcessModules succeeded
					// Get the name of 'hProcess' and save it in the 'szProc' variable 
					if (!GetModuleBaseName(hProcess, hModule, szProc, sizeof(szProc) / sizeof(WCHAR))) {
						printf("[!] GetModuleBaseName Failed [ At Pid: %d ] With Error : %d \n", adwProcesses[i], GetLastError());
					}
					else {
						// Perform the comparison logic
						if (wcscmp(szProcName, szProc) == 0) {
							wprintf(L"[+] FOUND \"%s\" - Of Pid : %d \n", szProc, adwProcesses[i]);
							// Return by reference
							*pdwPid		= adwProcesses[i];
							*phProcess	= hProcess;
							break;	
						}
					}
				}
 
				CloseHandle(hProcess);
			}
		}
	}
 
	// Check if pdwPid or phProcess are NULL
	if (*pdwPid == NULL || *phProcess == NULL)
		return FALSE;
	else
		return TRUE;
}

Phân tích hàm trên:

  • Ta sẽ gán cứng số lượng tiến trình mà ta cần lấy ra là 1024 * 2.

  • Do mảng PID mà EnumProcesses trả về là mảng DWORD còn kích thước của mảng là dwReturnLen1 nên ta cần phải thực hiện phép chia dwReturnLen1 / sizeof(DWORD) để biết chính xác số phần tử có trong mảng.

  • Hàm EnumProcessModules trả về danh sách các module có trong tiến trình nhưng ta chỉ cần lấy ra module đầu tiên để biết được tên của chương trình.

    Do đó, đối số thứ 3 có giá trị bằng đúng với kích thước của một HMODULE.

Warning

Chỉ những tiến trình nào được chạy với cùng đặc quyền với mã độc mới có thể được truy xuất thông tin. Lý do là vì ta sẽ nhận được lỗi ERROR_ACCESS_DENIED nếu mở handle đến các tiến trình có đặc quyền cao hơn.

NtQuerySystemInformation

Chúng ta cũng có thể thực hiện enum process sử dụng một syscall có tên là NtQuerySystemInformation, được export từ ntdll.dll.

Retrieve NtQuerySystemInformation’s Address

Trước tiên, ta cần lấy ra địa chỉ của hàm bằng cách sử dụng GetModuleHandleGetProcAddress:

// Function pointer
typedef NTSTATUS (NTAPI* fnNtQuerySystemInformation)(
	SYSTEM_INFORMATION_CLASS SystemInformationClass,
	PVOID                    SystemInformation,
	ULONG                    SystemInformationLength,
	PULONG                   ReturnLength
);
 
fnNtQuerySystemInformation pNtQuerySystemInformation = NULL;
 
// Getting NtQuerySystemInformation's address
pNtQuerySystemInformation = (fnNtQuerySystemInformation)GetProcAddress(GetModuleHandle(L"NTDLL.DLL"), "NtQuerySystemInformation");
if (pNtQuerySystemInformation == NULL) {
	printf("[!] GetProcAddress Failed With Error : %d\n", GetLastError());
	return FALSE;
}

NtQuerySystemInformation Parameters

Nguyên mẫu của hàm NtQuerySystemInformation:

__kernel_entry NTSTATUS NtQuerySystemInformation(
  [in]            SYSTEM_INFORMATION_CLASS SystemInformationClass,
  [in, out]       PVOID                    SystemInformation,
  [in]            ULONG                    SystemInformationLength,
  [out, optional] PULONG                   ReturnLength
);

Giải thích các tham số :

  • SystemInformationClass - quyết định loại thông tin hệ thống mà hàm sẽ trả về.
  • SystemInformation - con trỏ đến buffer chứa thông tin hệ thống mà ta đã yêu cầu dựa trên tham số SystemInformationClass.
  • SystemInformationLength - kích thước của buffer chứa thông tin hệ thống.
  • ReturnLength - con trỏ đến một biến ULONG cho biết lượng byte thực sự đã được ghi vào tham số SystemInformation.

Do chúng ta chỉ cần enum process nên giá trị truyền vào tham số đầu tiên sẽ là cờ SystemProcessInformation. Việc sử dụng giá trị này sẽ làm cho hàm trả về một mảng các cấu trúc có kiểu là SYSTEM_PROCESS_INFORMATION.

SYSTEM_PROCESS_INFORMATION Structure

Cấu trúc của SYSTEM_PROCESS_INFORMATION:

typedef struct _SYSTEM_PROCESS_INFORMATION {
    ULONG NextEntryOffset;
    ULONG NumberOfThreads;
    BYTE Reserved1[48];
    UNICODE_STRING ImageName;
    KPRIORITY BasePriority;
    HANDLE UniqueProcessId;
    PVOID Reserved2;
    ULONG HandleCount;
    ULONG SessionId;
    PVOID Reserved3;
    SIZE_T PeakVirtualSize;
    SIZE_T VirtualSize;
    ULONG Reserved4;
    SIZE_T PeakWorkingSetSize;
    SIZE_T WorkingSetSize;
    PVOID Reserved5;
    SIZE_T QuotaPagedPoolUsage;
    PVOID Reserved6;
    SIZE_T QuotaNonPagedPoolUsage;
    SIZE_T PagefileUsage;
    SIZE_T PeakPagefileUsage;
    SIZE_T PrivatePageCount;
    LARGE_INTEGER Reserved7[6];
} SYSTEM_PROCESS_INFORMATION;

Chúng ta sẽ tập trung vào các thành phần sau:

  • ImageName: chứa tên tiến trình
  • UniqueProcessId: PID
  • NextEntryOffset: con trỏ đến tiến trình tiếp theo.

Do việc gọi hàm NtQuerySystemInformation với cờ SystemProcessInformation sẽ trả về một mảng có kích thước không cố định, ta cần gọi nó hai lần: lần đầu tiên là để lấy ra kích thước của mảng phục vụ cho việc cấp phát vùng nhớ, lần thứ hai là để lấy ra thông tin thực sự và lưu vào vùng nhớ đã được cấp phát.

ULONG                        uReturnLen1    = NULL,
                             uReturnLen2    = NULL;
PSYSTEM_PROCESS_INFORMATION  SystemProcInfo = NULL;
NTSTATUS                     STATUS         = NULL;
 
// First NtQuerySystemInformation call
// This will fail with STATUS_INFO_LENGTH_MISMATCH
// But it will provide information about how much memory to allocate (uReturnLen1)
pNtQuerySystemInformation(SystemProcessInformation, NULL, NULL, &uReturnLen1);
 
// Allocating enough buffer for the returned array of `SYSTEM_PROCESS_INFORMATION` struct
SystemProcInfo = (PSYSTEM_PROCESS_INFORMATION) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (SIZE_T)uReturnLen1);
if (SystemProcInfo == NULL) {
	printf("[!] HeapAlloc Failed With Error : %d\n", GetLastError());
	return FALSE;
}
 
// Second NtQuerySystemInformation call
// Calling NtQuerySystemInformation with the correct arguments, the output will be saved to 'SystemProcInfo'
STATUS = pNtQuerySystemInformation(SystemProcessInformation, SystemProcInfo, uReturnLen1, &uReturnLen2);
if (STATUS != 0x0) {
	printf("[!] NtQuerySystemInformation Failed With Error : 0x%0.8X \n", STATUS);
	return FALSE;
}

Lần gọi đầu tiên chắc chắn sẽ có lỗi STATUS_INFO_LENGTH_MISMATCH (0xC0000004) bởi vì chúng ta truyền vào các đối số không hợp lệ để lấy ra kích thước mảng.

Seealso

Xem thêm danh sách các mã lỗi: [MS-ERREF]: NTSTATUS Values | Microsoft Learn

Iterating Through Processes

Bước tiếp theo là lặp qua mảng các tiến trình và truy xuất đến thành phần ImageName.Buffer có chứa tên của tiến trình nhằm thực hiện so sánh với tên tiến trình mà ta cần tìm.

Để đi đến tiến trình tiếp theo có trong mảng (mà thật ra là một danh sách liên kết), ta có thể sử dụng con trỏ NextEntryOffset như sau:

// 'SystemProcInfo' will now represent a new element in the array
SystemProcInfo = (PSYSTEM_PROCESS_INFORMATION)((ULONG_PTR)SystemProcInfo + SystemProcInfo->NextEntryOffset);

Vòng lặp dùng để tìm PID dựa trên tên tiến trình:

while (TRUE) {
	// Check the process's name size
	// Comparing the enumerated process name to the intended target process
	if (SystemProcInfo->ImageName.Length && wcscmp(SystemProcInfo->ImageName.Buffer, szProcName) == 0) {
		
		// Opening a handle to the target process, saving it, and then breaking 
		*pdwPid		= (DWORD)SystemProcInfo->UniqueProcessId;
		*phProcess	= OpenProcess(PROCESS_ALL_ACCESS, FALSE, (DWORD)SystemProcInfo->UniqueProcessId);
		break;
	}
 
	// If NextEntryOffset is 0, we reached the end of the array
	if (!SystemProcInfo->NextEntryOffset)
		break;
 
	// Move to the next element in the array
	SystemProcInfo = (PSYSTEM_PROCESS_INFORMATION)((ULONG_PTR)SystemProcInfo + SystemProcInfo->NextEntryOffset);
}

Freeing Allocated Memory

Ta cần lưu lại con trỏ đến phần tử đầu tiên trước khi di chuyển con trỏ SystemProcInfo đến tiến trình tiếp theo:

// Since we will modify 'SystemProcInfo', we will save its initial value before the while loop to free it later
pValueToFree = SystemProcInfo;

Undocumented Part of NtQuerySystemInformation

Hàm NtQuerySystemInformation có một số thành phần không được tài liệu hóa bởi Microsoft chẳng hạn như một số kiểu dữ liệu của các thành phần trong cấu trúc SYSTEM_PROCESS_INFORMATION:

Chúng ta có thể tham khảo các tài liệu khác để biết rõ hơn về các thành phần chưa rõ:

list
from outgoing([[MalDev - Process Enumeration]])
sort file.ctime asc

Resources