Чтобы сформировать понимание, как происходит получение списка процессов, просто заглянем в исходники самого 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)
По итогу, я решил пока оставить драйвер и заняться гуевой частью, чтобы сразу под каждый инструмент прорабатывать внешний вид, как, что и куда будет выводиться.
Конец! А кто слушал - можете прокачать свои навыки на крутейшем курсе по реверсу ММОРПГ :)
vilgeforce
Не очень понятно зачем все это, если CreateToolhelp32Snapshot и соответствующие First- и Next- функции перечисляют все что что нужно и без чтения чужой памяти...
osieman Автор
Спасибо) Опыта у меня не много, делюсь тем, что вижу)
Как я понимаю, это не всегда доступно.
vilgeforce
Доступно начиная с WinXP, статуса устаревшей на ней не висит. Вроде и от обычного пользователя работать должна...