Pull to refresh

Кидхак Prehistorik 2: анлочим уровни

Reading time 8 min
Views 15K

Любителям этой замечательной игры посвящается...


image
… Если таковые еще есть. Впрочем, я уверен, что не все так плохо и Crysis не поглотил мозг человечества целиком и окончательно.
Так вот, вышеупомянутые любители старых игр и в частности одной замечательной игры 92 года могут помнить, что система сохранений в игре сделана в несколько приставочном духе: по уровням разбросаны (иногда в довольно неожиданных и труднодоступных местах) коды, которые во времена MS-DOS старательно записывались на бумажечку и хранились, как сокровище. Код, введенный в главном меню, позволял начать соответствующий уровень со стартовой позиции.
Soshite, в наше время появилась, в некотором роде, проблема для тех, кто хотел бы поиграть в Prehistorik 2. А именно — необходимость эмулировать игру в Dosbox: далеко не у всех есть возможность найти на свалке или, что еще гораздо реже, вытащить с антресолей заботливо хранящийся там 486-й.
Дело в том, что при генерации кодов игра использует переменные окружения BIOS. Как это эмулируется в Dosbox, я не разбирался, знаю достоверно, что при каждом новом запуске эмулятора коды в игре другие. Соответственно, бумажка с кодами становится бесполезной, а возможность приятного и ненапрягающего прохождения игры сводится практически на нет: полностью игру можно пройти только за один присест. Возможно, это решается детальной настройкой Dosbox. Но это менее интересно, чем небольшое копание в исполняемом файле игры.

Постановка задачи


image
Реализовать небольшой хак, в результате которого олдгеймер вроде меня мог бы вместо кода ввести просто номер желаемого уровня. Перейти сразу к результатам.









Решение


image









Больше всего времени у меня отняла распаковка файла игры. К изначально запакованному коду игры было добавлено интро команды, взломавшей когда-то защиту от копирования, и затем все это было снова запаковано другим алгоритмом. За помощью в распаковке я обратился к статье г-на Гречникова: habrahabr.ru/post/187072, в которой меня заинтересовал раздел под заголовком «Распаковка», а конкретно следующий абзац:
То есть, остаток процедуры loc_19960 не вызывается, если передать в командной строке параметр /ni. Пробуем, видим, что с этим параметром игра пропускает напоминание о доблестной команде Hybrid и сразу переходит к собственно игре. Делаем вывод, что весь seg002 — ненужная для игры заставка, навешенная на рабочий exe'шник. Переписываем в заголовке cs:ip на 0000:0003, ss:sp на 1681:0080, обрезаем последние 5E90h байт, соответственно скорректировав поля размера и дополнительной памяти в заголовке.


(Дальше было неинтересно, т.к. готовое решение — это не то, чего мне хотелось.)
После того, как я последовал данному совету, я обнаружил яйцо в утке, извлеченной ранее из зайца… или как там в старинных русских сказках обычно бывало? Проще сравнить это с матрешкой:

image

image

После распаковки diet программа выросла до 90 Кб и уже ничто не мешало запустить в нее грязные лапы. Чем я и занялся, открыв Hiew и сразу же найдя интересующую меня строку:

image

Уточню для таких же, как я, набирающихся опыта (хотя учиться стоит всегда), что поиск строки в Hiew — это сразу после запуска F4 — Hex, F7 — ASCII -> «Интересующая нас строка».
Адрес строки — 15401. Теперь мне нужно узнать, в каком месте программы происходит обращение к этой строке. Естественно, что такие обращения с высокой долей вероятности будут близки к той части, которую я вознамерился ломать.
Как это можно узнать? Путей, на самом деле, множество. Можно открыть файл в Ida Pro и изучить перекрестные ссылки. Правда, вначале настроив Ida так, чтобы она дружила с используемым типом строк. Но поскольку Id'у я не смог подружить с Dosbox'ом на таком уровне, чтобы можно было спокойно и без раздражающих факторов отлаживать 16-битные файлы (Dosbox постоянно забывал, что он отладчик и пытался жить своей жизнью, несмотря на патч и плагин), то я решил вообще не использовать такую тяжелую артиллерию там, где нужно, скорее всего, просто подправить несколько байт.
Поэтому поступим проще. Чтобы выяснить, где используется смещение на эту строку в программе, нужно сначала выяснить само смещение. Для этого нужно узнать базу сегмента данных. В Hiew это более чем просто. Жмем F8 и смотрим заголовок файла.

