Войти
ФлеймФорумПрограммирование

Дизассемблер IDA Pro 7.5 для восстановления исходного кода игры (C/C++) (3 стр)

Страницы: 1 2 3 4 514 Следующая »
#30
10:30, 18 июля 2021

Gradius
> Стало (4+1 байт):
>
> push WORD PTR 0x1234 ;номер API инжектора
> int1 ; прерывание-исключение
> nop ; один свободный байт остался!

Здесь я ошибся, забыв исправить на 32-битную адресацию.  Вышло 5 байт. Не меньше.

Можно сделать ещё так:

push  eax    ;1 байт
mov    al,API   ;2 байта
int1             ;1 байт
pop     eax     ;1 байт

но этот вариант проигрывает первому, так как в подменённых функциях придётся корректировать не только  стек, но и регистр.

KPG
> А, в чём были сложности при сборкe и под какую ОС и какая версия собралась?

Никакой сложности нет.  Просто как всегда - делают всё, чтобы усложнить сборку: в последней версии дос-бокса для его сборки под mingw (Win32) отсутствуют нужные make-файлы.

Собрал предыдущую версию.

----

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

Вот чё пишут по этому поводу: http://www.delorie.com/djgpp/doc/dpmi/api/310203.html

И особенно помогло это: http://www.delorie.com/djgpp/doc/dpmi/ch4.5.html

Стек приложения (ss и esp) сидят во фрейме стека обработчика исключения. Пришлось аккуратно всё извлечь, чтоб ничего не запортить.  Обработчик эксепшена разросся, код спрятал в спойлер:

+ Показать

Сохраняются все необходимые регистры, которые понадобятся для работы подменных функций.

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

void UserHandler(u16 arg)
{
 switch(arg)
 {
  case 0:
   //функция 1
  break;

....

  case n:
   //функция n
  break;
 }
}

И не забыть ещё скорректировать стек - снять с него 2 байта.

Проверил, работает - игра периодически делает вызовы моих функций вместо штатных.

#31
13:19, 18 июля 2021

Gradius
> И не забыть ещё скорректировать стек - снять с него 2 байта.

Не нужно.  На меня снизошло озарение, что в качестве номера функции можно использовать значения cs:eip игры, которые передаёт DPMI обработчику исключения:

CS  |
  |---------------+---------------| 10H
  |        EIP    |
  |-------------------------------| 0CH


Проверил, действительно в EIP адрес следующего опкода. Если вычесть единицу, то получим как раз адрес инструкции инжектируемого прерывания!

Меня это сильно обрадовало - всего 1 байт вставить(int1) и 4 nop'а  вместо косвенного вызова.
И не надо стеки с регистрами дополнительно корректировать.


В принципе, можно было уставновить флаг TF и пошагово дёргать обработчик, но пока я смысла в этом не вижу. насколько оно будет практично ?

#32
13:21, 18 июля 2021

Интересно, что под DOS есть и легендарный отладчик SoftICE
Может ли он, как то пригодится для сабжа?

Gradius
> Никакой сложности нет. Просто как всегда - делают всё, чтобы усложнить сборку:
> в последней версии дос-бокса для его сборки под mingw (Win32) отсутствуют
> нужные make-файлы.
Понятно, на  Github есть разные варианты DosBox, типа DosBox-X, DosBox-daum, DosBoxFast - чем и как они отличаются XЗ, но тот же DosBoxFast собрался без проблем (в наличии configure, а не только autogen.sh) как под Linux так и под MinGW Windows с  SDL (запустился и  под Wine Linux)

#33
13:29, 18 июля 2021

KPG
> Интересно, что под DOS есть и легендарный отладчик SoftICE
> Может ли он, как то пригодится для сабжа?

В ДОС-боксе особо не разбежишься с софтом. Из-за эмуляции дебаг-софт падает часто.

Ещё такой момент, связан с определением базы сегментов. Как определить физические адреса сегментов, когда они уже легли в память?

Для сегмента кода сделал так:

pop    eax
push  eax
and    eax,0xFFFFF000
mov    codebase,eax

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

Проверил - вывел printf-ом строку из этого сегмента, добавив смещение: моя гипотеза  подтвердилась.

А вот что делать с остальными сегментами (пара сегментов данных + ещё один маленький сегмент из 4-х байт) - не знаю.  Пока нашёл решение с помощью поиска по подстроке.

Ищются фрагменты строк, лежащие в пределах от 1 до 4 МБ, находится их адрес.  Относительные смещения строк определяем в IDA Pro по дизассемблерному листингу.  Зная физический адрес начала строки и смещение в ИДЕ, я посчитал базовые адреса сегментов данных.  Они оказались верными - вывел на печать несколько других строк из этих сегментов  и переменные - значения совпали как в файле игры.

ИДА нихера не распихивает сегменты по реальным адресам - какие задашь, такие и будут.  Там только относительные величины можно смотреть...

А как если по науке делать ? С форматом LE executable сношаться?  Или это пререготива ДОС-экстендера - он решает куда грузить сегменты?

#34
13:33, 18 июля 2021

KPG
> Понятно, на Github есть разные варианты DosBox, типа DosBox-X, DosBox-daum,
> DosBoxFast - чем и как они отличаются XЗ, но тот же DosBoxFast собрался без
> проблем (в наличии configure, а не только autogen.sh) как под Linux так и под
> MinGW Windows с SDL (запустился и под Wine Linux)

Оно всё мимо кассы, дебаг там 16-битный.  Нету там дебага DPMI

#35
13:36, 18 июля 2021

Gradius
> А как если по науке делать ? С форматом LE executable сношаться? Или это
> пререготива ДОС-экстендера - он решает куда грузить сегменты?
Тут уже, наверное, надо копать как DOS вирусы с ним работали :)

P.S. А, сам код DosBox изменяется для использования под поставленную задачу?

Gradius
> Они оказались верными - вывел на печать несколько других строк из этих
> сегментов и переменные - значения совпали как в файле игры.
Думаю, что от этого можно пока и отталкиваться.

#36
13:52, 18 июля 2021

KPG
> P.S. А, сам код DosBox изменяется для использования под поставленную задачу?

Пока DOSBox не модифицирую.  Хотя по началу работы в этой области велись - логировал вызов интересующих инструкций.  Это это всё муторно - постоянно перекомпилировать его...

Дебаггеры тоже в пролёте.  Ну вот жму я пошаговую отладку, выставляю бряки на интересных местах, смотрю регистры и память....  А дальше что? 

Как в дебагер всунуть мой новый код на место старого ?  Причём, чтобы можно было задавать стартовый адрес и сколько байт заменить?

А как подключить свой сишный код в дебаггер и заставить его выполняться в чужом приложении на месте асмовского ?

НИКАК.

Поэтому сейчас пишу свой инжектор, чтобы можно было скрещивать свой Си-Асм код с кодом игры.

Си-функции из выдачи Hex-Rays.  Если он лажает - берется АСМ-вариант функции и доводится вручную до Си или ищется место, где Hex-Rays облажался...

Тут только свой софт писать или доворачивать существующий.  Для такой задачи нет готового решения.  Приходится мозг напрягать :)

#37
13:56, 18 июля 2021

KPG
> Тут уже, наверное, надо копать как DOS вирусы с ним работали :)

Вирусы под DPMI? Из ряда фантастики.  Вирусы под ДОС заражали только 16-битный стаб, не трогая 32-битную LE-часть.  Этого уже было достаточно, чтобы заразить файлы при запуске одного.

#38
13:56, 18 июля 2021

Gradius
> Как в дебагер всунуть мой новый код на место старого ? Причём, чтобы можно
> было задавать стартовый адрес и сколько байт заменить?

В этом варианте дебагеры могут только помочь в уточнении правильности дизассемблированного кода, например в сравнении с IDA, т.к. выводят тоже литинг инструкций в отслеживаемом бряке.

P.S. Ну да, сложно, но на уровне отслеживания адресов в DosBox  можно попробовать это провернуть.

#39
13:58, 18 июля 2021

KPG
> В этом варианте дебагеры могут только помочь в уточнении правильности
> дизассемблированного кода, например в сравнении с IDA, т.к. выводят тоже литинг
> инструкций в отслеживаемом бряке.

их даже скопировать и сохранить в файл по-человечески-то не получается...  Watcom  Debugger -  видит отладочную инфу, подсовывает человеческие имена функций.  А что с этим делать?  На диск даже не сохраняется дизассемблированный вариант.  И падает в ДОС-боксе..

#40
14:01, 18 июля 2021

Gradius
> их даже скопировать и сохранить в файл по-человечески-то не получается...
> Watcom Debugger - видит отладочную инфу, подсовывает человеческие имена
> функций. А что с этим делать? На диск даже не сохраняется дизассемблированный
> вариант. И падает в ДОС-боксе..
Возможно через DosBox перехватить вывод Дебагера, но т.к. он, скорее всего делает свой вывод в видеопамять, то не так это просто может оказаться сделать это.

Gradius
> Тут только свой софт писать или доворачивать существующий. Для такой задачи
> нет готового решения. Приходится мозг напрягать :)
Да, и лучше такой софт никому не показывать и им не делиться. :)

Gradius
> Си-функции из выдачи Hex-Rays. Если он лажает - берется АСМ-вариант функции и
> доводится вручную до Си или ищется место, где Hex-Rays облажался...
Может имеет смысл добавить свой вариант "Hex-Rays"  в сервис инжектора.

#41
15:47, 18 июля 2021

