Вечная жизнь в играх своими руками

Хак в памяти


Начнем со сравнения дампов памяти. Выберем игру и будем над ней издеваться. Пусть это будет например, DOOMLegacy – лучший порт классического DOOM'а, бесплатно распространяемый вместе с исходными текстами и замечательно работающий как под LINUX, так и под win32 (см рис. 2). На момент написания этих строк последней стабильной версий была версия 1.42. Вот прямой линк для скачки: http://prdownloads.sourceforge.net/doomlegacy/ legacy142.exe?download. Для согласования наших действий рекомендуется использовать именно эту версию, иначе все смещения уползут черт знает куда, но если вы себя чувствуете достаточно подготовленным мыщъхем — попробуйте потерзать более свежие беты, доступные с основной страницы проекта: http://sourceforge.net/projects/doomlegacy.

Кроме DOOM Legacy нам так же потребуются wad-файлы из оригинального DOOM/DOOM2 или HERITC'а. Ну DOOM-то наверняка у каждого найдется! Запускам все это хозяйство и мочим монстров добрые полчаса, не в силах оторваться от экрана. Да… ностальгия — великая вещь, а монстры — уроды. Однозначно! Ладно, хрен с ними, давайте лучше ломать.

Рисунок 3 это не сон и не эмулятор, это полноценный перенос DOOM'а на win32!

