Шукаємо коди рівнів в Prehistorik-2, SavePearlHarbor

Ще одна копія хабора

Шукаємо коди рівнів у Prehistorik-2

Під катом – процес дослідження. Потрібно знання асемблера x86 хоча б на рівні читаю зі словником.

Інструменти

Головний інструмент - дизасемблер IDA Pro. Для дослідження DOS-програм цілком вистачить безкоштовної версії 5.0.

Багато різних DOS-утиліт є тут: http://www.exetools.com/, деякі з них будуть згадані далі у тексті.

Необов'язковий інструмент – відладчик. Можна використовувати якийсь із DOSівських відладчиків (вибір є за посиланням вище), їх загальний недолік — поділ ресурсів (як мінімум, екрану) з програмою, що налагоджується. Віддати всі ресурси програмі, але при цьому налагоджувати її дозволяє емулятор Bochs із вбудованим відладчиком. Крім того, можна обійтися без налагоджувальника.

Розпакування

Аналізуватимемо версію, яку можна скачати з old-games.ru: pre2orig.exe розміром 63728 байт. Над нею попрацювала (знявши захист від копіювання оригінальної версії) гурт Hybrid, про що гра наполегливо повідомляє при старті.

У exe'шнику не виявляється зв'язкових текстових рядків, що наводить на думки про упакованість файлу: exe'шник на диску стиснутий з додаванням коду розпакувальника, при завантаженні спочатку виконується розпакувальник, який розпаковує дані в пам'яті та передає керування на розпакований код. Звичайно, перед дослідженням коду доведеться зняти цей шар. File Scanner звідси успішно ідентифікує пакувальник як LZEXE, для якого є автоматичний розпакувальник unlzexe. Запустивши unlzexe, отримуємо pre2_1.exe розміром 63 728 байт.

У pre2_1.exe є осмислені текстові рядки, перший з них «INTRO CODED BY CYBER». Дивимося всередину:

Тобто залишок процедури loc_19960 не викликається, якщопередати у командному рядку параметр /ni. Пробуємо, бачимо, що з цим параметром гра пропускає нагадування про доблесну команду Hybrid і відразу переходить до власне гри. Робимо висновок, що весь seg002 – непотрібна для гри заставка, навішена на робочий exe'шник. Переписуємо в заголовку cs:ip на 0000:0003, ss:sp на 1681:0080, обрізаємо останні 5E90h байт відповідно скоригувавши поля розміру та додаткової пам'яті в заголовку.

Отримуємо файл pre2_2.exe розміром 39 520 байт. У ньому знову немає зв'язкових текстових рядків (усі рядки з pre2_1.exe виявилися пов'язані із заставкою Hybrid), що знову наводить на думку про упакованість. Той самий File Scanner показує «Diet 1.00,1.20» як пакувальник. Цей факт примітний тим, що diet може працювати у двох режимах: упаковка бінарників та упаковка даних. Спробувавши розпакувати файли рівнів (як найцікавіші дані з усіх) за допомогою diet, переконуємося, що дійсно файли рівнів теж упаковані diet, і, якщо нас зацікавить структура файлів рівнів, один шар (упаковку) ми вже знаємо.

Для зняття diet вдамося до універсального розпакувальника cup386. diet не містить антиналагоджувальних прийомів, запуск cup386 pre2_2.exe pre2unp.exe /1 призводить до розпакованого файлу pre2unp.exe розміром 91344 байт. Пошук текстових рядків показує, що pre2unp.exe є те, що нам потрібно.

Пошук кодів

Першу команду виклику sub_19D1E проігноруємо. Далі слідує два виклики однієї і тієї ж процедури sub_19AC4 , перший раз для рядка «ENTER CODE», другий раз для рядка з 4 символів, що змінюється далі в ході процедури; логічно вважати, що це виведення рядка на екран, а другий рядок - код, що вводиться.

У режимі byte_251D6 == 1 збільшується лічильник word_251DC , доки досягне значення 8Ch, після чогоbyte_251D6 скидається в 0, а введений код очищається. Це підходить під те, що після введення неправильного коду в грі він деякий час залишається на екрані, після чого скидається сам собою. У режимі byte_251D6 == 2 відбувається те саме, але без паузи; це нам нецікаво.

Тут розпочинаються цікаві дії. Сканкод підставляється як індекс у рядок "—1234567890—-A-E————A-DF————C-B-…-", це перетворення на ASCII-код (заглянувши в список сканкодів, виявляємо, що ASCII-код 'A' виходить не тільки від клавіші A, а й від клавіші Q — треба вважати, щоб гра працювала також із розкладкою AZERTY). Якщо код ASCII не є hex-цифрою, процедура ігнорує клавішу і виходить. Якщо є, відбувається перетворення в число від 0 до 0Fh.

Змінна word_251E5 зсувається на 4 біти вліво, молодші біти заповнюються числом, що відповідає введеному символу. Сам символ дописується в рядок із введеним кодом, кількість введених символів word_251D4 збільшується на одиницю. Якщо ще немає 4 символів, обробка закінчується. Після 4 символів word_251E5 містить 16-бітове число, що зберігає введений код («1234» - 1234h), далі слід очікувати перевірок цього числа.

Обнулення лічильника word_251DC та перша перевірка. Якщо деякі перетворення над трьома змінними, що лежать у пам'яті безпосередньо перед word_251E5 , призводять до деяких фіксованих значень і введений код знаходиться в діапазоні від 1 до 15h включно, відбувається перехід на мітку loc_19CD9 з dx , що дорівнює введеному коду мінус одиниця. Відкладемо поки що з'ясування, що ж це за змінні.

Перевірка. word_251E5 послідовно порівнюється з результатом роботи функції sub_19559 , що викликається послідовно з dx від 0 до 14h. При рівності відбуваєтьсяперехід на мітку loc_19CD9 з dx при якому sub_19559 повертає введений код. Зрозуміло, sub_19559 повертає код заданого рівня.

Якщо збігу немає, то byte_251D6 встановлюється в 1 (як ми вже бачили, це відповідає неправильному коду), введений код і два попередніх змінних переміщаються на одну змінну назад. Після трьох неправильних спроб три змінні, що передують введеному коду, зберігатимуть попередні варіанти; тепер зрозуміло, звідки беруться вихідні дані першої перевірки.

Завершення процедури при правильному коді: якщо dx менше 10, воно записується як є в byte_1CDB6, а byte_251C3 стає нулем; інакше в byte_1CDB6 записується dx-10, а byte_251C3 стає одиницею. У будь-якому випадку byte_251D6 дорівнює 2, що означає кінець процедури.

Залишається зробити генератор кодів, що діє так само, як sub_19599 і/або з'ясувати, як пройти першу перевірку. Коротко про обчислення кодів: спочатку обчислюється змінна word_2435F на основі останніх 10h байт BIOS F000:FFF0 (що включають дату BIOS) та перших 80h байт всіх розширень BIOS в інтервалі від C000:0000 до F000:0000. Власне код рівня обчислюється так: