Живём вечно - Взлом компьютерных игр своими руками

CUK77

Professional
Messages
1,193
Reputation
3
Reaction score
386
Points
83
Получить бессмертие и полный боекомплект практически в любой игре — это совсем несложно! Потребуется всего лишь hex-редактор и несколько минут свободного времени. Сегодня пробил час: Крис Касперски поделится с тобой древними алхимическими рецептами, дошедшими до нас со временен ZX-SPECTRUM и накопившими огромный потенциал.

Вечная жизнь

Что хорошего в вечной жизни? Да ничего в ней хорошего нет, если разобраться! Это же сплошные напряги и тоска смертная. Никакого тебе суицида, только бесконечные патроны. И сердце не екнет при случке с монстром, появляющимся как раз тогда, когда боезапас на исходе и здоровья нет ни хрена. Взломанная игра теряет свое очарование. Но все-таки без взлома никакое хорошее дело не обходится. Игра может содержать баг, делающий ее непроходимой (если в UFO 2 замучить живого таоста, то Cydonia/L'Tech в ресерче не появится никогда), или просто хочется погонять монстров, снять напряжение после тяжелого дня и не думать ни о здоровье, ни о патронах. Наконец взлом интересен сам по себе, с технической точки зрения.

Рецепт бессмертия

Рецепт бессмертия обычно представляет манускрипт со смещением ячейки, которую необходимо хакнуть, прописав сюда максимальное количество жизней или заменив инструкцию DEC на NOP. Как найти эту магическую позицию во многомегабайтной мешанине кода и данных? Некоторые скажут: "Взять дизассемблер и проанализировать программу", но… современные игры так велики, что этот проект даже не обсуждается. Пошлем таких советчиков подальше, а сами пойдем более разумным путем.

Общая тактика и стратегия

Как несложно догадаться, патроны, жизни, артефакты и прочее барахло — все это переменные, хранящиеся в определенных ячейках и всегда выражаемые числами. С точки зрения компьютера, эти ячейки ничем не отличаются от огромного множества остальных, содержащих в себе координаты монстров, текстуры и прочие объекты игрового мира. Как установить, за что отвечает та или иная ячейка? Самое простое, что приходит в голову — просто методично изменять одну ячейку за другой, наблюдая за реакцией игры, которой в этом случае, скорее всего, будет срыв крыши или повисание. Зная точное количество жизней/патронов, можно значительно сузить круг поиска, исследуя только те ячейки, которые содержат нужное значение. Однако следует помнить, что соответствие может быть как прямым, так и обратным. Одни программисты ведут учет жизней, другие — смертей, причем отсчет может вестись как от единицы, так и от нуля, а в некоторых случаях и -1. Допустим, у нас есть три нерастраченных жизни. Означает ли это, что переменная live_count обязательно будет равна трем? Разумеется, нет! В ней вполне может быть 2 (и тогда игра заканчивается, когда live_count < 0) или ноль (игра заканчивается при live_count > 2). Возможны и другие значения. С патронами в этом плане все обстоит намного лучше и чаще всего они хранятся в памяти «как есть», однако количество ложных срабатываний все равно будет очень велико! Допустим, у нас есть 50 патронов, и мы ищем 32h в дампе программы. Да там этих 32h целый миллион! До конца сезона все не переберешь! Ключ к решению лежит в изменениях! Наблюдая за характером изменения различных ячеек, мы легко отделим зерна от плевел. План наших действий выглядит так:

- снимаем с программы дамп (сохраняем состояние игры в файле)
- двигаемся без потери жизней и патронов, после чего снимаем ещё один дамп
- делаем один выстрел (теряем несколько процентов жизни) и получаем очередной дамп
- повторяем предыдущую операцию несколько раз (3-х дампов обычно достаточно)

Сравнение первого и второго дампов (сэйвов) показывает кучу отличий, соответствующих передвижениям монстров и прочим изменениям игрового мира. Но ни патронов, ни жизней в изменившихся ячейках не оказывается — ведь эти параметры заведомо не менялись! ОК, вычеркиваем изменившиеся ячейки из списка «подозреваемых» лиц и сравниваем второй дам с третьим, игнорируя ранее изменявшиеся ячейки. На этот раз отличий будет не так уж и много. Ищем ячейки, дельта изменения которых соответствует количеству выстрелов или потерянных жизней/здоровья. Если таких ячеек больше одной, повторяем операцию 3 до тех пор, пока не останется только одна изменившаяся ячейка или, как вариант, последовательно хачим все подходящие ячейки в надежде, что рано или поздно нам повезет. В некоторых играх количество патронов хранится в нескольких переменных, дублирующих друг друга, но только одна из них значима, а остальные на хакерском жаргоне называются «тенями», отвечающими, например, за вывод текущего значения на экран. При модификации «теневой» переменной количество патронов/жизней чаще всего остается неизменным, и даже если оно возрастает, оружие перестает стрелять задолго до исчерпания своего боезапаса, а нас все равно убивают. Сравнивать можно как дампы памяти, снятые с работающей программы, так и «сэйвы» — файлы сохранений. Зная адрес нужной ячейки, мы можем повесить резидента, прописывающего сюда максимально возможное значение и при необходимости обновляющего его каждые несколько секунд (или чаще). Еще можно запустить soft-ice и, установив точку останова, перехватить тот код, который уменьшает количество патронов с каждым выстрелом — тогда мы сможем его хакнуть. Но это потребует дополнительных телодвижений, что не всегда удобно, поэтому многие хакеры ограничиваются правкой сэйвов, выдавая игроку полный боезапас и максимум здоровья, однако подлинное бессмертие в этом случае уже не достигается — патроны и жизни продолжают убывать, и, чтобы не умереть, их приходится постоянно пополнять. Кроме того, некоторые монстры убивают наповал одной ракетой!

Хак в памяти

Начнем со сравнения дампов памяти. Выберем игру и будем над ней издеваться. Пусть это будет, например, DOOM Legacy – лучший порт классического DOOM'а, бесплатно распространяемый вместе с исходными текстами и замечательно работающий как под LINUX, так и под win32. На момент написания этих строк последней стабильной версий была версия 1.42. Для согласования наших действий рекомендуется использовать именно эту версию, иначе все смещения уползут неизвестно куда, но если ты себя чувствуешь достаточно подготовленным — попробуй потерзать более свежие беты, доступные с основной страницы проекта. Кроме DOOM Legacy, нам также потребуются wad-файлы из оригинального DOOM/DOOM2 или HERITC'а. Ну DOOM-то наверняка у каждого найдется! Берем любой приличный дампер, например PE Tools или LordPE, находим процесс legacy.exe и снимаем с работающей игрушки full dump, обзывая файл, к примеру, dump_1.exe. Условимся считать, что в этот момент у нас имеется 50 патронов.

После создания первого дампа побегай немного и задампись еще раз, получив файл 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, но отличаются у всех остальных. Переменные, отвечающие за хранение количества патронов будут где-то среди них. Для решения этой задачи мыщъх написал небольшую утилиту, исходный код и готовый бинарник которой можно найти на диске. Возьми с диска файл fck.exe и запусти утилиту:

fck dump_1.exe dump_2.exe dump_3.exe dump_4.exe>o

Полученный результат (перенаправленный в файл с именем «o») должен выглядеть примерно так:

Результат сравнения дампов:

raw offset d_2 d_3 d_4

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

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

В глаза сразу же бросается стройная цепочка 32h, 31h, 30h, соответствующая следующим десятичным числам: 50, 49, 48. Ага, это же количество патронов! Эта переменная встречается в дампе трижды по смещениям 00166574h, 001666B4h, 00168F28h. Одна из них настоящая, остальные — тени. Как найти нужную? Для начала переведем «сырые» файловые смещения в виртуальные адреса. Проще всего это сделать с помощью hiew. Грузим dumped_1.exe, давим <F5> (goto), вводим «сырое» смещение 166574 и нажимаем <enter> — hiew тут же показывает в верхней строке соответствующий виртуальный адрес (PE.00568574), а значение байта под курсором равно 32h, значит, всё правильно!

Берёмся за отладчик

Загружаем soft-ice (подопытная игрушка при этом уже должна быть запущена), нажимаем <Ctrl-D> и говорим «addr legacy», заставляя отладчик переключиться на контекст нужного процесса (в данном случае legacy.exe – главный исполняемый файл игрушки). Вводим «wd», чтобы появилось окно дампа и пишем «g 568574», где 568574 — виртуальный адрес предполагаемой ячейки памяти с патронами. Отладчик показывает в дампе содержимое памяти. Команда «e» позволяет его редактировать в интерактивном режиме. Как вариант, можно написать «e 568574 66», где 66 – количество патронов в шестнадцатеричном исчислении. Захачив предполагаемую ячейку с патронами, выходим из отладчика по <Ctrl-D> и смотрим, добавили нам патронов или нет (в некоторых играх изменения отображаются только после следующего выстрела). Ни хрена! Патроны продолжают убывать, а враги напирают, и долго мы так не продержимся! Сурово! Пробуем вторую ячейку — 1666B4h, лежащую, как утверждает hiew, по виртуальному адресу 5686B4h, но количество патронов по-прежнему остается неизменным. А вот на третий раз нам действительно везет, и патроны послушно увеличиваются до нужного значения. Следовательно, искомая переменная - это 168F28h с виртуальным адресом 56AF28h. В любой момент мы можем вызвать отладчик, набрать «addr legacy <enter> e 56AF28 FF», пополняя запас патронов до максимального значения, вот только постоянно лазить в soft-ice слишком напряжно, да и не у всех он есть. Мы поступим проще: напишем программу, которая будет висеть в бэкграунде и подкидывать нам новые патроны каждые несколько секунд, даже несколько раз в секунду. Вот это действительно «подарок свыше»! Программа очень проста, ее код легко укладывается в пару строк – в этом легко убедиться, посмотрев на соответствующую врезку. Программа принимает идентификатор процесса (PID) в качестве аргумента командной строки, который можно определить с помощью виндового «Диспетчера задач». После завершения игрушки, наш автопатчер завершается автоматически. Он также может быть применен для хака других игрушек — необходимо лишь скорректировать AMMO_ADDR на адрес нужной ячейки, AMMO_VALUE – на желаемое значение, а AMMO_SIZE - на размер переменной. Запускаем нашу утилиту и оттягиваем монстров по полной, то есть не по-детски. При быстрой стрельбе патроны слегка убывают, но тут же вновь восстанавливаются в исходное значение. Красота!

Хак на диске

Модификация игр в памяти — мощная штука, но все-таки несвободная от ограничений. Программы, защищенные различными протекторами (типа star-force), активно сопротивляются снятию дампа, а под linux нормальных дамперов вообще нет! В этих (и многих других) случаях приходится прибегать к альтернативному методу — правке файлов состояния игры ("сэйвов"). Тактическая стратегия выглядит как обычно: сохраняется в saved_1, перемещаемся без потери здоровья (патронов) и сохраняется в saved_2, затем стреляем один раз (тратим несколько процентов здоровья) и сохраняемся в saved_3. Сравниваем полученные файлы нашей утилитой fck.exe и смотрим различия. Выбрав наиболее вероятных "кандидатов", правим их в hex-редакторе, загружаем исправленный файл в игру и, если патроны/здоровье не изменились, правим следующий байт и т.д. Вернемся к DOOM'у. Подготавливаем три сэйва (doomsav0.dsg, doomsav1.dsg и doomsav2.dsg) и сравниваем их друг с другом. Опс! Все они имеют разный размер: 2440, 2586 и 2650 байт. Плохо дело! Судя по всему, сэйв имеет сложную структуру, и простое побайтовое сравнение, скорее всего, ничего не даст, поскольку ячейки, отвечающие за хранение патронов (здоровье), окажутся расположенными по различным смещениям. Расшифровка структуры сэйв-файлов — сложное, но очень увлекательное дело, "заразившее" множество светлых умов. Основным оружием становится интуиция и нечеткий "скользящий" поиск — мы ищем совпадающие (или просто похожие) фрагменты и корректируем смещения с привязкой к ним.

В частности, doomsavX.dsg имеет следующую структуру: сначала идет заголовок, содержащий имя сэйва, версию игры и прочую лабуду.

Заголовок сэйва:

0000000000: 32 00 F4 77 00 00 00 00 ? 00 00 00 00 00 00 00 00 2 Їw

0000000010: 2B 7E 9C F7 00 00 00 00 ? 76 65 72 73 69 6F 6E 20 +~Ьў version

0000000020: 31 34 32 00 00 00 00 00 ? 61 66 33 32 00 52 37 59 142 af32 R7Y

0000000030: 65 73 00 B3 19 31 00 8F ? 29 32 30 00 A8 43 4F 66 es ??1 П)20 иCOf

