Тема перечисления процессов затрагивалась здесь:Уроки - Разработка малвари-12. Иньекция в процесс
Также в этой теме для проведения атаки Thread Hijacking в удаленный процесс необходимо получить ID целевого процесса.
Давайте попробуем это сделать, что-бы не привлекать внимание антивирусов.)
Предлагаю использовать функцию NtQuerySystemInformation.
NtQuerySystemInformation экспортируется из модуля ntdll.dll, поэтому для его использования потребуется GetModuleHandle и GetProcAddress.
Документация Microsoft по NtQuerySystemInformation показывает, что он способен возвращать много информации о системе. Основное внимание этой статьи будет уделено его использованию для перечисления процессов.
Получение адреса NtQuerySystemInformation
Как было упомянуто ранее, для получения адреса NtQuerySystemInformation из ntdll.dll необходимы GetProcAddress и GetModuleHandle.
Параметры NtQuerySystemInformation
Параметры NtQuerySystemInformation показаны ниже.
SystemInformationClass - Определяет, какой тип системной информации возвращает функция.
SystemInformation - Указатель на буфер, который получит запрошенную информацию. Возвращенная информация будет в форме структуры, тип которой указан в соответствии с параметром SystemInformationClass.
SystemInformationLength - Размер буфера, на который указывает параметр SystemInformation, в байтах.
ReturnLength - Указатель на переменную ULONG, которая получит фактический размер информации, записанной в SystemInformation.
Поскольку задача заключается в перечислении процессов, будет использован флаг SystemProcessInformation. При использовании этого флага функция вернет массив структур SYSTEM_PROCESS_INFORMATION (через параметр SystemInformation), по одной для каждого процесса, работающего в системе.
Структура SYSTEM_PROCESS_INFORMATION
Следующим шагом будет изучение документации Microsoft, чтобы понять, как выглядит структура SYSTEM_PROCESS_INFORMATION.
Основное внимание будет уделено полям UNICODE_STRING ImageName, которое содержит имя процесса, и UniqueProcessId, которое является идентификатором процесса.
Кроме того, поле NextEntryOffset будет использовано для перехода к следующему элементу в возвращаемом массиве.
Поскольку вызов NtQuerySystemInformation с флагом SystemProcessInformation вернет массив структур SYSTEM_PROCESS_INFORMATION неизвестного размера, NtQuerySystemInformation должен быть вызван дважды. Первый вызов получит размер массива, который будет использован для выделения буфера, а затем второй вызов использует выделенный буфер.
Ожидается, что первый вызов NtQuerySystemInformation завершится с ошибкой STATUS_INFO_LENGTH_MISMATCH (0xC0000004), так как в нем передаются недействительные параметры, просто для получения размера массива.
Перечисление Процессов
Теперь, когда массив успешно получен, следующим шагом будет проход по нему что-бы найти нужный ImageName.
Buffer, который содержит имя процесса. На каждой итерации будет производиться сравнение имени процесса с целевым именем процесса.
Чтобы получить доступ к каждому элементу типа SYSTEM_PROCESS_INFORMATION в массиве, необходимо использовать член NextEntryOffset. Чтобы найти адрес следующего элемента, добавьте адрес предыдущего элемента к NextEntryOffset. Это демонстрируется в приведенном ниже фрагменте кода.
Освобождение выделенной памяти
Перед переходом SystemProcInfo к новому элементу в массиве необходимо сохранить начальный адрес выделенной памяти, чтобы освободить его позже. Следовательно, перед началом цикла адрес необходимо сохранить во временной переменной.
Недокументированная часть NtQuerySystemInformation
NtQuerySystemInformation остается в значительной степени недокументированным, и большая часть его до сих пор остается неизвестной. Например, обратите внимание на члены Reserved в SYSTEM_PROCESS_INFORMATION.
Полный код выполнения перечисления процессов с использованием NtQuerySystemInformation представлен ниже:
Этот код использует NtQuerySystemInformation, чтобы перечислить все процессы, работающие в системе, и затем ищет определенный процесс на основе его имени (szProcName). Если такой процесс найден, функция возвращает его идентификатор процесса (pdwPid) и дескриптор (phProcess). Если процесс не найден или произошла какая-либо другая ошибка, функция возвращает FALSE.
Демонстрация:
Также в этой теме для проведения атаки Thread Hijacking в удаленный процесс необходимо получить ID целевого процесса.
Давайте попробуем это сделать, что-бы не привлекать внимание антивирусов.)
Предлагаю использовать функцию NtQuerySystemInformation.
NtQuerySystemInformation экспортируется из модуля ntdll.dll, поэтому для его использования потребуется GetModuleHandle и GetProcAddress.
Документация Microsoft по NtQuerySystemInformation показывает, что он способен возвращать много информации о системе. Основное внимание этой статьи будет уделено его использованию для перечисления процессов.
Получение адреса NtQuerySystemInformation
Как было упомянуто ранее, для получения адреса NtQuerySystemInformation из ntdll.dll необходимы GetProcAddress и GetModuleHandle.
C:
// Указатель на функцию
typedef NTSTATUS (NTAPI* fnNtQuerySystemInformation)(
SYSTEM_INFORMATION_CLASS SystemInformationClass,
PVOID SystemInformation,
ULONG SystemInformationLength,
PULONG ReturnLength
);
fnNtQuerySystemInformation pNtQuerySystemInformation = NULL;
// Получение адреса NtQuerySystemInformation
pNtQuerySystemInformation = (fnNtQuerySystemInformation)GetProcAddress(GetModuleHandle(L"NTDLL.DLL"), "NtQuerySystemInformation");
if (pNtQuerySystemInformation == NULL) {
printf("[!] GetProcAddress завершилась с ошибкой: %d\n", GetLastError());
return FALSE;
}
Параметры NtQuerySystemInformation
Параметры NtQuerySystemInformation показаны ниже.
C:
__kernel_entry NTSTATUS NtQuerySystemInformation(
[in] SYSTEM_INFORMATION_CLASS SystemInformationClass,
[in, out] PVOID SystemInformation,
[in] ULONG SystemInformationLength,
[out, optional] PULONG ReturnLength
);
SystemInformationClass - Определяет, какой тип системной информации возвращает функция.
SystemInformation - Указатель на буфер, который получит запрошенную информацию. Возвращенная информация будет в форме структуры, тип которой указан в соответствии с параметром SystemInformationClass.
SystemInformationLength - Размер буфера, на который указывает параметр SystemInformation, в байтах.
ReturnLength - Указатель на переменную ULONG, которая получит фактический размер информации, записанной в SystemInformation.
Поскольку задача заключается в перечислении процессов, будет использован флаг SystemProcessInformation. При использовании этого флага функция вернет массив структур SYSTEM_PROCESS_INFORMATION (через параметр SystemInformation), по одной для каждого процесса, работающего в системе.
Структура SYSTEM_PROCESS_INFORMATION
Следующим шагом будет изучение документации Microsoft, чтобы понять, как выглядит структура SYSTEM_PROCESS_INFORMATION.
C:
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;
Основное внимание будет уделено полям UNICODE_STRING ImageName, которое содержит имя процесса, и UniqueProcessId, которое является идентификатором процесса.
Кроме того, поле NextEntryOffset будет использовано для перехода к следующему элементу в возвращаемом массиве.
Поскольку вызов NtQuerySystemInformation с флагом SystemProcessInformation вернет массив структур SYSTEM_PROCESS_INFORMATION неизвестного размера, NtQuerySystemInformation должен быть вызван дважды. Первый вызов получит размер массива, который будет использован для выделения буфера, а затем второй вызов использует выделенный буфер.
Ожидается, что первый вызов NtQuerySystemInformation завершится с ошибкой STATUS_INFO_LENGTH_MISMATCH (0xC0000004), так как в нем передаются недействительные параметры, просто для получения размера массива.
C:
ULONG uReturnLen1 = NULL,
uReturnLen2 = NULL;
PSYSTEM_PROCESS_INFORMATION SystemProcInfo = NULL;
NTSTATUS STATUS = NULL;
// Первый вызов NtQuerySystemInformation
// Он завершится ошибкой STATUS_INFO_LENGTH_MISMATCH
// Но он предоставит информацию о том, сколько памяти необходимо выделить (uReturnLen1)
pNtQuerySystemInformation(SystemProcessInformation, NULL, NULL, &uReturnLen1);
// Выделение достаточного буфера для возвращаемого массива структур `SYSTEM_PROCESS_INFORMATION`
SystemProcInfo = (PSYSTEM_PROCESS_INFORMATION) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (SIZE_T)uReturnLen1);
if (SystemProcInfo == NULL) {
printf("[!] HeapAlloc завершилась с ошибкой: %d\n", GetLastError());
return FALSE;
}
// Второй вызов NtQuerySystemInformation
// Вызываем NtQuerySystemInformation с правильными аргументами, результат будет сохранен в 'SystemProcInfo'
STATUS = pNtQuerySystemInformation(SystemProcessInformation, SystemProcInfo, uReturnLen1, &uReturnLen2);
if (STATUS != 0x0) {
printf("[!] NtQuerySystemInformation завершилась с ошибкой: 0x%0.8X \n", STATUS);
return FALSE;
}
Перечисление Процессов
Теперь, когда массив успешно получен, следующим шагом будет проход по нему что-бы найти нужный ImageName.
Buffer, который содержит имя процесса. На каждой итерации будет производиться сравнение имени процесса с целевым именем процесса.
Чтобы получить доступ к каждому элементу типа SYSTEM_PROCESS_INFORMATION в массиве, необходимо использовать член NextEntryOffset. Чтобы найти адрес следующего элемента, добавьте адрес предыдущего элемента к NextEntryOffset. Это демонстрируется в приведенном ниже фрагменте кода.
C:
// 'SystemProcInfo' теперь представляет новый элемент в массиве
SystemProcInfo = (PSYSTEM_PROCESS_INFORMATION)((ULONG_PTR)SystemProcInfo + SystemProcInfo->NextEntryOffset);
Освобождение выделенной памяти
Перед переходом SystemProcInfo к новому элементу в массиве необходимо сохранить начальный адрес выделенной памяти, чтобы освободить его позже. Следовательно, перед началом цикла адрес необходимо сохранить во временной переменной.
C:
// Поскольку мы будем изменять 'SystemProcInfo', мы сохраняем его начальное значение перед while-циклом, чтобы освободить его позже
pValueToFree = SystemProcInfo;
Недокументированная часть NtQuerySystemInformation
NtQuerySystemInformation остается в значительной степени недокументированным, и большая часть его до сих пор остается неизвестной. Например, обратите внимание на члены Reserved в SYSTEM_PROCESS_INFORMATION.
Полный код выполнения перечисления процессов с использованием NtQuerySystemInformation представлен ниже:
C:
BOOL GetRemoteProcessHandle(LPCWSTR szProcName, DWORD* pdwPid, HANDLE* phProcess) {
fnNtQuerySystemInformation pNtQuerySystemInformation = NULL;
ULONG uReturnLen1 = NULL,
uReturnLen2 = NULL;
PSYSTEM_PROCESS_INFORMATION SystemProcInfo = NULL;
NTSTATUS STATUS = NULL;
PVOID pValueToFree = NULL;
pNtQuerySystemInformation = (fnNtQuerySystemInformation)GetProcAddress(GetModuleHandle(L"NTDLL.DLL"), "NtQuerySystemInformation");
if (pNtQuerySystemInformation == NULL) {
printf("[!] GetProcAddress завершилась с ошибкой: %d\n", GetLastError());
return FALSE;
}
pNtQuerySystemInformation(SystemProcessInformation, NULL, NULL, &uReturnLen1);
SystemProcInfo = (PSYSTEM_PROCESS_INFORMATION)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (SIZE_T)uReturnLen1);
if (SystemProcInfo == NULL) {
printf("[!] HeapAlloc завершилась с ошибкой: %d\n", GetLastError());
return FALSE;
}
// Так как мы будем изменять 'SystemProcInfo', мы сохраняем его начальное значение перед циклом while, чтобы освободить его позже
pValueToFree = SystemProcInfo;
STATUS = pNtQuerySystemInformation(SystemProcessInformation, SystemProcInfo, uReturnLen1, &uReturnLen2);
if (STATUS != 0x0) {
printf("[!] NtQuerySystemInformation завершилась с ошибкой: 0x%0.8X \n", STATUS);
return FALSE;
}
while (TRUE) {
// Проверяем размер имени процесса
// Сравниваем имя перечисленного процесса с заданным целевым процессом
if (SystemProcInfo->ImageName.Length && wcscmp(SystemProcInfo->ImageName.Buffer, szProcName) == 0) {
// Открываем дескриптор целевого процесса, сохраняем его и завершаем выполнение
*pdwPid = (DWORD)SystemProcInfo->UniqueProcessId;
*phProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, (DWORD)SystemProcInfo->UniqueProcessId);
break;
}
// Если NextEntryOffset равен 0, мы достигли конца массива
if (!SystemProcInfo->NextEntryOffset)
break;
// Переходим к следующему элементу в массиве
SystemProcInfo = (PSYSTEM_PROCESS_INFORMATION)((ULONG_PTR)SystemProcInfo + SystemProcInfo->NextEntryOffset);
}
// Освобождаем память, используя начальный адрес
HeapFree(GetProcessHeap(), 0, pValueToFree);
// Проверяем, удалось ли нам успешно получить дескриптор целевого процесса
if (*pdwPid == NULL || *phProcess == NULL)
return FALSE;
else
return TRUE;
}
Этот код использует NtQuerySystemInformation, чтобы перечислить все процессы, работающие в системе, и затем ищет определенный процесс на основе его имени (szProcName). Если такой процесс найден, функция возвращает его идентификатор процесса (pdwPid) и дескриптор (phProcess). Если процесс не найден или произошла какая-либо другая ошибка, функция возвращает FALSE.
Демонстрация: