Чтобы сформировать понимание, как происходит получение списка процессов, просто заглянем в исходники самого Cheat Engine.
Здесь у нас есть процедура GetProcessList, в которую мы подаем массив строк, в который она запишем нам имена и айди процессов.
Первое, на что обратим внимание - структура, куда записывается информация о процесе, в СЕ она выглядит так
{$IFDEF WINDOWS} type TProcessListInfo=record processID: dword; winhandle: HWND; processIcon: HICON; //issystemprocess: boolean; end; PProcessListInfo=^TProcessListInfo; {$ENDIF}
Мы можем ее записать так.
typedef struct ProcessListInfo { DWORD processID; HWND winhandle; <-- не используется в GetProcessList HICON processIcon; <-- не используется в GetProcessList } ProcessListInfo, *PProcessListInfo;
Далее в процедуре идет блок с переменными
var SNAPHandle: THandle; ProcessEntry: PROCESSENTRY32; <--- сюда запишем информацию о процессе Check: Boolean; <--- сюда будем записывать ответ от Process32First/Next; {$IFDEF WINDOWS} lwindir: string; <--- путь в рабочий каталог Windows, так же не используется.. HI: HICON; <--- хендл иконки не используется в процедуре.. ProcessListInfo: PProcessListInfo; <--- наша структура {$ENDIF} i,j: integer; <--- не упомянается в коде вообще... s,s2: string; <--- не упомянается в коде вообще...
Как итог я оставил так
HANDLE snap_handle = nullptr; PROCESSENTRY32W process_entry; bool check; PProcessListInfo process_list_info;
Для замены под C++ объекта ProcessList: TStrings, я использовал std::unordered_map<std::wstring, PProcessListInfo>& process_list. Потому что в коде, была логика схожая с мапой, когда у нас по имени процесса идет связка с объектом, содержащим информацию о нем (фактически один ProcessID…)
getmem(ProcessListInfo,sizeof(TProcessListInfo)); // Выделение блока памяти размером с TProcessListInfo и получаем указатель на него в ProcessListInfo // Установка значений полей структуры PProcessListInfo (идентификатор процесса, дескрипторы иконки и окна) ProcessListInfo.processID:=processentry.th32ProcessID; ProcessListInfo.processIcon:=0; ProcessListInfo.winhandle:=0;
Что у нас эквивалентно
process_list_info = (PProcessListInfo)malloc(sizeof(ProcessListInfo)); process_list_info->processID = process_entry.th32ProcessID; process_list_info->processIcon = 0; process_list_info->winhandle = 0;
И в конце при необходимости это включается в лист
{$ifdef windows} // Если не требуется получение дополнительной информации о процессе (noProcessInfo=true), добавляем строку с именем исполняемого файла без объекта if noprocessinfo then ProcessList.Add(s) // В противном случае добавляем в список processlist новую запись состоящую из имени исполняемого файла и связанного с ней дополнительной информации (объекта) else ProcessList.AddObject(s, TObject(ProcessListInfo)); {$else}
Заменил на
if (no_process_info) process_list[ss.str()] = nullptr; else process_list[ss.str()] = process_list_info;
По этой процедуре особо добавить и нечего, она просто делает снимок и пробегается по процессам, выгружая данные…
Но тут мое любопытство увело меня в сторону от Cheat Engine, и я решил посмотреть, что там у Process Hacker, это утилита позволяет работать с процессами. Самой интересной частью является - список модулей. Потому, что там сразу можно увидеть кто-где и какой размер в памяти занимает каждый из них.
Через поиск по файла по фразе EnumModules я вышел на вот такую вот функцию
NTSTATUS PhpEnumProcessModules( _In_ HANDLE ProcessHandle, _In_ PPHP_ENUM_PROCESS_MODULES_CALLBACK Callback, _In_opt_ PVOID Context1, _In_opt_ PVOID Context2 )
так же у нее есть 32битная реализация
NTSTATUS PhpEnumProcessModules32( _In_ HANDLE ProcessHandle, _In_ PPHP_ENUM_PROCESS_MODULES32_CALLBACK Callback, _In_opt_ PVOID Context1, _In_opt_ PVOID Context2 )
Результатом работы оных будет вот такая вот структура
typedef struct _LDR_DATA_TABLE_ENTRY_PHNT { LIST_ENTRY InLoadOrderLinks; LIST_ENTRY InMemoryOrderLinks; union { LIST_ENTRY InInitializationOrderLinks; LIST_ENTRY InProgressLinks; }; PVOID DllBase; PLDR_INIT_ROUTINE EntryPoint; ULONG SizeOfImage; UNICODE_STRING FullDllName; UNICODE_STRING BaseDllName; union { UCHAR FlagGroup[4]; ULONG Flags; struct { ULONG PackagedBinary : 1; ULONG MarkedForRemoval : 1; ULONG ImageDll : 1; ULONG LoadNotificationsSent : 1; ULONG TelemetryEntryProcessed : 1; ULONG ProcessStaticImport : 1; ULONG InLegacyLists : 1; ULONG InIndexes : 1; ULONG ShimDll : 1; ULONG InExceptionTable : 1; ULONG ReservedFlags1 : 2; ULONG LoadInProgress : 1; ULONG LoadConfigProcessed : 1; ULONG EntryProcessed : 1; ULONG ProtectDelayLoad : 1; ULONG ReservedFlags3 : 2; ULONG DontCallForThreads : 1; ULONG ProcessAttachCalled : 1; ULONG ProcessAttachFailed : 1; ULONG CorDeferredValidate : 1; ULONG CorImage : 1; ULONG DontRelocate : 1; ULONG CorILOnly : 1; ULONG ChpeImage : 1; ULONG ChpeEmulatorImage : 1; ULONG ReservedFlags5 : 1; ULONG Redirected : 1; ULONG ReservedFlags6 : 2; ULONG CompatDatabaseProcessed : 1; }; }; USHORT ObsoleteLoadCount; USHORT TlsIndex; LIST_ENTRY HashLinks; ULONG TimeDateStamp; struct _ACTIVATION_CONTEXT *EntryPointActivationContext; PVOID Lock; // RtlAcquireSRWLockExclusive PLDR_DDAG_NODE DdagNode; LIST_ENTRY NodeModuleLink; struct _LDRP_LOAD_CONTEXT *LoadContext; PVOID ParentDllBase; PVOID SwitchBackContext; RTL_BALANCED_NODE BaseAddressIndexNode; RTL_BALANCED_NODE MappingInfoIndexNode; ULONG_PTR OriginalBase; LARGE_INTEGER LoadTime; ULONG BaseNameHashValue; LDR_DLL_LOAD_REASON LoadReason; // since WIN8 ULONG ImplicitPathOptions; ULONG ReferenceCount; // since WIN10 ULONG DependentLoadFlags; UCHAR SigningLevel; // since REDSTONE2 ULONG CheckSum; // since 22H1 PVOID ActivePatchImageBase; LDR_HOT_PATCH_STATE HotPatchState; } LDR_DATA_TABLE_ENTRY_PHNT, *PLDR_DATA_TABLE_ENTRY_PHNT;
Самыми интересными для нас будут PVOID DllBase - начало модуля относительно процесса
, ULONG SizeOfImage - размер модуля (сколько байт он занимает внутри процесса) и UNICODE_STRING FullDllName. Но с именем все не так просто. Структура выглядит так
typedef struct _UNICODE_STRING { USHORT Length; USHORT MaximumLength; PWSTR Buffer; } UNICODE_STRING;
Казалось бы, вот же она… PWSTR Buffer строка… выводись в студаут!! А вот и нет, здесь находится адрес в чужом пространстве, чтобы прочитать эту строку, придется сделать следующее.
PWSTR name[MAX_PATH]; // зададим наш буффер для строки ZeroMemory(name, MAX_PATH*sizeof(WCHAR)); // зануляем if (NT_SUCCESS(status = NtReadVirtualMemory( ProcessHandle, // хендл процесса currentEntry.FullDllName.Buffer, // наш указатель name, // буффер currentEntry.FullDllName.Length, // размер строки NULL ))) { // по итогу можем вывести результат в консоль и порадоваться :з std::wcout << std::hex << currentEntry.DllBase << L' ' << std::dec << currentEntry.SizeOfImage << L' ' << (WCHAR*)name << L'\n'; }
И тут я на радостях побежал смотреть все модули, но не тут-то было… Если получить список процессов вполне себе легитимная процедура, то читать память другого процесса уже не всегда дозволяется авторами софта. Но на этот случай у ProcessHacker есть свой собственный драйвер, на то он и хакер.
Все, до чего я докопался - это метод
PHLIBAPI NTSTATUS NTAPI KphConnect( _In_opt_ PWSTR DeviceName );
Данные о драйвере выглядят так
// Device #define KPH_DEVICE_SHORT_NAME L"KProcessHacker3" #define KPH_DEVICE_TYPE 0x9999 #define KPH_DEVICE_NAME (L"\\Device\\" KPH_DEVICE_SHORT_NAME)
Но, к сожалению, с наскока подключиться к драйверу не удалось и лучший ответ, который я получилъ
#define STATUS_INVALID_DEVICE_REQUEST ((NTSTATUS)0xC0000010)
По итогу, я решил пока оставить драйвер и заняться гуевой частью, чтобы сразу под каждый инструмент прорабатывать внешний вид, как, что и куда будет выводиться.
Конец! А кто слушал - можете прокачать свои навыки на крутейшем курсе по реверсу ММОРПГ :)