0000000040: 66 00 BE 9F 4E 6F 00 6E ? 77 59 65 73 00 C8 37 4E f ?ЯNo nwYes ?7N

За заголовком расположен какой-то бессистемный блок, всегда начинающийся со смещения 100h:

0000000100: 31 34 35 37 36 33 32 38 ? 0D 00 C8 00 02 00 64 00 14576328? ? ? d

0000000110: 00 00 90 01 28 00 90 01 ? 02 00 28 00 00 00 2C 01 Р?( Р?? ( ,?

0000000120: 64 00 00 00 07 00 00 00 ? 03 00 00 00 00 DC 05 01 d • ? ???

0000000130: 00 00 00 04 03 01 00 4C ? 00 20 00 00 00 00 00 00 ??? L

К бессистемному блоку примыкает характерная структура с косыми потоками рожиц. Ее смещение и размер различны и варьируются в очень широких пределах. Мы еще не знаем, за что она отвечает, но что бы здесь ни располагалось, вести сравнение нужно не с начала файла, а с начала этой самой структуры:

0000000140: FF 00 00 01 01 01 01 00 ? 01 01 01 08 00 01 1C 01 ???? ???? ???

0000000150: 0A 00 01 01 01 0B 00 01 ? 01 01 0C 00 01 01 01 10 ? ???? ???? ????

0000000160: 00 01 01 01 11 00 01 01 ? 01 12 00 01 01 01 13 00 ???? ???? ????

0000000170: 01 01 01 14 00 01 01 01 ? 15 00 01 4C 01 16 00 01 ???¶ ???§ ?L?? ?

Что значат косые потоки рожиц (знаков “?”)? И как определить это самое начало? Очень просто! Точно так, как астрономы определяют переменные и вспыхивающие звезды! Звездное небо не остается постоянным,и излучения некоторых звезд изменяют свои свойства со временем. Но как найти их среди тысяч других? Очень просто. Проецируем один звездный снимок на стену, затем удаляем его из проектора и вставляем другой, снятый чуть позже, добиваясь, чтобы звезды располагались на тех же самых местах, а теперь быстро-быстро меняем снимки один за другим. Переменные звезды начинают характерно мерцать! Используем эту технику для поиска отличающихся байт! Нам потребуется только FAR, все по-мужски.

Подгоняем курсор к doomsav0.dsg и давим <F3> (view), а затем - <F4> (hex-mode). Нажимаем <+> для перехода к следующему файлу (doomsav1.dsg), а затем - <-> для возвращения к предыдущему. Повторяем эму операцию несколько раз, убеждаясь, что косые потоки рожиц смещаются на значительные расстояния, поскольку начинаются с разных смещений. Нажимая <Alt-8> (goto) изменяем стартовое смещение файла doomsav1.dsg так, чтобы рожицы перестали прыгать. Словно мы крутим «синхронизацию» на осциллографе или накладываем отпечатки пальцев друг на друга, добиваясь наибольшего совпадения. В моем случае разница в базовом смещении «рожиц» составила 7 байт. То есть, чтобы они синхронизовались, один файл необходимо просматривать, начиная со смещения F0h, другой — с F7h. ОК! Рожицы совпадают, и никаких различий между ними не обнаруживается! За что же они отвечают? Возвращаемся в игру и, не сходя со своего места, убиваем одного монстра. Сохраняемся. Ага! Различий по-прежнему нет. Значит, «рожицы» отвечают не за трупы. Обращаем внимание, что по мере прохождения игры рожиц становится все больше и больше. Так, может быть, это и есть картографирование? Проверяем свою гипотезу. Действительно, стоит нам войти в новый сектор, как сразу же добавляется новая порция рожиц. Интересно, хранят ли они только карту или еще и состояния дверей? Это легко выяснить экспериментально! За рожицами начинается совсем другая структура данных, в которой на первый взгляд нет никакой закономерности и которая чудовищно изменяется между двумя соседними сохранениями. Логично предположить, что здесь сосредоточена святая святых — описание объектов игрового мира. Но как во всем этом разобраться?

Фрагмент структуры, описывающей состояние игрового мира:

0000000740: 00 50 05 00 00 30 0E 40 ? FF FF BF 01 00 00 00 00 P? 0?@ ??

0000000750: 03 26 00 00 68 2E F1 00 ? 00 00 08 00 00 00 08 00 ?& h.е ? ?

0000000760: 09 00 00 00 30 07 00 00 ? 30 0E 40 FF FF BF 38 03 ? 0• 0?@ ?8?

0000000770: 04 00 00 00 01 00 00 00 ? 00 03 20 00 00 34 2F F1 ? ? ? 4/е

0000000780: 00 00 00 08 00 00 00 08 ? 00 0A 00 00 00 E0 06 00 ? ? ? р?

0000000790: 00 50 0D 40 FF FF BF 01 ? 00 00 00 00 03 24 04 00 P?@ ?? ?$?

00000007A0: 00 30 F1 00 00 00 70 00 ? 00 00 70 00 0B 00 00 00 0е p p ?

При внимательном осмотре дампа мы обнаружим, что константа FFFFh встречается намного чаще, чем остальные. Это ключ к понимаю структуры файла, но… где тот замок, куда его вставить? Смотрим. Константы расположены на различном расстоянии друг от друга, значит, мы имеем дело со структурой переменного размера или со списком, завершаемым «терминирующим» символом FFFFh. Если это структура, то где-то должен храниться ее размер, выражаемый в байтах, словах или двойных словах. Как найти его в дампе? Возьмем две ближайших константы, расположенные по смещению (748h и 76Bh). Как нетрудно подсчитать, их разделяет 23h байта. Следовательно, размер структуры не может выражаться ни словами, ни двойными словами (23h не кратно двум), а только байтам. Ищем число 23h в окрестности наших констант. Его нет! Поэтому можно предположить, что FFFFh используется в качестве терминирующего символа, то есть служит знаком конца списка. Остается только написать программу, отображающую содержимое списков в удобочитаемом виде, тогда искать различия будет намного проще. Однако это довольно сложная задача, решение которой требует уймы времени и терпения. Зато потом мы сможем «убивать» любых монстров или добавлять новых, подкладывать аптечки и другие артефакты, словом, творить чудеса, но это будет потом. Сейчас же мы ограничимся тем, что пополним запас патронов, здоровья и брони, а также дадим герою все оружие, из которого лично я предпочитаю совсем не BFG, а обыкновенный дробовик, причем одностволку! Вместо сравнения сэйвов, мы используем альтернативный подход, называемый «прямым константным поиском». Допустим, у нас сложилась следующая ситуация: здоровье — 68%, броня — 95%, патроны — 73 (200 max), патроны для дробовика — 24 (50 max). Переводим 68 и 95 в шестнадцатеричную систему исчисления и получаем 44h и 5Fh. Отгружаем игру в doomsav.dsg и загружаем этот файл в hiew. Давим <F7> (search) и ищем 44h, где оно есть (внимание! При поиске чисел > 255 необходимо помнить, что младший байт располагается по меньшему адресу и поэтому ищется в обратном порядке, то есть для поиска 1234h в hiew'е необходимо ввести 34 12). Ячейка с искомым значением тут же обнаруживается по смещению B4h. Может, это и не здоровье, но… рядом с ней лежит 5Fh, а это, как мы помним, наша броня:

Фрагмент сэйв-файла с ячейками, хранящими здоровье и броню:

000000B0: 00 00 00 00-44 00 5F 00-01 00 01 0A-00 00 00 00 D _ ? ??

000000C0: 00 00 00 00-00 00 00 00-00 00 00 00-00 00 00 00

000000D0: 00 00 00 00-00 00 00 00-00 00 00 00-00 00 00 00

000000E0: 00 00 00 00-00 00 00 00-00 00 00 00-00 00 00 00

000000F0: 00 00 00 00-00 00 00 00-00 00 00 00-00 00 00 30 0

Исправляем оба числа на FFFFh и загружаем исправленный сэйв в игру. Нетрудно заметить, что у нас все получилось :).

Заключение

Приобретенные навыки оказываются очень полезными при расшифровке сетевых протоколов и реконструкции недокументированных форматов файлов и файловых систем. Квалифицированных специалистов мало, поэтому на них всегда присутствует устойчивый спрос, так что бессмертие в играх — это совсем не забава! Это очень и очень серьезно! Многие выдающиеся хакеры начинали именно с бессмертия. Осваивали hex-редактор, терзали отладчик, понемногу изучали ассемблер и постепенно, шаг за шагом двигались к тому, кем они стали сейчас. В общем, ты меня понял. Вливайся!

Насколько законно патчить игры?

Мне часто приходится слышать, что модификация игр незаконна и хачить можно только сэйвы. В корне неверное утверждение! По российскому законодательству, на своем компьютере с программой можно делать всё, что угодно, в том числе и заменять DEC на NOP. А вот распространять хакнутый файл нельзя, что логично и правильно.

автопатчер add_ammo_clip.c
#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:eek:pen process %d\n",atol(v[1]));

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

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

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

}
 
Top