KPG
> Да, и лучше такой софт никому не показывать и им не делиться. :)

Рано пока говорить о софте. Всё в стадии эксперимента пока :)

Я тут немного обжёгся.  Hex-Rays при декомпиляции объявляет переменные и использует их по-разному - как указатель и как просто переменная  по значению.  В то же время - много других переменных - которые есть массивы с неопределённой (или определённой) длиной.

Я не долго думая, взял все переменные и объявил их указателями на некий базовый адрес сегмента + смещение.

Тоесть было так:

int joystickread;
int MemoryRead[];

Обращение к переменным:

joystickread = 5;

*((char*)&joystickread + 4) = p + 7

MemoryRead[3] = 8;

fread (&MemoryRead + 7, ....)

В итоге напоролся на неоднозначность при замене на указатели.

Если  MemoryRead - это массив, как объявлен выше,  то операция взятия адреса этого массива: &MemoryRead и просто обращение по имени MemoryRead - есть одно и то же.

А если MemoryRead - указатель на некий сегмент со смещением - типа такого:

int *MemoryRead=(int*)(BASE_Address + Offset_IDA)

то конструкции:

MemoryRead и &MemoryRead - уже НЕ одно и то же!

Возникает вопрос:  как объявить массив, ссылающийся на внешний указатель?

Второй вопрос по переменным.  Как красиво сделать обращения к переменным, если они - внешние адреса?

Способа кроме как задефайнить разыменовынный указатель не нахожу:

#define joystickread  (*(int*)(Base_Addr + Offset_IDA)) 

Вообще, есть способ сделать переменные и массивы, которые на самом деле "псевдо-" и лежат в по определённым адресам в памяти?

В микроконтроллерных компиляторах есть такие штуки с помощью специальных директив.

А в С++ как?

#42
16:59, 18 июля 2021

Gradius
> сделать переменные и массивы, которые на самом деле "псевдо-" и лежат в по определённым адресам в памяти?
Можно объявить переменные как extern volatile В C++-ном коде. Потом каким-то образом надо наколдовать с компоновщиком, чтобы он обращения к этим переменным заменял на обращение к конкретный адресам.

#43
18:42, 18 июля 2021

Удалось подключить пару функций на C++ в код игры.

Первая функция без параметров - её подключить легко.

Со второй пришлось повозиться, так как передача параметра происходит через стек, да и ещё передаётся указатель. А учитывая, что  косвенный вызов заменяется прерыванием-исключением, пришлось узнать на сколько надо корректировать указатель кадра стека.

Функция на асме (фрагмент) - установка палитры, фейд(-in/-out):

Clipboard02 | Дизассемблер IDA Pro 7.5 для восстановления исходного кода игры (C/C++)

Её сишный вариант, сгенерённый Hex-rays:

Clipboard02 | Дизассемблер IDA Pro 7.5 для восстановления исходного кода игры (C/C++)

Из картинки ясно, что параметр функции лежит в стеке [esp+C]. также видно, что это - указатель на палитру (тройки значений R,G,B).

Опытным путём (через вывод значений содержимого) установил, что необходимо отнять 12 для корректировки смещения.

Подменённые вызовы функции:

Clipboard02 | Дизассемблер IDA Pro 7.5 для восстановления исходного кода игры (C/C++)

Последние 4 свитча - и есть вызов палитры из мест, где прерывание INT1 (icebp):

Clipboard02 | Дизассемблер IDA Pro 7.5 для восстановления исходного кода игры (C/C++)

Как раз 1 из вариантов - оригинальная процедура игры была по адресу CSEG1 ..... +0x15DEB
Остальные байты забиты NOP-ами.
Исходная инструкция вызова была 5 байт.

Все регистры заранее сохранены в системном обработчике исключения - о нём я рассказывал здесь:
https://gamedev.ru/flame/forum/?id=262348&page=3&m=5419998#m32

Игра работает и не артефачит.

На первый раз процесс муторный.  Много времени уходит на уточнение корректировок  фрейма стека и прочего. Хорошо, что это надо 1 раз сделать!

Посоветуйте удобный патчер файла, а то через ИДУ байты набирать неудобно.  Что-то типа HIEW, только 32-битный - чтобы дизасмила и патчила EXE моментально.

И как заставку в ДОС-боксе отключить?  Много отнимает времени при каждом запуске, когда кидаешь новые файлы...

#44
22:03, 18 июля 2021

Gradius
> патчер файла

Древняя технология crk/xck файлов не прокатит?

http://old-dos.ru//index.php?page=files&mode=files&do=list&cat=350

В современных условиях, наверное проще использовать http://xdelta.org/  вопрос в том, как этот delta патч наваять, не имея на руках исходного и патченного файлов.

Страницы: 1 2 3 4 514 Следующая »
ФлеймФорумПрограммирование