Свой Cheat Engine с нуля! Часть 1 — Получаем список процессов и модули в нем

от автора

Чтобы сформировать понимание, как происходит получение списка процессов, просто заглянем в исходники самого 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) 

По итогу, я решил пока оставить драйвер и заняться гуевой частью, чтобы сразу под каждый инструмент прорабатывать внешний вид, как, что и куда будет выводиться.

Конец! А кто слушал — можете прокачать свои навыки на крутейшем курсе по реверсу ММОРПГ 🙂


ссылка на оригинал статьи https://habr.com/ru/articles/878660/