for-ever

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

крис касперски ака мыщъх, no-email

когда человек тратит столько времени и сил на дорогу, и наконец доходит, он уже не может себе позволить увидеть все таким, как оно есть на самом деле…

Пелевин, «Принц Госплана»

получить бессмертие и полный боекомплект практически в любой игре — это реально! потребуется всего лишь hex-редактор и несколько минут свободного времени. мыщъх делится древними алхимическими рецептами, дошедшими до нас со временен ZX-SPECTRUM и накопившими огромный потенциал. хвост так и чешется его реализовать!

Что хорошего в вечной жизни? Да ничего в ней хорошего нет, если разобраться! Это же сплошные напряги и тоска смертная. Никакого тебе суицида, только бесконечные патроны. И сердце не екнет при случке с монстром, появляющимся как раз тогда, когда боезапас на исходе и здоровья нет ни хрена. Взломанная игра теряет свое очарование. Азарт — исчезает, а чувство глубокого удовлетворения от прохождения последнего уровня рассеивается как утренний туман. Мыщъх в этом случае говорит: «нельзя все ломать, на чем-то нужно и сидеть».

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

Рецепт бессмертия обычно представляет манускрипт со смещением ячейки, которую необходимо хакнуть, прописав сюда максимальное количество жизней или заменив инструкцию DEC на NOP. Как найти эту магическую позицию во многомегабайтной мешанине кода и данных? Некоторые скажут — взять дизассемблер и проанализировать программу, но… современные игры так велики, что этот проект даже не обсуждается. Пошлем советчиков в жопу и пойдем кратчайшим путем, который только знает мыщъх: поиск различий между дампами на диске и в памяти. Во времена MS-DOS существовало множество программ для автоматизации взлома игр (gametools, gamewizard), но под Windows ничего подобного так и не появилось!

Мыщъх покажет как взламывать игры вручную, работая только хвостом и еще головой (голова конечно лучше хвоста, но с одной головой жить хреново). Эта техника одинаково хорошо работает как под Windows, так и под UNIX, открывая неограниченные перспективы. Утопите монстров в крови, поджарьте их на медленном огне!

for-ever_image_0.jpg

Рисунок 1 в поисках вечной жизни

─ [10] ─ RU.HACKER (2:5060/9.18) ─────────────────────────────────── RU.HACKER ─

Msg : 53 из 560

.т : Roman Lesnichenko 2:465/666 .он 13 .оя 95 01:49

.ому : Eugene Muzychenko

Subj : хакеpы

────────────────────────────────────────────────────────────────────────────────

Hello Eugene!

Friday November 10 1995 00:42, Eugene Muzychenko wrote to Andrey Fedotov:

AF» Ну чего сложно объяснить как каой-нить гейм на жизни поломать?

AF» Он же не просит тебя объяснить как банковскую сеть сломать.

EM> Блин, откуда это постоянное «ломать» и при чем тут хакеры? :-/

Вот именно! Хакерство— это не взлом! Это образ жизни! Союз души, головы и хвоста!

Я, например, впервые сев за ИБМ совместимую тачку ЕС1840 ;) в далеком 89 году, уже

через месяц с помощью системного Дебага наклацал тонну всяких BUSHIDO, CATS,

ZAXON, PACMAN…etc и хакером себя не считаю, хоть и успел нага… натворить ;-)

Просто хотел узнать что такое Ассемблер. Вот и все. И не было ни Интрудеров,

ни ГамеТулсов, ни АвтоХаков ни других модных импортных штучек :)

Меня удивляет, что при теперешней ситуации с литературой (можно купить все что

хочешь), с софтом (практически любые отладчики на выбор и на любителя) люди

осмеливаются просить научить их ломать игрушки, причем БУДУЧИ В ФИДО!

Я это к тому, что все же надеюсь, что сюда просто так с улицы не приходят,

и должны иметь хоть какие-то знания.

У нас на работе охранник, бывший мент, увидевший комп полгода назад,

сломал на хрен UFO! Причем без знаний чего либо в компьютерах вообще!

Просто ему показали как сравнивать файлы и все! И дали HEX-editor.

Купил чего-то, записался, сравнил файлы, подправил и вперед.

И ни кого ни о чем не спрашивал!

Сейчас он уже себе комп собрал почти. Собирается программизму учиться.

Просто ПРЕТ человека, и все этим сказано. И я уверен, что он дойдет до всего САМ!

Я имею ввиду основные идеи и тенденции.