image

Итак, для начала откроем калькулятор, переключимся в шестнадцатиричную систему и введем в него значение Paragraphs in header: 86, после чего умножим на 10. Скопируем это значение в буфер и вернемся в Hiew, где все еще не закрыто окошко заголовка. Не выходя, нажмем F5 для перехода в точку входа. И вот тут уже перейдем в режиме decode (F4 — decode). Перед нами такой листинг:

листинг 1.
00000468: FA                           cli
00000469: FC                           cld
0000046A: BAFFFF                       mov         dx,0FFFF ;"  "
0000046D: BE8000                       mov         si,00080 ;" А"
00000470: AC                           lodsb
00000471: 98                           cbw
00000472: 8BC8                         mov         cx,ax
00000474: E35E                         jcxz        0000004D4  ---↓ (1)
00000476: AC                           lodsb
00000477: 3C2F                         cmp         al,02F ;"/"
00000479: 7404                         je          00000047F  ---↓ (2)
0000047B: E2F9                         loop        000000476  ---↑ (3)
0000047D: EB55                         jmps        0000004D4  ---↓ (4)
0000047F: AC                           lodsb
00000480: 24DF                         and         al,0DF ;"▀"
00000482: 3C46                         cmp         al,046 ;"F"
00000484: 7506                         jne         00000048C  ---↓ (5)
00000486: 2E83260300FE                 and         w,cs:[0003],0FFFE ;"■"
0000048C: 3C4D                         cmp         al,04D ;"M"


Все это нам неинтересно. Нам интересно присвоение значения регистру ds. Даже не прибегая к поиску, мы прокручиваем экран совсем немного вниз и находим искомое:

листинг 2.
000004D4: B8030A                       mov         ax,00A03 ;"◙"
000004D7: 8ED8                         mov         ds,ax


Вытаскиваем 00А03 и запихиваем в калькулятор, где тоже умножаем на 10 и прибавляем к сохраненному ранее в буфере значению. Получаем А290. А теперь вычтем А290 из 15401: В171. Это смещение строки, которое будет использоваться внутри программы. Теперь переходим в гекс-отображение (F4 — Hex) и жмем F7, куда в строку, поименованную басурманским словом «Нех», введем 71 В1 (байты идут в обратном порядке, помним ведь?). Выходим на следующий листинг:

листинг 3.
00009E23: E85801                       call        000009F7E  ---↓ (1)
00009E26: C606A7B103                   mov         b,[0B1A7],003 ;""
00009E2B: BB71B1                       mov         bx,0B171 ;"▒q"
00009E2E: C706A2B1F20A                 mov         w,[0B1A2],00AF2 ;"◙Є"
00009E34: E8EDFE                       call        000009D24  ---↑ (2)
00009E37: C606A7B104                   mov         b,[0B1A7],004 ;""
00009E3C: BB6CB1                       mov         bx,0B16C ;"▒l"
00009E3F: C706A2B1C912                 mov         w,[0B1A2],012C9 ;"╔"
00009E45: E8DCFE                       call        000009D24  ---↑ (3)
00009E48: 803EA6B101                   cmp         b,[0B1A6],001 ;""
00009E4D: 7503                         jne         000009E52  ---↓ (4)
00009E4F: E90001                       jmp         000009F52  ---↓ (5)
00009E52: 803EA6B102                   cmp         b,[0B1A6],002 ;"☻"
00009E57: 7503                         jne         000009E5C  ---↓ (6)
00009E59: E90601                       jmp         000009F62  ---↓ (7)
00009E5C: 8A1E7028                     mov         bl,[2870]
00009E60: F6C380                       test        bl,-080 ;"А"
00009E63: 7403                         je          000009E68  ---↓ (8)
00009E65: E91501                       jmp         000009F7D  ---↓ (9)
00009E68: A02928                       mov         al,[2829]
00009E6B: 0A060C28                     or          al,[280C]


