Поиск и редактирование значений в памяти сторонней программы на C++

    Чем не устраивает 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
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 31

      +1
      ArtMoney же умеет работать с указателями, с указателями на начало блока памяти и т.п. Т.е. при желании можно для конкретной программы создать 100% работающую таблицу…
        0
        Я знаю, но это не поможет, например, в случае, когда адреса в памяти процесса меняются на протяжении работы (т.е. объекты в памяти удаляются и создаются заново). Кроме того, таблица для программы работает только для конкретного компьютера, при переносе на другую машину, адреса почти всегда меняются, и таблица становится бесполезной.
          +2
          А вот такое www.artmoney.ru/manual/russian/dma.htm не помогает? Способ нахождения муторный, но после перезапуска игр которые взламывал значения находит правильно.
            +1
            Спасибо. Помогает но, к сожалению, не всегда.

            Во-первых, как написано там же:
            «Указатели используется только в играх Win32 имеющих исполняемый файл. Никакие DOS игры, эмуляторы, Интернет-браузеры и игры Macromedia Flash указателей не имеют! И искать их бесполезно!»
            соответственно в этом случае поможет только поиск по значению в памяти процесса.

            Во-вторых (и это самое главное), ArtMoney все таки ручная программа и на автоматизацию не расчитана.

            Ну и в-третьих, способ с указателями не подходит, если программа динамически выделяет память во время работы, особенно, если количество блоков памяти (создаваемых объектов, в которых мы меняем значения) зависит от действий пользователя. В этом случае сканирование памяти — единственный выход, и каждый раз, во время работы, следить за объектами и использовать ArtMoney для их изменения очень не удобно.

            А в целом, AtrMoney отлично справляется с большинством задач. Как я уже писал, единственный существенный минус — невозможность автоматизировать процесс. Чтобы что-то найти, надо постоянно запускать программу, вводить значения, отсеивать и т.д. Если бы была возможность управлять действиями программы, например, из консоли, цены бы ей не было.
              0
              >Ну и в-третьих, способ с указателями не подходит, если программа динамически выделяет память во время работы

              Кау раз для этого указатели и нужны, те же lp (long pointer) в твоей программе. Впрочем я примерно понимаю о чём речь. Иногда из указателей нужна слишком сложная структура, чтобы добраться до нужных данных. Опять же анализ для её построения может стать проблемой.

              >Спасибо. Помогает но, к сожалению, не всегда.

              В ней ещё можно неправильно найти даже таким методом как DMA, то есть после перезагрузки игры будет облом. Потому и говорю, что процесс муторный. Надо понимать хоть немного, что делаешь по инструкции, иначе можно запросто пропустить сканирование нужных адресов и вместо них найти временное.

              Помню даже выходил из игр в главное меню (не закрывая приложение), и обратно возвращался, чтобы отыскать постоянное. Хотя в целом АртМани для обычных пользователей с простыми целями. В целом можно ведь и исследовать механизмы выделения памяти, стать экспертом. А с другой стороны, существуют специализированные программы, которые ломают игры с ходу сделанные для конкретной сборки. АртМани по сути где-то посередине.
            0
            возможно, стоило пропатчить оригинильный экзек, оставить себе хуков на вызовы динамического создания требуемых обьектов..?

            еще, в винде есть механизм хуков, к сожалению я с ним не знаком, но помоему стоит копнуть тему.

            инструментарий: например, Ida, Hiew
          0
          спасибо за статью, как раз когда то было необходимо сделать подобное, но знания не позволяли.

          добавил в избранное.
            0
            Пожалуйста, скоро, как разберусь во всех нюансах, добавлю готовые классы для поиска (и замены) строк.
              –1
              да, в АП чужих процессов спокойно не походишь crtшными классами (:

              буду ждать вашего релиза.
            +11
            Код бы подсветить да отдельным файлом для ленивых выложить.
              +1
              Насчёт скорости — попробуйте посмотреть на winner game cheater. Уже не помню почему, но он зачастую работал на несколько порядков быстрее artmoney.
                0
                Спасибо, посмотрю, но общие принципы работы с памятью под Windows одинаковы: работа с регионами памяти, чтение «блоками» из регионов.
              • НЛО прилетело и опубликовало эту надпись здесь
                  0
                  Спасибо. Я знаю это, с проблемой на счет границ уже столкнулся и исправил, скоро выложу функцию, которая буде работать и в таких случаях. А что Вы имели ввиду на счет ReplaceMem? Что конкретно плохо?
                  • НЛО прилетело и опубликовало эту надпись здесь
                      0
                      Не удивительно, что вы сейчас быстрее артмани, вы же проверяете в 128 раз меньше значений
                    +1
                    Спасибо большое, сам сталкивался с проблемой поиска информации для С++, решил было уже сесть за Делфи, но наткнулся на эту замечательную статью.
                      +4
                      А причём тут С++? Это ж WinAPI практически в чистом виде. Правильней было бы озаглавить «Доступ к памяти стороннего процесса в Windows. Реализация на C++».
                      0
                      Cheat Engine вам в помощь. Вы только что реализовали одну из его функциональностей. :)
                        0
                        Ссылку на топик поправьте: ...php?howtopic… -> ...php?showtopic…
                          +2
                          А я кстати автор заброшенного ныне конкурента ArtMoney, аки Detective Story. До тех пор как мне таки надоело я довольно успешно конкурировал с сабжем и временами обходил его технологически, хотя потом то же самое появлялось и в артмани.

                          В общем. Я бы мог ответить на некоторые вопросы наверное. Но сразу скажу, с совсем уж глупыми — пожалуйте в гугл.
                            +1
                            Не знаю как новые версии ArtMoney, но когда-то давно мне Detective Story показался более удачным. Создание трейнеров только чего стоит.
                            –4
                            Это умеет работать где-нибудь кроме Windows, например на Mac OS X?
                              +1
                              У вас эппл головного мозга.
                              +2
                              Попытался все это дело собрать и наткнулся на две ошибки в коде:
                              • 1. В for (lpAddress; lpAddress<(lpAddress + dwSize); lpAddress+=0x00000100) указан dwSize, который нигде не объявлен, наверное это mbi.RegionSize.
                              • 2. В if(VirtualQueryEx(fGetPID (PROC_NAME), lpAddress, &mbi, sizeof(mbi))) зачем-то опять получаем ID процесса, а нам нужен HANDLE, полученный на предыдущем этапе — hProc
                                –4
                                Программисты под GNU/Linux с упоением слелят за постом.
                                  0
                                  А там доступ к памяти другого активного процесса делается проще?
                                    0
                                    А там он как-то не делается вообще.
                                      +1
                                      GNU Debugger же как-то работает
                                        0
                                        на базе ptrace, цели другие, но то что в статье описано тоже легко делается )

                                Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                Самое читаемое