EM> интересно, как может «затянуть» описание метода взлома? Hy напишу я:

EM> «набери DEBUG, набери там U xxxx:xxxx, найди в тексте строчку,

EM> посмотри, что там в квадратных скобках…». Как тупое описание

EM> без понимания _сути_ может что-то стимулировать? стимулировать

EM> может только сильное собственное желание, помноженное на любовь

EM> к анализу…

См. выше :-)

Roman

Патроны, жизни, артефакты и прочее барахло — все это переменные, хранящиеся в определенных ячейках и всегда выражаемые числом. С точки зрения компьютера, эти ячейки ничем не отличаются от огромного множества остальных, содержащих в себе координаты монстров, текстуры и прочие объекты игрового мира. Как установить за что отвечает та или иная ячейка? Неэффективность дизассемблирования мы уже отмечали, но… что если просто методично изменять одну ячейку за другой, наблюдая за реакцией игры, которой скорее всего будет срыв крыши или повисание.

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

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

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

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

В некоторых играх количество патронов хранится в нескольких переменных, дублирующих друг друга, но только одна из них значима, а остальные на хакерском жаргоне называются «тенями», отвечающими, например, за вывод текущего значения на экран. При модификации «теневой» переменной количество патронов/жизней чаще всего остается неизменным, и даже если оно возрастает, оружие перестает стрелять задолго до исчерпания своего «боезапаса», а нас все равно убивают. Ну не уроды?!

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

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

На этом ноте мы заканчиваем с теорией и переходим к практике.

типминимальное значениемаксимальное значение
unsigned byte0255
signed byte-128127
unsigned word065.535
signed word-32.76832.767
unsigned dword04.294.967.295
signed dword -2.147.483.6482.147.483.647

Таблица 1 основные типы данных и соответствующий им диапазон значений

Рисунок 2 главная страница open-source проекта DOOM legacy, портироващего бессмертную игрушку на win32/linux

хак в памяти

