Реверс инжиниринг DOS игр
От: WFrag США  
Дата: 16.07.03 13:00
Оценка: 15 (3)
Хотел ответить одному человеку, насчет реверс инжиниринга. Но подумал, а вдруг кому еще интересна эта специфическая область, реверс инжиниринг DOS-игр. Да и у меня есть пара вопросов.

В общем, этим делом я занялся как-то спонтанно (где-то три недели назад), без всякой подготовки, поэтому никакие книжки порекомендовать особо не могу . Просто раньше, когда я учился в школе, я имел некоторый опыт общения с DOS-овским ассемблером, а тут видимо проснулась давняя любовь , да еще подпитанная свободным временем. Но могу дать некоторые советы и рассказать некоторые грабли, на которые я за эти три недели уже успел наступить. Может кто-то что-то про эти грабли знает — уж как-то некрасиво я их обошел .

Вот моя последовательность действий и инструменты (для DOS-программ):

Инструменты:
1. IDA Pro 4.3.0.740a. Сначала пользовался бесплатной версией — триальной, но с ней я бы еще месяц в CRT ковырялся. Поэтому лучше сразу с Pro версии начинать, она многие стандартные функции знает, фреймы функций распознает. Но правда наблюдаются странные глюки — отрубаются хоткеи и иногда (похоже, при скроллинге колесиком мышки) — вылетает окошка, жмешь OK, оно сново вылетает , приходится убивать. Поэтому лучше чаще сохранятся.

2. Borland C++ 3.1. Вообще-то та игра, которую я ковыряю, была написана на более раннем, но этот подходит. Для сборки уже переписанных на C функций.

3. Tasm32 5.0 — для сборки огромного исходника, полученного от IDA. Я не знаю, можно ли Ida-шке сказать, чтоб исходники делила на несколько файлов, по сегменту в каждом. Указываю ему параметры /m500000 /kh500000 (хэш-таблицу символов побольше и количество проходов). Грабли: Похоже, некоторые из счетчиков строчек в tasm32 — 16 разрядные , поэтому если файл длиннее 65536 строк, то выдает дурацкие ошибки. Решение — разбить файл на поменьше и в конце каждого указывать "include partnext.asm".

4. Tasm от BC3.1. Почему-то tasm32 не собирает мои 16-разрядные исходники (маленькие, большой, от IDA, без проблем). Для меня это прям загадка осталась. Компилирую огромный исходник — OK. Вырезаю из него один сегмент, указываю в исходнике 286 процессор, компилирую — говорит "ошибка". Ощущение, что пытается собирать исходник как 32 разрядный. В общем, забил я на это, и обычным tasm-ом собираю.

5. Tlink от BC3.1. Опять загадка. Указываю параметры в командной строке — говорит сегмент _TEXT больше 64 килобайт, ошибка. Пишу параметры в файл, запускаю tlink @params, все работает.

Алгоритм. Расчитан на итерационный процесс (получил асмовый мсходник, собрал, проверил, переписал часть на C, свернул эти функции в Ida, экспортировал в asm, собрал, проверил, и.т.д), в результате которого получается исходник на C. Если все, что нужно, это просто подсмотреть некоторые алгоритмы, то все гораздо проще. Расчитан на программу, написанную на C. Может подойдет и для других языков.

Действия:
1. Автоматический анализ с помощью IDA Pro. Просто запускаешь и даешь exe-шник.
2. Проход всех неизвестных мест в сегментах кода, пометка их кодом или данными (данные в сегменте кода мне встречались двух типов — просто данные, когда данный сегмент был собран из ассемблеровского исходника или switch-таблицы в сегментах, собранных из C)
2. Самая муторная часть, требует огромной внимательности — от этого шага зависит успех всей операции! Нужно все абсолютные смещения заменить на метки. Часть смещений, например, вида
mov ax,[1234h]

Ida сама заменяет. Но вот такие:
push ds
push 1234h

(передача far-указателя параметром)
или такие
mov ax,[bx+1234h]

(индексация)
или даже такие:
mov dx,seg seg012
mov ax,1234h

(dx:ax — указатель на ячейку под адресу 1234h в сегменте seg012 ) она не заменяет. Я не знаю другого способа этого сделать, кроме как руками (я с Ida всего три недели знаком, может как-то и можно). Пришлось пройтись по всем таким местам и явно указать тип операнда — смещение в указанном сегменте.

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

