Чем не устраивает ArtMoney
Часто возникает необходимость найти и поменять какие-либо строки/числа в чужой программе. С этой задачей лучше всего справляется ArtMoney. Для тех, кто не умеет или не хочет использовать отладчики, это на сегодня, наверное, единственный вариант, так как нормальных аналогов просто нету. Хотя ArtMoney и поддерживает очень много возможностей для работы с памятью, весь процесс происходит вручную, без возможности создания действий по алгоритму. Если значений много и их надо, например, менять при каждом запуске программы, то время, затрачиваемое на эту работу, превышает всякие допустимые пределы. Выход один — написать свой редактор памяти!
Ищем исходники
В свое время у меня возникла задача, требующая модификации значений в памяти другой программы постоянно во время ее работы. Одним из условий было, что программа будет работать и на других машинах, а это значит, что вариант записи в память по заранее определенным адресам не подходил бы: адреса для разных машин будут разные и нужно реализовывать именно поиск адресов в памяти.
Я принялся за написание загрузчика и стал искать информацию по этому вопросу. К сожалению я не владею Delphi, и когда обнаружилось, что большинство исходников именно на этом языке, я немного растерялся. Переписывать огромное количество кода на C++ у меня не было ни времени, ни возможности. Еще одним открытием стало, что ни один из найденных мной исходников не работал корректно: программа либо висла, либо работала с ошибками. К тому же работа всех этих программ по поиску в памяти занимала гораздо больше времени, чем должна (раз в 15-20 дольше ArtMoney).
Поиск по форумам (в т.ч. и английским) так же не был радостным: в темах, связанных с сабжем по C++ были обрывки кода (явно без необходимого функционала). Не помню тем, где бы автор успешно решил свой вопрос. Тем не менее я вынес определенные знания про устройство оперативной памяти в Windows и узнал о функциях, используемых в работе.
После нескольких дней мучений я нашел один полезный топик. Автор приводит вполне рабочую программу на C++, к сожалению работала она очень медленно. Решение есть там же в предпоследнем посте. Осталось только скомпоновать два кода и снабдить их комментариями. Думаю, приведенная ниже программа многим поможет решить задачу поиска по памяти процесса, ну и узнать что-то новое.
Поиск и редактирование значений в памяти программы
Программа для теста
#include <windows.h>
char szText[] = "Hello world.",
szTitle[] = "Information";
main() {
while (TRUE)
MessageBox(NULL, szText, szTitle, MB_ICONINFORMATION);
return EXIT_SUCCESS;
}
/*Просто выведем окошко, в котором через память будем менять текст*/
Сам редактор
#include <stdio.h>
#include <windows.h>
#include <tlhelp32.h>
#
define PROC_NAME "n00b.exe"#
define MAX_READ 128
int fMatchCheck(char * mainstr, int mainstrLen, char * checkstr, int checkstrLen) {
/*
Проверка наличия подстроки в строке.
При этом под "строкой" подразумевается
просто последовательность байт.
*/
BOOL fmcret = TRUE;
int x, y;
for (x = 0; x < mainstrLen; x++) {
fmcret = TRUE;
for (y = 0; y < checkstrLen; y++) {
if (checkstr[y] != mainstr[x + y]) {
fmcret = FALSE;
break;
}
}
if (fmcret)
return x + checkstrLen;
}
return -1;
}
char * getMem(char * buff, size_t buffLen, int from, int to) {
/*
Выделяем у себя память, в которой будем хранить
копию данных из памяти чужой программы.
*/
size_t ourSize = buffLen * 2;
char * ret = (char * ) malloc(ourSize);
memset(ret, 0, ourSize);
memcpy(ret, & buff[from], buffLen - from);
memset( & ret[to - from], 0, to - from);
return ret;
}
char * delMem(char * buff, size_t buffLen, int from, int to) {
/*
Освобождаем память.
*/
size_t ourSize = buffLen * 2;
char * ret = (char * ) malloc(ourSize);
int i, x = 0;
memset(ret, 0, ourSize);
for (i = 0; i < buffLen; i++) {
if (!(i >= from && i < to)) {
ret[x] = buff[i];
x++;
}
}
return ret;
}
char * addMem(char * buff, size_t buffLen, char * buffToAdd, size_t addLen, int addFrom) {
/*
Запись в память.
*/
size_t ourSize = (buffLen + addLen) * 2;
char * ret = (char * ) malloc(ourSize);
int i, x = 0;
memset(ret, 0, ourSize);
memcpy(ret, getMem(buff, buffLen, 0, addFrom), addFrom);
x = 0;
for (i = addFrom; i < addLen + addFrom; i++) {
ret[i] = buffToAdd[x];
x++;
}
x = 0;
for (i; i < addFrom + buffLen; i++) {
ret[i] = buff[addFrom + x];
x++;
}
return ret;
}
char * replaceMem(char * buff, size_t buffLen, int from, int to, char * replaceBuff, size_t replaceLen) {
/*
Заменяем найденную "строку" на свою.
*/
size_t ourSize = (buffLen) * 2;
char * ret = (char * ) malloc(ourSize);
memset(ret, 0, ourSize);
memcpy(ret, buff, buffLen); // copy 'buff' into 'ret'
ret = delMem(ret, buffLen, from, to); // delete all memory from 'ret' betwen 'from' and 'to'
ret = addMem(ret, buffLen - to + from, replaceBuff, replaceLen, from);
return ret;
}
DWORD fGetPID(char * szProcessName) {
PROCESSENTRY32 pe = {
sizeof(PROCESSENTRY32)
};
HANDLE ss;
DWORD dwRet = 0;
ss = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (ss) {
if (Process32First(ss, & pe))
while (Process32Next(ss, & pe)) {
if (!strcmp(pe.szExeFile, szProcessName)) {
dwRet = pe.th32ProcessID;
break;
}
}
CloseHandle(ss);
}
return dwRet;
}
BOOL DoRtlAdjustPrivilege() {
/*
Важная функция. Получаем привилегии дебаггера.
Именно это позволит нам получить нужную информацию
о доступности памяти.
*/
#
define SE_DEBUG_PRIVILEGE 20 L# define AdjustCurrentProcess 0
BOOL bPrev = FALSE;
LONG(WINAPI * RtlAdjustPrivilege)(DWORD, BOOL, INT, PBOOL);
*(FARPROC * ) & RtlAdjustPrivilege = GetProcAddress(GetModuleHandle("ntdll.dll"), "RtlAdjustPrivilege");
if (!RtlAdjustPrivilege) return FALSE;
RtlAdjustPrivilege(SE_DEBUG_PRIVILEGE, TRUE, AdjustCurrentProcess, & bPrev);
return TRUE;
}
main() {
/*** VARIABLES ***/
HANDLE hProc;
MEMORY_BASIC_INFORMATION mbi;
SYSTEM_INFO msi;
ZeroMemory( & mbi, sizeof(mbi));
GetSystemInfo( & msi);
/*
Получаем информацию о памяти в текущей системе.
*/
DWORD dwRead = 0;
char * lpData = (VOID * ) GlobalAlloc(GMEM_FIXED, MAX_READ),
lpOrig[] = "Information", // что ищем
lpReplacement[] = "habrahabr.ru"; // на что меняем
int x, at;
/*****************/
if (!lpData)
return -1;
ZeroMemory(lpData, MAX_READ);
// открываем процесс
do {
hProc = OpenProcess(PROCESS_ALL_ACCESS,
FALSE,
fGetPID(PROC_NAME));
if (!hProc) {
Sleep(500);
puts("Cant open process!\n"
"Press any key to retry.\n");
getch();
}
} while (!hProc);
if (DoRtlAdjustPrivilege()) {
/*
Привилегии отладчика для работы с памятью.
*/
puts("Process opened sucessfully\n"
"Scanning memory...\n");
for (LPBYTE lpAddress = (LPBYTE) msi.lpMinimumApplicationAddress; lpAddress <= (LPBYTE) msi.lpMaximumApplicationAddress; lpAddress += mbi.RegionSize) {
/*
Этот цикл отвечает как раз за то, что наша программа не совершит
лишних действий. Память в Windows в процессе делится на "регионы".
У каждого региона свой уровень доступа: к какому-то доступ запрещен,
какой-то можно только прочитать. Нам нужны регионы доступные для записи.
Это позволит в разы ускорить работу поиска по памяти и избежать ошибок
записи в память. Именно так работает ArtMoney.
*/
if (VirtualQueryEx(fGetPID(PROC_NAME), lpAddress, & mbi, sizeof(mbi))) {
/*
Узнаем о текущем регионе памяти.
*/
if ((mbi.Protect & PAGE_READWRITE) || (mbi.Protect & PAGE_WRITECOPY)) {
/*
Если он доступен для записи, работаем с ним.
*/
for (lpAddress; lpAddress < (lpAddress + dwSize); lpAddress += 0x00000100) {
/*
Проходим по адресам указателей в памяти чужого процесса от начала, до конца региона
и проверяем, не в них ли строка поиска.
*/
dwRead = 0;
if (ReadProcessMemory(hProc,
(LPCVOID) lpAddress,
lpData,
MAX_READ, &
dwRead) == TRUE) {
/*
Читаем по 128 байт из памяти чужого процесса от начала
и проверяем, не в них ли строка поиска.
*/
if (fMatchCheck(lpData, dwRead, lpOrig, sizeof(lpOrig) - 1) != -1) {
/*Нашли, сообщим об успехе и поменяем в чужом процессе искомую строку на нашу.*/
printf("MEMORY ADDRESS: 0x00%x\n"
"DATA:\n", lpAddress);
for (x = 0; x < dwRead; x++) {
printf("%c", lpData[x]);
}
puts("\n");
at = fMatchCheck(lpData,
dwRead,
lpOrig,
sizeof(lpOrig) - 1);
if (at != -1) {
at -= sizeof(lpOrig) - 1;
lpData = replaceMem(lpData,
dwRead,
at,
at + sizeof(lpOrig) - 1,
lpReplacement,
/*sizeof(lpReplacement)-1*/
sizeof(lpOrig) - 1);
puts("REPLACEMENT DATA:");
for (x = 0; x < dwRead - sizeof(lpOrig) - 1 + sizeof(lpReplacement) - 1; x++) {
printf("%c", lpData[x]);
}
puts("\n");
puts("Replacing memory...");
if (WriteProcessMemory(hProc,
(LPVOID) lpAddress,
lpData,
/*dwRead-sizeof(lpOrig)-1+sizeof(lpReplacement)-1*/
dwRead, &
dwRead)) {
puts("Success.\n");
} else puts("Error.\n");
} else puts("Error.\n");
}
}
}
} else puts("Error.\n");
} else puts("Error.\n");
}
} else puts("Error.\n");
// // // // //
// Cleanup
if (hProc)
CloseHandle(hProc);
if (lpData)
GlobalFree(lpData);
///////////////
puts("Done. Press any key to quit...");
return getch();
}
В заключении
Код сыроват, есть над чем поработать, но это, видимо, единственный готовый пример решения задачи на C++ в рунете. Я намеренно не стал делать здесь все исправления и освещать все ньюансы, т.к. это сильно раздуло бы статью (а она и так не маленькая получилась). В любом случае он будет полезен, как каркас для написания редактора памяти под свои нужды, а так же дает понимание, как надо производить поиск в памяти, чтобы он занимал несколько секунд, а не минут.
Еще раз упомяну топик из которого взяты исходники и принципы работы.
P.S. За инвайт благодарю donnerjack13589