Чем не устраивает 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