3. Сборка, проверка. Должно работать.
4. Отрыв CRT. Первый сегмент (seg000) в C программах — CRT. Нужно заменить его на CRT-библиотеку (чтоб не мешался). Просто свернул весь сегмент, вырезал из сегмента данных все данные, относящиеся к CRT (они подлинкуются вместе с библиотекой). Перед этим подсмотрел модель памяти (метка, по-моему, _MMODEL). Сравнил с меткой в проге, собранной с моделью памяти LARGE — совпало. Значит, исследуемая прога тоже собрана в LARGE. На основании этого выбрал CRT (c0l.obj, cl.lib, mathl.lib, и.т.д)
Поскольку я не знаю, как в Ida ставить комментарии на строчку, записал сегмент данных в отдельный файл, и руками вырезаю из него переменные (например, которые перекочовывают в C-исходники). Правда, при переименовывании переменной в Ida приходится переименовывать ручками .
5. Сборка, проверка. Должно работать.

Далее начинаются собственно итерации для получения исходника на C:
6. Внимательно смотрим исходники. "Ага! Действия этой функции понятны! Более того, она не имеет ссылок на асмовые функции (только на уже переписанные)". Переписываем на C (кстати, полезно сразу сделать поддержку лога, чтоб в него ошибки писать). Сворачиваем функцию в Ida.
7. Анализируем код, переименовываем переменные значения которых поянтны.
8. Собираем, проверяем. Должно работать.
9. Идем на 6

Из литературы я использую:
Книжки:
"Системное программирование" и "Управление ресурсами" А.И. Касаткина (92/93 год), просто под руку попались — я на них программированию под DOS на C обучался когда-то. Из них я почерпнул информацию о моделях памяти, huge-указателях, работе с видеопамятью,
работе с прерываниями (как использование, так и написание собственных обработчиков)

Электронная документация:
Thelp, базу "Tech Help 4.0". Ценнейшая информация о прерываниях DOS/BIOS, портах
I/O (правда, ограниченная), Scan кодах и тому подобное.
Простенький хелп по ассемблеру (я давно им не занимался, кой чего позабыл, а кой чего и не знал )

Далее идет специфическая информация, например, по портам, найденная в гугле:
Программирование звуковой карты Adlib (базируясь на этой иформации планирую вытащить звук), Adlib.txt
Программирование системного таймера (порты 40h-43h), найдено, по-моему, на vcl.ru

Ну вот, собственно и все. А дальше — опыт. Потом одного взгляда хватает, чтоб понять, что какой кусок делает.

О! Еще грабли, на которых набил огромную шишку размером в один день.
Если имеем две far-функции в одном сегменте, то Borland C оптимизирует вызов одной из другой
Вместо
call far ptr _someFunc

делает
push cs
call near ptr _someFunc

А теперь представим, что переписали someFunc на C. someFunc попадает в другой сегмент. Очень вероятно, что ошибка на этапе компиляции не обнаружится (если смещение до someFunc в терминах сегмента первой функции меньше 64k). Но! Функция someFunc будет вызвана, просто пара cs:ip будет другой. Для некоторых функци это не имеет значения (все смещения внутри них — относительные). А для других функций (например, имеющих данные в своем сегменте) — будет, смещения поедут. ВАЖНО: Частным случаем таких функций являются функции с switch-ами (я в монитор, когда у меня switch перестал правильно работать). Нормально решения я не знаю, кроме как все такие вызовы:
push cs
call near ptr _someFunc

заменять на
call far _someFunc


Замечание:
Формат switch-таблицы:
Первый вариант — просто смешения, в коде встречается что-то вроде:

mov     bx, di
sub     bx, 5           ; switch 16 cases 
cmp     bx, 0Fh
jbe     loc_1A34_12DE
jmp     loc_1A34_143D   ; default

loc_1A34_12DE:                          ; CODE XREF: sub_1A34_12B1+28j
shl     bx, 1
jmp     cs:off_1A34_1443[bx] ; switch jump

и таблица вида:
off_1A34_1443 dw offset loc_10BC_31A
                            dw offset loc_10BC_34E

Такие switch-и Ida автоматически распознает.

Второй вариант — таблица значений и смещений:

word_10BC_1F23    dw 1            ; DATA XREF: sub_10BC_225+DEo
                dw 2
                dw offset loc_10BC_31A
                dw offset loc_10BC_34E


В коде что-то вроде:

mov    cx, 2    ; Размер switch-таблицы
mov    bx, offset word_10BC_1F23

loc_10BC_306:                ; CODE XREF: sub_10BC_225+EBj
    mov    ax, cs:[bx]
    cmp    ax, [bp+var_12]
        jz    loc_10BC_315
        inc    bx
        inc    bx
        loop    loc_10BC_306
        jmp    loc_10BC_1F0B

Соответственно, видя похожий код следует явно указывать тип операнда — смешение до метки в текущем сегменте.
mov bx,0a12h

на
mov bx,offset word_111_222



Усе. Вроде все сказал.

P.S. На последок хочу сказать, что занятие это чрезвычайно трудное, требует внимательности и аккуратности. А так же опыт, и анализ.
... << RSDN@Home 1.1 beta 1 >>
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.