Берем любой приличный (а можно и неприличный) дампер, например, знаменитый PE Tools (http://www.wasm.ru/baixado.php?mode=tool&id=124) или не менее знаменитый LordPE (http://www.wasm.ru/baixado.php?mode=tool&id=44), находим процесс "legacy.exe" и снимаем с работающей игрушки дамп типа "full dump", обзывая файл "dump_1.exe" или как-то так. (чтобы переключиться из полноэкранного режима необходимо нажать <Ctrl>+<Esc>). Условимся считать, что в этот момент у нас имеется 50 патронов.

Рисунок 4 мы, хакеры, работаем там, где другие отдыхают!

Возвращаемся в игру, немного перемещаемся (или просто стоим как идиоты) и дампимся еще раз, создавая файл dump_2.exe. Затем стреляем и сбрасываем dump_3, стреляем еще и получаем dump_4.exe.


После всех операций у нас в хвосте оказывается четыре файла: dump_1.exe/dump_2.exe с 50 патронами и dump_3.exe, dump_3.exe с 49- и 48 патронами соответственно. Теперь мы должны сравнить все четыре файла и найти такие ячейки, которые совпадают в dump_1.exe и dump_2.exe, но отличаются у всех остальных. Переменные, отвечающие за хранение количества патронов будут где-то среди них.

Для решения этой задачи мыщъх написал небольшую утилиту, исходный код которой приведен ниже:

#define MAX_DUMPS    0x10                 // макс. кол-во поддерживаемых дампов

main(int c, char **v)



{

       // объявляем переменные

       int pos=0; int a,b,flag; unsigned char ch[MAX_DUMPS]; FILE* f[MAX_DUMPS];

      

       // проверяем

аргументы

       if (c-- < 4) return printf("-err: need more files\n");

      

       // открываем все файлы

       for (a=0;a<c;a++)

              if (!(f[a]=fopen(v[a+1],"rb")))

                     return printf("-err: cant open %s\n",v[a+1]);

      

       // печать

имен файлов

       printf("raw offset");for (a=1;a<c;a++) printf("\t%s",v[a+1]);

      

       while(1)

       {

              // чтение очередной ячейки из каждого файла

              for(a=0;a<c;a++) if (!fread(&ch[a],1,1,f[a])) return 0; pos++;

             

              // если ячейки двух первых дампов совпадают они отбрасываются

              if (ch[0] - ch[1]) continue;

             

              // поиск совпадающих ячеек во втором и всех последущих дампах

              // (такие ячеки отбрасываются как "левые" и на фиг не нужные)

              for(a=flag=1;a<c;a++)

                     for(b=a;b<c;b++) if ((a-b) && (ch[a]==ch[b])) flag=0;

             

              // печать "правильных" ячеек

              if (flag)for (printf("\n%08Xh:",pos-1),a=1;a<c;a++)

                                                       printf("\t%02X",ch[a]);



       } printf("\n");

}

Листинг 1 простая утилита fck.c для сравнения дампов

Компилируем программу любым Си-компилятором (например, в случае Microsoft Visual C++ командная строка будет выглядеть так: "cl.exe /Ox fck.c") и запускаем свежеиспеченный файл на выполнение: "fck dump_1.exe dump_2.exe dump_3.exe dump_4.exe>o". Полученный результат (перенаправленный в файл "o") должен выглядеть так:

raw offset    dumped_2.exe  dumped_3.exe  dumped_4.exe

000A2FB0h:    B2h    2Bh    B6h

000A2FB6h:    C6h    00h    63h

000A2FC0h:    40h    00h    01h

000A2FC2h:    AEh    00h    46h

000A2FC8h:    16h    00h    65h

000A2FCAh:    18h    00h    56h

000A2FCCh:    60h    00h    65h

000A2FCEh:    15h    00h    72h

000A2FD0h:    EEh    00h    73h

000A2FD2h:    EEh    00h    69h

000A2FD4h:    C0h    00h    6Fh

000A2FDCh:    5Ah    00h    31h

000A2FEAh:    DCh    00h    36h

000A2FEDh:    80h    00h    0Bh

000A2FEEh:    2Ah    00h    15h

000A2FF0h:    08h    00h    EEh

000A2FF1h:    1Ch    00h    F1h

000A2FF6h:    59h    00h    06h

000A2FFAh:    02h    00h    49h

000A2FFCh:    FCh    00h    6Eh

000CFBA0h:    FEh    FDh    FFh

00152262h:    A1h    60h    00h

001523E2h:    A1h    60h    00h

00166574h:    32h    31h    30h

001666B4h:    32h    31h    30h

00168F28h:    32h    31h    30h

001772A5h:    65h    64h    FFh

00177538h:    CCh    4Ah    FFh

001775ECh:    C7h    26h    FFh

00177A10h:    08h    04h    FFh

00177AC5h:    49h    48h    FFh

001C8724h:    06h    0Fh    03h

Листинг 2 результат сравнения дампов памяти, снятых с программы. ячейки, предположительно содержащие патроны, выделены полужирным шрифтом

В глаза сразу же бросается стройная цепочка 32h, 31h, 30h, соответствующая следующим десятичным числам: 50, 49, 48. Ага! Оторвать мыщъх'у хвост если это количество патронов! Эта переменная встречается в дампе трижды по смещениям 00166574h, 001666B4h, 00168F28h. Одна из них настоящая, остальные — тени.


Как найти нужную?

Для начала переведем "сырые" файловые смещения в виртуальные адреса. Проще всего это сделать в помощью hiew'а (http://www.wasm.ru/baixado.php?mode=tool&id=112). Грузим dumped_1.exe, давим <F5> (goto), вводим "сырое" смещение 166574 и нажимаем <enter> — hiew тут же показывает в верхней строке соответствующий виртуальный адрес (PE.00568574), а значение байта под курсором равно 32h (см. рис. 5), значит, все правильно!



Рисунок 5 перевод сырых смещений в виртуальные адреса в hiew'e

Загружаем soft-ice (подопытная игрушка при этом уже должна быть запущена), нажимаем <Ctrl-D> и говорим "addr legacy", заставляя отладчик переключиться на контекст нужного процесса (в данном случае: legacy.exe – главного исполняемого файла игрушки). Вводим "wd", чтобы появилось окно дампа (если окно уже присутствует на экране, то вводить "wd" не надо) и пишем "g 568574", где 568574 — виртуальный адрес предполагаемой ячейки памяти с патронами. Отладчик показывает в дампе содержимое памяти. Команда "e" позволяет его редактировать в интерактивном режиме. Как вариант, можно написать "e 568574 66", где 66 – количество патронов в шестнадцатеричном исчислении.



Рисунок 6 soft-ice отличное средство для пополнения запаса патронов

Захачив предполагаемую ячейку с патронами выходим из отладчика по <Ctrl-D> и смотрим — добавили нам патронов или нет (в некоторых играх изменения отображаются только после следующего выстрела). Ни хрена! Патроны продолжают убывать, а враги напирают и долго мы так не продержимся! Сурово!

Пробуем вторую ячейку — 1666B4h, лежащую (как утверждает hiew) по виртуальному адресу 5686B4h, но количество патронов по прежнему остается неизменным. А вот на третий раз нам действительно везет и патроны послушно увеличиваются до нужного значения (см. рис. 7).


Следовательно, искомая переменная это 168F28h с виртуальным адресом 56AF28h. В любой момент мы можем вызывать отладчик, набрать "addr legacy <enter> e 56AF28 FF", пополняя запас патронов до максимального значения, вот только постоянно лазить в soft-ice слишком напряжно, да и не у всех он есть.



Рисунок 7 боезапас успешно пополнен!

Поступим проще — напишем программу, которая будет висеть в фоне и подкидывать нам новые патроны каждые несколько секунд или даже несколько раз в секунду. Вот это действительно "подарок свыше"! Исходный код настолько прост, что свободно укладывается в пару строк (остальное – объявление переменных и комментарии):

#define AMMO_ADDR    0x56AF28

#define AMMO_VALUE   66

#define AMMO_SIZE    1

main(int c, char** v)

{

       // объявляем переменные и проверяем аргументы командной строки

       int x; HANDLE h; unsigned int ammo = AMMO_VALUE; if (c < 2) return -1;

      

       // открываем

процесс

       if (!(h=OpenProcess(PROCESS_VM_WRITE | PROCESS_VM_OPERATION ,0,atol(v[1]))))

              return printf("-err:open process %d\n",atol(v[1]));

      

       // несколько раз в секунду пополняем запас патронов

       // 669 – задержка между обновлениями в миллисекундах

       while (WriteProcessMemory(h, AMMO_ADDR, &ammo, AMMO_SIZE, &x))Sleep(669);

}

Листинг 3 программа авто-патчер add_ammo_clip.c, висящая резидентом подкидывающая нам новые патроны

Программа принимает идентификатор процесса (PID) в качестве аргумента командной строки, который можно определить с помощью "Диспетчера задач" (см. рис. 8) или средствами toolhelp32 по имени исполняемого файла (к статье прилагается программа proc-list.cpp, показывающая как это сделать). После завершения игрушки, наш автопатчер завершается автоматически. Он так же может быть применен для хака других игрушек — необходимо лишь скорректировать AMMO_ADDR на адрес нужной ячейки, AMMO_VALUE – на желаемое значение, а AMMO_SIZE на размер переменной.





Рисунок 8 определение идентификатора процесса (PID) при помощи диспетчера задач

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



Рисунок 9 с автопатчером количество патронов застывает на отметке 66

Настоящие мыщъх'и правят машинный код программы так, чтобы патроны (жизни, здоровье) не убывали. Код, обращающийся к ячейке с патронами, можно найти двояко: по перекрестным ссылкам и через аппаратную точку останова. Первый способ ориентирован на дизассемблер и вообще говоря не очень надежен, второй — на отладчик и срабатывает в 100% если, конечно, программа дружит с soft-ice.

Начнем с перекрестных ссылок. Загружаем dumped_1.exe в IDA PRO и дождавшись, когда дизассемблирование завершится, переходим по адресу 56AF28h (<g>, "0x56AF28") и смотрим — удалось ли ей восстановить перекрестные ссылки или нет. Ага! Есть две перекрестные ссылки: одна на чтение, другая — на запись.

.data:0056AF28 dword_56AF28 dd 32h               ; DATA XREF: .text:004615AA^r

.data:0056AF28                                         ; .text:00461711^w

Листинг 4 IDA PRO нашла две перекрестных ссылки к ячейке с патронами одна из которых на запись (она выделена полужирным)

Переходим к той ссылке что на запись (в данном случае это .text:00461711h)и видим следующий код:

.text:00461707                    mov    dword_575BB8, 1

.text:00461711                    mov    dword_56AF28[ecx], eax

.text:00461717                    mov    dword_575BBC, eax

Листинг 5 код, обращающийся к "патронной" ячейке

Ни хрена не понятно что тут, зачем и куда! К тому же ячейка 56AF28h адресуется через регистр ECX, то есть используется в качестве базового указателя.


Вероятнее всего, мы имеем дело со структурой, описывающей состояние игрока и переменная с количеством патронов — член этой структуры. В этом случае перекрестные ссылки нам не помогают, но с другими играми они могут и сработать (кстати говоря, если нет IDA, можно воспользоваться hiew'ом и поискать ссылку на ячейку "прямым текстом", записав ее адрес "задом наоборот" с учетом порядка следования байт на x86, т.е. в данном случае искомая строка будет выглядеть так: "28 AF 56 00").

Переходим к точкам останова. Вызываем soft-ice привычным нажатием <Ctrl-D> (игра при этом уже должна быть запущена), переключаемся на нужный процесс командой "addr legacy", и ставим бряк на запись памяти: "bpm 56AF28 w", где 56AF28 — адрес ячейки с патронами. Если не указывать "w", soft-ice будет всплывать на чтении количества патронов, то есть постоянно (под VM Ware после нескольких всплытий виртуальная машина обрушивается. впрочем, это явный баг, который разработчики в обозримом будущем должны исправить).

После установки аппаратной точки останова на запись, soft-ice всплывает при первом же выстреле, отображая следующий код (см. рис. 10). Не обращайте внимание на гамму – отладчик не смог восстановить VGA-палитру, переопределенную игрушкой и текст едва можно разглядеть.



Рисунок 10 точка останова на "патронную" переменную сработала и soft-ice выдал машинную инструкцию, уменьшающую количество патронов

На всякий случай мыщъх (как порядочный хакер) приводит ключевой фрагмент листинга в "текстовом режиме", поскольку графика на бумаге наверняка будет не видна (издержки дешевой полиграфии, это вам не Финляндия, это гораздо хуже):

00442A91 8D B3 60 01 00 00        lea    esi, [ebx+160h]

00442A97 8B 0C 02                 mov    ecx, [edx+eax]

00442A9A 8B 94 8B C8 00 00 00            mov    edx, [ebx+ecx*4+0C8h]

00442AA1 4A                       dec    edx



00442AA2 8D 84 8B C8 00 00 00            lea    eax, [ebx+ecx*4+0C8h]

00442AA9 89 10                           mov    [eax], edx

00442AAB 8B 93 9C 00 00 00        mov    edx, [ebx+9Ch]

00442AB1 8B 83 B8 01 00 00        mov    eax, [ebx+1B8h]

00442AB7 C1 E2 05                 shl    edx, 5

00442ABA 8B 7C 02 1C              mov    edi, [edx+eax+1Ch]

00442ABE 85 FF                           test   edi, edi

00442AC0 74 4C                           jz     short loc_442B0E

Листинг 6 фрагмент кода игровой программы, отвечающий за уменьшение количества патронов (строка на которой всплыл soft-ice подчеркнута, инструкция, изменяющая значение "патронной" ячейки выделена полужирным)

Отладчик всплыл на команде "442AABh:mov edx,[ebx+9Ch]" (в вышеприведенном листинге она выделена подчеркиванием). Значит, к памяти обратилась предшествующая ей команда "442AA9h:mov [eax],edx", выделенная для наглядности полужирным шрифтом. Это архитектура у x86 процессоров такая — генерировать отладочное исключение после выполнения породившей его команды. Ладно, не будем высаживаться, лучше попробуем проанализировать код, насколько это возможно в данных условиях.

Сначала какое-то значение грузится в регистр EDX, считываясь по довольно сложной адресации [ebx+ecx*4+0C8h], затем EDX уменьшается на единицу командой DEC EDX, после чего обновленный EDX записывается в ту же самую ячейку памяти командами lea eax,[ebx+ecx*4+0C8h]/mov [eax],edx. Следовательно, ячейка [ebx+ecx*4+0C8h]

хранит количество патронов, а DEC — и есть та зараза, что коварно уменьшает их с каждым выстрелом на единицу (речь, естественно, идет про пистолет, остальное оружие расстается со своим боезарядом еще быстрее).

Замена DEC EDX

на NOP

заморозит патроны на неограниченный срок, а если приколоться и вместо DEC поставить INC, каждый выстрел будет добавлять нам один патрон. Вот так бонус! Правда, при достижении некоторой границы произойдет переполнение и патроны обратятся в нуль.


А, может быть, и не обратятся — все зависит от того насколько правильно спроектирована программа.

Хакнть DEC можно как в памяти, слегка модфицировав наш автопатчер, так и на диске — непосредственно в исполняемом файле, если он не упакован каким-нибудь хитрожопным навесным протектором, который не так-то просто снять. Наша гейма ничем таким не защищена и патчиться элементарно.

Создав резервную копию legacy.exe, загружаем его в hiew, переходим в дизассемблерный режим двойным нажатием ENTER'а, давим <F5> и вводим адрес команды DEC — ".442AA1" (точка указывает редактору, что это виртуальный адрес, а не смещение в файле). Нажимаем <F3> для разрешения редактирования и по ENTER'у заменяем DEC EDX на NOP или INC EDX — это уж кому как больше по вкусу. Сохраняем изменения по <F9> и выходим (при этом необходимо предварительно выйти из игры, т. к. Windows блокирует доступ к исполняющимся в файлам).



Рисунок 11 исправление DEC EDX на INC EDX _увеличивает_ патроны с каждым выстрелом

Запускаем игру и смотрим — все ли у нас получилось? А то! Получилось еще как! Теперь монстров можно отправлять на мясокомбинат. Мяса (и трупов) будет просто гора! Можно даже в соседний лес продавать (все равно мыщъх мяса не ест, ну разве шашлык там или колбасу).

Как вариант, можно не вводить адрес в hiew'е а просто поискать значения байтиков, равных в данном случае:89h 10h/8Bh 93h 9Ch 00h 00h 00h/8Bh 83h B8h 01h 00h 00h/C1h E2h 05h. К этому трюку обычно прибегают тогда, когда hiew недоступен, а под рукой есть только примитивный hex-редактор типа Hex Whorkshop, ни хрена не поддерживающий трансляции виртуальных адресов.

Аналогичным способом колются патроны к дробовику, BFG, количество жизней, проценты здоровья и т. д. Хачить игры — совсем не сложно! У опытных мыщъхей на это уходит всего лишь несколько минут. Кстати говоря, несмотря на то, что исходные тексты данной игры доступы, хануть программу отладчиком намного проще и быстрее, чем разбираться с сорцами.


Содержание раздела