Хак в памяти
Начнем со сравнения дампов памяти. Выберем игру и будем над ней издеваться. Пусть это будет например, 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, количество жизней, проценты здоровья и т. д. Хачить игры — совсем не сложно! У опытных мыщъхей на это уходит всего лишь несколько минут. Кстати говоря, несмотря на то, что исходные тексты данной игры доступы, хануть программу отладчиком намного проще и быстрее, чем разбираться с сорцами.