Начнем со сравнения дампов памяти. Выберем игру и будем над ней издеваться. Пусть это будет например, DOOM Legacy – лучший порт классического 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_DUMPS0x10 макс. кол-во поддерживаемых дампов 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 1) 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 для сравнения дампов Компилируем программу любым Си-компилятором (например, в случае MicrosoftVisualC++ командная строка будет выглядеть так: «cl.exe /Oxfck.c») и запускаем свежеиспеченный файл на выполнение: «fck dump_1.exedump_2.exedump_3.exedump_4.exe>o». Полученный результат (перенаправленный в файл «o») должен выглядеть так: raw offsetdumped_2.exedumped_3.exedumped_4.exe 000A2FB0h:B2h2BhB6h 000A2FB6h:C6h00h63h 000A2FC0h:40h00h01h 000A2FC2h:AEh00h46h 000A2FC8h:16h00h65h 000A2FCAh:18h00h56h 000A2FCCh:60h00h65h 000A2FCEh:15h00h72h 000A2FD0h:EEh00h73h 000A2FD2h:EEh00h69h 000A2FD4h:C0h00h6Fh 000A2FDCh:5Ah00h31h 000A2FEAh:DCh00h36h 000A2FEDh:80h00h0Bh 000A2FEEh:2Ah00h15h 000A2FF0h:08h00hEEh 000A2FF1h:1Ch00hF1h 000A2FF6h:59h00h06h 000A2FFAh:02h00h49h 000A2FFCh:FCh00h6Eh 000CFBA0h:FEhFDhFFh 00152262h:A1h60h00h 001523E2h:A1h60h00h 00166574h:32h31h30h 001666B4h:32h31h30h 00168F28h:32h31h30h 001772A5h:65h64hFFh 00177538h:CCh4AhFFh 001775ECh:C7h26hFFh 00177A10h:08h04hFFh 00177AC5h:49h48hFFh 001C8724h:06h0Fh03h Листинг 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_ADDR0x56AF28 #define AMMO_VALUE66 #define AMMO_SIZE1 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_56AF28dd 32h; DATA XREF: .text:004615AA↑r .data:0056AF28; .text:00461711↑w Листинг 4 IDA PRO нашла две перекрестных ссылки к ячейке с патронами одна из которых на запись (она выделена полужирным) Переходим к той ссылке что на запись (в данном случае это .text:00461711h)и видим следующий код: .text:00461707movdword_575BB8, 1 .text:00461711movdword_56AF28[ecx], eax .text:00461717movdword_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 00leaesi, [ebx+160h] 00442A97 8B 0C 02movecx, [edx+eax] 00442A9A 8B 94 8B C8 00 00 00movedx, [ebx+ecx*4+0C8h] 00442AA1 4Adecedx 00442AA2 8D 84 8B C8 00 00 00leaeax, [ebx+ecx*4+0C8h] 00442AA9 89 10mov[eax], edx 00442AAB 8B 93 9C 00 00 00movedx, [ebx+9Ch] 00442AB1 8B 83 B8 01 00 00moveax, [ebx+1B8h] 00442AB7 C1 E2 05shledx, 5 00442ABA 8B 7C 02 1Cmovedi, [edx+eax+1Ch] 00442ABE 85 FFtestedi, edi 00442AC0 74 4Cjzshort 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 83hB8h 01h 00h 00h/C1hE2h 05h. К этому трюку обычно прибегают тогда, когда hiew недоступен, а под рукой есть только примитивный hex-редактор типа HexWhorkshop, ни хрена не поддерживающий трансляции виртуальных адресов. Аналогичным способом колются патроны к дробовику, BFG, количество жизней, проценты здоровья и т. д. Хачить игры — совсем не сложно! У опытных мыщъхей на это уходит всего лишь несколько минут. Кстати говоря, несмотря на то, что исходные тексты данной игры доступы, хануть программу отладчиком намного проще и быстрее, чем разбираться с сорцами. ===== »> врезка что находится в куче ===== Большинство игрушек хранят свои переменные в секции данных, которую все дамперы дампят по умолчанию. Некоторые программы используют для этой цели динамическую память, называемую кучей (heap), что значительно хуже. Если в снятом дампе ничего похожего на здоровье не обнаружено, необходимо сдампить не только образ файла (image), но еще и кучу. В LordPE это делается так: в контекстом меню выбирается пункт «dump region» и в открывшемся списке регионов выделяются все блоки типа COMMIT/PRIVATE с атрибутами доступа на чтение и запись. Иногда переменные находится в самом большом блоке, иногда —нет. Короче, тут без эксперимента ничего конкретного не скажешь! Рисунок 12 кладем кучу в дамп ==== хак на диске ==== Модификация игр в памяти — мощная штука, но все-таки не свободная от ограничений. Программы, защищенные различными протекторами (типа 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 Листинг 7 заголовок сэйва За заголовком расположен какое-то бессистемный блок, всегда начинающийся со смещения 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 Листинг 8 «бессистемный» блок К бессистемному блоку примыкает характерная структура с косыми потоками рожиц. Ее смещение и размер различны и варьируются в очень широких пределах. Мы еще не знаем, за что она отвечает, но что бы здесь ни располагалось, вести сравнение нужно не с начала файла, а с начала этой самой структуры: 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☺■ ☺ Листинг 9 косые потоки рожиц — что они значат? А как определить это самое начало? Очень просто — точно так, как астрономы определяют переменные и вспыхивающие звезды! Звездное небо не остается постоянным и некоторые звезды изменяют свой блеск (иногда — значительно). Но как найти их среди тысяч других? Очень просто — проецируем один звездный снимок на стену, затем удаляем его из проектора и вставляем другой, снятый чуть позже, добавясь, чтобы звезды располагались на тех же самых местах, а теперь быстро-быстро меняем снимки один за другим. Переменные звезды начинают характерно мерцать! Используем эту технику для поиска отличающихся байт! Нам потребуется только наш верный хвост и FAR. Подгоняем курсор к doomsav0.dsg и давим <F3> (view), а затем <F4> (hex-mode). Нажимаем <+> для перехода к следующему файлу (doomsav1.dsg), а затем ↔ для возвращения к предыдущему. Повторяем эму операцию несколько раз, убеждаясь, что косые потоки рожиц смещаются на значительные расстояния, поскольку начинаются с разных смещений. Нажимая <Alt-8> (goto) изменяем стартовое смещение файла doomsav1.dsg так, чтобы рожицы перестали прыгать. Словно мы крутим «синхронизацию» на осциллографе или накладываем отпечатки пальцев друг на друга, добиваясь наибольшего совпадения. В моем случае, разница в базовом смещении «рожиц» составила 7 байт. То есть, чтобы они засинхронизовались один файл необходимо просматривать начиная со смещения F0h, другой — F7h. ОК! Рожицы совпадают и никаких различий между ними не обнаруживается! За что же они отвечают? Рисунок 13 синхронизация «рожиц» в FAR'е Рисунок 14 быстро переключаясь между файлами по <+>/↔ ищем изменившиеся ячейки Возвращаемся в игру и, не сходя со своего места, убиваем одного монстра. Сохраняемся. Ага! Различий по прежнему нет. Значит, «рожицы» отвечают не за трупы. Обращаем внимание, что по мере прохождения игры рожиц становится все больше и больше. Так, может быть, это и есть картографирование? Проверяем свою гипотезу и действительно — стоит нам войти в новый сектор, как сразу же добавляется новая порция рожиц. Интересно: хранят ли они только карту или еще и состояния дверей? Это легко выяснить экспериментально! За рожицами начинается соврем другая структура данных, в которой на первый взгляд нет никакой закономерности и которая чудовищно изменяется между двумя соседними сохранениями. Логично предположить, что здесь сосредоточена святая святых — описание объектов игрового мира. Но как во всем этом разобраться? 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 ♂ Листинг 10 фрагмент структуры, описывающей состояние игрового мира При внимательном осмотре дампа мы обнаружим, что константа 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 Листинг 11 фрагмент сэйв-файла, с ячейками хранящими здоровье (подчеркнуто) и броню (выделено полужирным) Исправляем оба числа на FFFFh и загружаем исправленный сэйв в игру. Держите мой хвост! У нас получилось!!!! Рисунок 15 c такой броней и здоровьем никакие враги не страшны! Теперь разберемся с оружием и патронами. Добавить патроны — несложно, но прежде необходимо заполучить оружие. Обычно оно хранится в игре в переменных-флагов: одно значение соответствует состоянию «оружие есть», другое — «оружия нет». Флаги бывают битовыми, байтовыми, словными и двухсловными. Битовый флаг хранит все оружие в одной ячейке. Например, 01h (0000000001b) означает пистолет, 02h (0000000010b) – дробовик, а 03h (0000000011b) — пистолет с винчестером. Ладно, завязываем с теорией и переходим к активным действиям. Рисунок 16 бензопила — не самое мощное оружие, но чуть позже с ее помощью мы завладеем BFG Возвращаемся в DOOM. На самый первый уровень. Выходим на балкон и видим пилу. В смысле бензопилу. Сохраняемся, затем не совершая никаких движений сохраняемся еще. Берем пилу и сохраняемся в третий раз (см. рис. 16). Мы получаем следующие файлы: doomsav0.dsg, doomsav1.dsg и doomsav2.dsg. Натравливаем на них fck.exe и видим результат: raw offsetdoomsav1.dsgdoomsav2.dsg 00000008h:CEh35h 00000009h:DDhCFh 0000000Ah:1Ch22h 0000000Ch:E8h15h 0000000Dh:2ChB1h 0000000Eh:D9h98h 00000014h:51h54h 000000BAh:01h07h 00000121h:0Bh44h 00000123h:01h02h 00000127h:00h47h 00000128h:00hFDh 00000129h:01h00h 0000012Bh:00h20h 0000012Ch:00h01h 00000135h:00h47h 00000136h:00hFDh 00000137h:01h00h 00000139h:00h20h 0000013Ah:00h01h 0000013Dh:03h83h 0000028Ah:24h26h 000002A9h:00h08h 000002ABh:01h00h Листинг 12 до и после взятия бензопилы («подозреваемая» ячейка выделена полужирным) Да тут до фига изменений! Даже не сообразишь с чего начинать… Будем рассуждать логически. Если оружие храниться с байтовом флаге, то он скачкообразно поменяет свое значение с FALSE (ноль) на TRUE (обычно 1 или FFh) или наоборот. Если это битовый флаг, то после захвата бензопилы, нужная нам переменная будет отличаться всего одним битом. Начнем с ячейки 12Ch – она поменяла свое значение с 00h на 01h. Допустим, этот флаг хранит бензопилу. Тогда логично предположить, что остальное оружие хранится где-то рядом (хотя это бывает и не всегда), однако, соседние с не ячейки меняют свое значение в сумбурно-хаотичном порядке и на флаги не тянут. А вот ячейка 13Dh очень даже похожа! Смотрите — сначала в ней было 03h (00000011b), а после взятия бензопилы — 83h (10000011b). Скорее всего, первый справа бит хранит оружие типа «кастет», второй — пистолет, а последний — пилу. Чтобы проверить наше предположение необходимо добыть еще одно оружие, например, дробовик и посмотреть. raw offsetdoomsav1.dsgdoomsav2.dsg 00000000h:32h33h 000000A8h:05h06h 000000BAh:01h02h 0000010Ch:00h10h 00000129h:0Bh13h 00000145h:83h87h 00000642h:00h01h 0000064Ch:00h34h Листинг 13 до и после взятия дробовика («подозреваемая» ячейка выделена полужирным) Вот черт! Ячейка по смещению 13Dh ушла в туман, но зато ячейка по смещению 145h изменилась с 83h (10000011b) на 87h (10000111b), то есть после взятия дробовика добавился третий бит. Это явно она! Именно здесь храниться все оружие, которое добыл игрок. Вот только ее смещение непостоянно и в разных файлах оно сильно неодинаковое… но мы-то знаем, что в doomsav1.dsg/doomsav2.dsg оно гарантировано равно 145h! Загружаем doomsav2.dsg в hiew и меняем 87h на FFh (все оружие). Открываем исправленный сэйв и смотрим что из оружие у нас есть. У нас есть все! Вот только патронов не хватает, а что толку от оружия, если оно не стреляет? Приходится хачить! Как мы помним, у нас есть 73/200 пистолетных патрона и 24/50 патронов для дробовика. Переводим эти числа в hex-форму — 49h/C8h, 18h/32h и смотрим где они в дампе: 00000100: 31 34 35 37-36 33 32 38-49 00C8 00-18 0032 00 14576328; E ► 2 00000110: 00 00 2C 01-00 00 32 00-00 00 00 00-00 00 00 00 ,☺ 2 00000120: 05 00 00 00-02 00 00 00-00 13 00 01-00 00 00 00 ♣ ☻ ‼ ☺ 00000130: 00 01 00 00-00 20 00 00-00 00 00 00-00 00 00 01 ☺ ☺ Листинг 14 ячейки, ответственные за хранение пистолетных патронов (текущее/максимальное количество) и зарядов для дробовика Ага! Лежат рядышком! А следом за ними идет 00h 00h 2Ch 01h. Ничего не напоминает? С учетом обратного порядка байт 2Ch 01h равно 12Ch или 300 в десятичном исчислении — максимально возможное количество зарядов плазмогана/BFG на данном уровне. А два нуля — это количество самих зарядов. Тогда последовательности 00h 00h 32h 00h ничего не остается как отвечать за число ракет (00 ракет в наличии, 50 — максимум). Меняем все эти байты на FFh и получаем максимальный боезапас: 00000100: 31 34 35 37-36 33 32 38-FF FF FF FF-FF FF FF FF 14576328yyyyyyyy 00000110: FF FF FF FF-FF FF FF FF-00 00 00 00-00 00 00 00 yyyyyyyy 00000120: 05 00 00 00-02 00 00 00-00 13 00 01-00 00 00 00 ♣ ☻ ‼ ☺ 00000130: 00 01 00 00-00 20 00 00-00 00 00 00-00 00 00 01 ☺ ☺ Листинг 15 фрагмент исправленного сэйва, дающий все оружие с кучей патронов Загружаем исправленный сэйв в DOOM, нажимаем <7> для выбора BFG и встаем на тропу войны. Рисунок 17 мыщъх на тропе войны Остальные игрушки ломаются схожим образом. Вот, например, UFO, которое кладет все сейвы в каталоги GAME_X, причем кладет их очень много! Почти полсотни файлов! К счастью, утилита fc.exe из штатной поставки Windows, поддерживает джокеры и позволяет сравнивать множество файлов сразу: «fc.exe /bGAME_X\* GAME_Y\* > log», избавляя нас от необходимости делать это вручную. Утилита fck.exe джокеров не поддерживает (кто возьмется ее доработать?), но можно поступить проще, воспользовавшись простым пакетным файлов следующего содержания: for A IN (*) DO fck.exe game_1\A game_2\A game_3\A » log Листинг 16 командный файл, позволяющей утилите fck обрабатывать множество файлов Сравнение сэйвов показывает, что при добавлении новых исследований в проект, один или несколько байтов в research.dat устанавливаются в единицу. Наблюдая за изменениями легко установить, что файл имеет блочную структуру и размер блока равен 16h байтам, а флаг, «разрешающий» исследовательский проект находится по смещению 08h от начала блока. Приведенная ниже программа открывает все исследования, в том числе и Cydonia/L'Tech без которого игра не может закончится, зачастую становясь непроходимой. #define MAX_BUF_SIZE(10*1024) main(int x, char argv) { int a,t,c, x=0; FILE *fsave, *fback; unsigned char gamebuf[MAX_BUF_SIZE]; unsigned char fn_save[MAX_BUF_SIZE]; unsigned char fn_back[MAX_BUF_SIZE]; #define BLOCK_SIZE0x16 #define off_ENABLE_RESEARCH8 #define FN_SAVE «\\research.dat» define FN_BACK «\\research.da_» if (x<2)return printf(«USAGE: ufo1-2AlienContainmentBugFix.exe path_to_save\n»\ «example: ufo1-2AlienContainmentBugFix.exe GAME_1\n»); printf(«* * * ENABLING Cydonia or L'Tech City in RESEARCH LIST * * *\n»); strcpy(fn_save, argv[1]); strcat(fn_save, FN_SAVE); strcpy(fn_back, argv[1]); strcat(fn_back, FN_BACK); делаем резервную копию fsave = fopen(fn_save,«rb»); c = fread(gamebuf, 1, MAX_BUF_SIZE, fsave); fback = fopen(fn_back, «wb»);fwrite(gamebuf,1,c, fback); добавляем Cydonia/L'Tech в исследовательский проект for (a = 0; a < c; a += BLOCK_SIZE) gamebuf[a + off_ENABLE_RESEARCH] = 1; сохраняем изменения в файле fclose(fsave); fsave = fopen(fn_save,«wb»); fwrite(gamebuf, 1, c, fsave); printf(«============================================================\n»\ «now see BASES > RESEARCH > Add New Project > Cydonia/L'Tech\n»); } Листинг 17 программа, дающая Cydonia/L'Tech в UFO-2 ===== »> врезка баг в UFO и его последствия ===== ─ SU.GAME.STRATEGY (2:5060/8.13) ──────────────────────────── SU.GAME.STRATEGY ─ Msg : 5 of 694 From : Alexander Volkov 2:5020/168.7 Срд 04 Июн 97 11:59 To : Kostya Shapovalov Пнд 16 Июн 97 14:58 Subj : UFO II ──────────────────────────────────────────────────────────────────────────────── Hello Kostya! Thursday May 22 1997 09:06, Kostya Shapovalov wrote to All: KS> Левиафан чертов!!!!!!! Не хочет изобретаться, хоть убей! Я уже две базы KS> загpохал, брал Commander'ов в рюкзаки и уматывал - не появляются! В смысле берешь в рюкзак живого лобстеp-командеpа, эвакуируешься с колонии - а командеp на твоей базе не появляется? странно… KS> Alien Contaiment y меня пустая (продал и от'transfer'ил всех). KS> А без Левиафана T'Leth не проявляется, сволочь! KS> Может, расу определенную надо?! Лобстеp-командеpа. В непатченной, правда, был глюк - если сначала замучать тасот-командеpа, то левиафана в pесече не будет никогда. ===== »> врезка насколько законно патчить игры ===== Мыщъху часто приходится слышать, что модификация игр незаконна и хачить можно только сэйвы. В корне неверное утверждение! По российскому законодательству, на своем компьютере с программой можно делать все, что угодно, в том числе и заменять DEC на NOP. А вот распространять хакнутый файл без согласия правообладателя — нельзя, что логично и правильно. ===== заключение ===== Форматы файлов-сэйвов многих игрушек уже давно исследованы и могут быть найдены в сети. На их основе создаются хакерские редакторы и прочие читерские средства, имеющие хождение в народе. За ними стоит огромный объем исследовательской работы и далеко не всегда тривиального сравнения оказывается достаточно. Без помощи отладчика и дизассемблера здесь не обойтись! Все правильно: геймеры — играют, хакеры — ковыряются в двоичных файлах, отлаживают, дизассемблируют, в общем хакерстуют. Приобретенные навыки оказываются очень полезны при расшифровке сетевых протоколов и реконструкции недокументированных форматов файлов и файловых систем. Квалифицированных специалистов единицы на них всегда присутствует устойчивый спрос, так что бессмертие в играх — это совсем не забава! Это очень и очень серьезно! Многие выдающиеся хакеры начинали именно с бессмертия. Осваивали hex-редактор, терзали отладчик, понемногу изучали ассемблер и вот так постепенно шаг за шагом двигались к тому кем они стали сейчас. В общем, вы меня поняли. Вливайтесь! Рисунок 18

1)
a-b) && (ch[a]==ch[b]