В начале этого листинга происходят действия с интересующей нас строкой, а затем те же действия — с соседствующей строкой "[[[[". Пожалуй, статическим наблюдением дальше не обойдешься. Нужно прибегнуть к помощи отладчика. Чтобы не нарушать атмосферу консольности, прибегну к специальному выпуску Dosboх 0.74, содержащему в себе дебаггер. Кроме того, этот вариант гораздо стабильнее, чем костыль Ida + Dosbox plugin.

image

Итак, открываю Dosbox-74-debug (предварительно настроив conf-файл, где просто указаны команды монтирования директории игры и директивы ее запуска с префиксом debug). Не мудрствуя лукаво, запускаем игру в режиме трассировки и вскоре узнаем, что вот в этом месте:

листинг 4.
022E:014C  7B00                jpo  0000014E ($+0) 
022E:014E  C606A46C00          mov  byte [6CA4],00  
022E:0153  B80300              mov  ax,0003
022E:0156  E86701              call 000002C0 ($+167)
022E:0159  E8138D              call FFFF8E6F ($-72ed)
022E:015C  803E862D08          cmp  byte [2D86],08 
022E:0161  7234                jc   00000197 ($+34)


После вызова второй подфункции запускается главное меню, которое «крутится» внутри этой подфункции. Ставим бряк на входе в первую подфункцию и запускаем игру заново, отправляя поначалу в свободный полет.
Вскоре выясняется, что…

image

… отрисовка главного меню заканчивается на этой команде. А сразу за ней следует такой листинг:

листинг 5.
022E:8E99  8ED8                mov  ds,ax
022E:8E9B  33C0                xor  ax,ax
022E:8E9D  A3EC27              mov  [27EC],ax
022E:8EA0  A3EE27              mov  [27EE],ax
022E:8EA3  32C0                xor  al,al
022E:8EA5  3806F227            cmp  [27F2],al
022E:8EA9  7548                jne  00008EF3 ($+48)
022E:8EAB  3806F327            cmp  [27F3],al
022E:8EAF  7557                jne  00008F08 ($+57)
022E:8EB1  A02928              mov  al,[2829]
022E:8EB4  0A060C28            or   al,[280C]
022E:8EB8  7539                jne  00008EF3 ($+39)
022E:8EBA  813EEC270E01        cmp  word [27EC],010E
022E:8EC0  72E3                jc   00008EA5 ($-1d)


Главное меню, напомню, предлагает нажать либо 1, либо 2. В листинге два перехода. В этом месте игра зацикливается между 022E:8EA5 и 022E:8EC0. Все очевидно. Установим брейкпоинты на оба варианта: ВР 022Е:8EF3 и ВР 022Е:8F08, «отпустим» игру в свободный режим, нажав F5 и нажмем 2 — пункт меню, предлагающий ввести password.

Спустя некоторое время трассировки приходим к листингу 3. Строка "[[[[", отрисовывающаяся вместе с «ENTER CODE» — может быть либо строкой "_ _ _ _", которая отображается таким образом из-за хитрого шрифта, либо зарезервированным местом под код. Допустим, что верно последнее и установим брейкпойнт на обращение к памяти по смещениям подозрительных 4 байт: ВРМ 0С11: В16С, ВРМ 0С11: В16D, ВРМ 0С11: В16E и ВРМ 0С11: В16F (текущую базу сегмента данных видим в отладчике). Запускаем опять игру, в меню ввода кода нажимаем клавишу, вводя символ предполагаемого кода, и программа стопорится на листинге:

листинг 6.
022E:9A57  FF06A4B1            inc  word [B1A4]
022E:9A5B  FEC3                inc  bl
022E:9A5D  80FB04              cmp  bl,04
022E:9A60  7303                jnc  00009A65 ($+3)
022E:9A62  E9B800              jmp  00009B1D ($+b8)

DEBUG: Memory breakpoint: 0C11:B16C — 5B -> 38


Инкрементируется какая-то переменная, затем происходит инкремент регистра и проверка, видимо, на превышение 0004, это логично связуется с необходимостью ввода 4 символов. По результатам проверки происходит переход либо на ret, либо на некую дополнительную последовательность команд. В том числе такую:

image

Переменная 0С11:B1B5 хранит введенный только что мною код «4789» в виде числа. Затем следует интересный участок:

листинг 7.
022E:9AAC  33D2                xor  dx,dx
022E:9AAE  8BC2                mov  ax,dx
022E:9AB0  E8A6F8              call 00009359 ($-75a)
022E:9AB3  3906B5B1            cmp  [B1B5],ax
022E:9AB7  7420                je   00009AD9 ($+20)
022E:9AB9  42                  inc  dx
022E:9ABA  83FA14              cmp  dx,0014
022E:9ABD  76EF                jbe  00009AAE ($-11)


Обнуляются dx и ax; вызывается подфункция, после чего недавно очищенный регистр ах сравнивается с введенным кодом (в численном виде). Если они идентичны, то происходит переход куда-то вдаль, иначе dx увеличивается на единицу, и, если не превышает 14, то операции повторяются.
Логично предположить, что происходит перебор четырнадцати правильных паролей и сравнение с введенным (первые 9 уровней для сложности Beginner, остальные — для Expert, хотя эксклюзивных уровней для этой сложности только 2: Замок и Минотавр, чем отличаются другие уровни, не знаю. к примеру, по номеру 10 грузится Ледяной). Все факты подтверждают, что мы на правильном пути.

Собственно… все. Дальше идти некуда; достаточно изменить условный переход на безусловный. В принципе, было бы красиво изменить также количество вводимых символов, так как если оставить это без изменения, то придется вводить номер седьмого уровня как 0007 — ведь все же удобней будет вводить его как 07. Для этого изменим листинг 6 на следующий:

листинг 6_1.
022E:9A57  FF06A4B1            inc  word [B1A4]
022E:9A5B  FEC3                inc  bl
022E:9A5D  80FB02              cmp  bl,02
022E:9A60  7303                jnc  00009A65 ($+3)
022E:9A62  E9B800              jmp  00009B1D ($+b8)

Искать, где и как рисуется строка из четырех черточек, чтобы заменить их на две, мне было уже лень. При том, что для отрисовки восклицательного знака из ресурса шрифтов Змиро использовал символ <, а для запятой — >, поиск «того, не знаю чего» мог оказаться весьма трудоемким. Потратив еще некоторое время на пошаговое исполнение в отладчике, можно было локализовать это место, но я не настолько перфекционист.

Выводы. Что сделано


Новый исполняемый файл игры prel.exe. Не забудьте проверить на вирусы. ;)
Упразднена необходимость собирать пароли по уровням. Сделан возможным выбор уровня из главного меню посредством ввода его порядкового номера из двух разрядов вместо пароля из четырех. За ненадобностью убраны из маски вводимых символов буквы латинского алфавита (A-F), оставлены возможными только цифры; добавлена проверка диапазона вводимых номеров уровней (01-14). В самом деле, зачем игрокам по ошибке или от излишнего любопытства наблюдать вылет в "Insert disk with levelFF.sqz" вместо загрузки уровня. Пригласительная надпись в режиме ввода пароля с «ENTER CODE» изменена на «SELECT LVL». В самом начале из игры вырезано интро Hybrids. Изменено первое интро разработчика («Yeaaa… My game is still working in...»), — дополнено информацией о Hybrids и этой модификации. Изменены строки названий сложностей.

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

image
imageimageimageimage
Tags:
Hubs:
+38
Comments 10
Comments Comments 10

Articles