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:
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
.EnumProcessModules
: enum tất cả các module có trong tiến trình. Cần thiết cho bước 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ảngDWORD
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 chiadwReturnLen1 / 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 GetModuleHandle
và GetProcAddress
:
// 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ếnULONG
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ìnhUniqueProcessId
: PIDNextEntryOffset
: 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õ:
- ReactOS: _SYSTEM_PROCESS_INFORMATION Struct Reference
- systeminformer/phnt/include/ntexapi.h at master · winsiderss/systeminformer
Related
list
from outgoing([[MalDev - Process Enumeration]])
sort file.ctime asc