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 байта.
Проверил, работает - игра периодически делает вызовы моих функций вместо штатных.
Gradius
> И не забыть ещё скорректировать стек - снять с него 2 байта.
Не нужно. На меня снизошло озарение, что в качестве номера функции можно использовать значения cs:eip игры, которые передаёт DPMI обработчику исключения:
CS |
|---------------+---------------| 10H
| EIP |
|-------------------------------| 0CH
Проверил, действительно в EIP адрес следующего опкода. Если вычесть единицу, то получим как раз адрес инструкции инжектируемого прерывания!
Меня это сильно обрадовало - всего 1 байт вставить(int1) и 4 nop'а вместо косвенного вызова.
И не надо стеки с регистрами дополнительно корректировать.
В принципе, можно было уставновить флаг TF и пошагово дёргать обработчик, но пока я смысла в этом не вижу. насколько оно будет практично ?
Интересно, что под DOS есть и легендарный отладчик SoftICE
Может ли он, как то пригодится для сабжа?
Gradius
> Никакой сложности нет. Просто как всегда - делают всё, чтобы усложнить сборку:
> в последней версии дос-бокса для его сборки под mingw (Win32) отсутствуют
> нужные make-файлы.
Понятно, на Github есть разные варианты DosBox, типа DosBox-X, DosBox-daum, DosBoxFast - чем и как они отличаются XЗ, но тот же DosBoxFast собрался без проблем (в наличии configure, а не только autogen.sh) как под Linux так и под MinGW Windows с SDL (запустился и под Wine Linux)
KPG
> Интересно, что под DOS есть и легендарный отладчик SoftICE
> Может ли он, как то пригодится для сабжа?
В ДОС-боксе особо не разбежишься с софтом. Из-за эмуляции дебаг-софт падает часто.
Ещё такой момент, связан с определением базы сегментов. Как определить физические адреса сегментов, когда они уже легли в память?
Для сегмента кода сделал так:
pop eax
push eax
and eax,0xFFFFF000
mov codebase,eax
Тоесть снимаем со стека адрес возврата (откуда был вызван стартовый код инжектора), округляем в меньшую сторону до размера страницы (4096 байт). Это и будет адрес начала сегмента кода. При условии, что вызов инжектируемого кода находится в переделах начала сегмента.
Проверил - вывел printf-ом строку из этого сегмента, добавив смещение: моя гипотеза подтвердилась.
А вот что делать с остальными сегментами (пара сегментов данных + ещё один маленький сегмент из 4-х байт) - не знаю. Пока нашёл решение с помощью поиска по подстроке.
Ищются фрагменты строк, лежащие в пределах от 1 до 4 МБ, находится их адрес. Относительные смещения строк определяем в IDA Pro по дизассемблерному листингу. Зная физический адрес начала строки и смещение в ИДЕ, я посчитал базовые адреса сегментов данных. Они оказались верными - вывел на печать несколько других строк из этих сегментов и переменные - значения совпали как в файле игры.
ИДА нихера не распихивает сегменты по реальным адресам - какие задашь, такие и будут. Там только относительные величины можно смотреть...
А как если по науке делать ? С форматом LE executable сношаться? Или это пререготива ДОС-экстендера - он решает куда грузить сегменты?
KPG
> Понятно, на Github есть разные варианты DosBox, типа DosBox-X, DosBox-daum,
> DosBoxFast - чем и как они отличаются XЗ, но тот же DosBoxFast собрался без
> проблем (в наличии configure, а не только autogen.sh) как под Linux так и под
> MinGW Windows с SDL (запустился и под Wine Linux)
Оно всё мимо кассы, дебаг там 16-битный. Нету там дебага DPMI
Gradius
> А как если по науке делать ? С форматом LE executable сношаться? Или это
> пререготива ДОС-экстендера - он решает куда грузить сегменты?
Тут уже, наверное, надо копать как DOS вирусы с ним работали :)
P.S. А, сам код DosBox изменяется для использования под поставленную задачу?
Gradius
> Они оказались верными - вывел на печать несколько других строк из этих
> сегментов и переменные - значения совпали как в файле игры.
Думаю, что от этого можно пока и отталкиваться.
KPG
> P.S. А, сам код DosBox изменяется для использования под поставленную задачу?
Пока DOSBox не модифицирую. Хотя по началу работы в этой области велись - логировал вызов интересующих инструкций. Это это всё муторно - постоянно перекомпилировать его...
Дебаггеры тоже в пролёте. Ну вот жму я пошаговую отладку, выставляю бряки на интересных местах, смотрю регистры и память.... А дальше что?
Как в дебагер всунуть мой новый код на место старого ? Причём, чтобы можно было задавать стартовый адрес и сколько байт заменить?
А как подключить свой сишный код в дебаггер и заставить его выполняться в чужом приложении на месте асмовского ?
НИКАК.
Поэтому сейчас пишу свой инжектор, чтобы можно было скрещивать свой Си-Асм код с кодом игры.
Си-функции из выдачи Hex-Rays. Если он лажает - берется АСМ-вариант функции и доводится вручную до Си или ищется место, где Hex-Rays облажался...
Тут только свой софт писать или доворачивать существующий. Для такой задачи нет готового решения. Приходится мозг напрягать :)
KPG
> Тут уже, наверное, надо копать как DOS вирусы с ним работали :)
Вирусы под DPMI? Из ряда фантастики. Вирусы под ДОС заражали только 16-битный стаб, не трогая 32-битную LE-часть. Этого уже было достаточно, чтобы заразить файлы при запуске одного.
Gradius
> Как в дебагер всунуть мой новый код на место старого ? Причём, чтобы можно
> было задавать стартовый адрес и сколько байт заменить?
В этом варианте дебагеры могут только помочь в уточнении правильности дизассемблированного кода, например в сравнении с IDA, т.к. выводят тоже литинг инструкций в отслеживаемом бряке.
P.S. Ну да, сложно, но на уровне отслеживания адресов в DosBox можно попробовать это провернуть.
KPG
> В этом варианте дебагеры могут только помочь в уточнении правильности
> дизассемблированного кода, например в сравнении с IDA, т.к. выводят тоже литинг
> инструкций в отслеживаемом бряке.
их даже скопировать и сохранить в файл по-человечески-то не получается... Watcom Debugger - видит отладочную инфу, подсовывает человеческие имена функций. А что с этим делать? На диск даже не сохраняется дизассемблированный вариант. И падает в ДОС-боксе..
Gradius
> их даже скопировать и сохранить в файл по-человечески-то не получается...
> Watcom Debugger - видит отладочную инфу, подсовывает человеческие имена
> функций. А что с этим делать? На диск даже не сохраняется дизассемблированный
> вариант. И падает в ДОС-боксе..
Возможно через DosBox перехватить вывод Дебагера, но т.к. он, скорее всего делает свой вывод в видеопамять, то не так это просто может оказаться сделать это.
Gradius
> Тут только свой софт писать или доворачивать существующий. Для такой задачи
> нет готового решения. Приходится мозг напрягать :)
Да, и лучше такой софт никому не показывать и им не делиться. :)
Gradius
> Си-функции из выдачи Hex-Rays. Если он лажает - берется АСМ-вариант функции и
> доводится вручную до Си или ищется место, где Hex-Rays облажался...
Может имеет смысл добавить свой вариант "Hex-Rays" в сервис инжектора.
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))
Вообще, есть способ сделать переменные и массивы, которые на самом деле "псевдо-" и лежат в по определённым адресам в памяти?
В микроконтроллерных компиляторах есть такие штуки с помощью специальных директив.
А в С++ как?
Gradius
> сделать переменные и массивы, которые на самом деле "псевдо-" и лежат в по определённым адресам в памяти?
Можно объявить переменные как extern volatile В C++-ном коде. Потом каким-то образом надо наколдовать с компоновщиком, чтобы он обращения к этим переменным заменял на обращение к конкретный адресам.
Удалось подключить пару функций на C++ в код игры.
Первая функция без параметров - её подключить легко.
Со второй пришлось повозиться, так как передача параметра происходит через стек, да и ещё передаётся указатель. А учитывая, что косвенный вызов заменяется прерыванием-исключением, пришлось узнать на сколько надо корректировать указатель кадра стека.
Функция на асме (фрагмент) - установка палитры, фейд(-in/-out):
Её сишный вариант, сгенерённый Hex-rays:
Из картинки ясно, что параметр функции лежит в стеке [esp+C]. также видно, что это - указатель на палитру (тройки значений R,G,B).
Опытным путём (через вывод значений содержимого) установил, что необходимо отнять 12 для корректировки смещения.
Подменённые вызовы функции:
Последние 4 свитча - и есть вызов палитры из мест, где прерывание INT1 (icebp):
Как раз 1 из вариантов - оригинальная процедура игры была по адресу CSEG1 ..... +0x15DEB
Остальные байты забиты NOP-ами.
Исходная инструкция вызова была 5 байт.
Все регистры заранее сохранены в системном обработчике исключения - о нём я рассказывал здесь:
https://gamedev.ru/flame/forum/?id=262348&page=3&m=5419998#m32
Игра работает и не артефачит.
На первый раз процесс муторный. Много времени уходит на уточнение корректировок фрейма стека и прочего. Хорошо, что это надо 1 раз сделать!
Посоветуйте удобный патчер файла, а то через ИДУ байты набирать неудобно. Что-то типа HIEW, только 32-битный - чтобы дизасмила и патчила EXE моментально.
И как заставку в ДОС-боксе отключить? Много отнимает времени при каждом запуске, когда кидаешь новые файлы...
Gradius
> патчер файла
Древняя технология crk/xck файлов не прокатит?
http://old-dos.ru//index.php?page=files&mode=files&do=list&cat=350
В современных условиях, наверное проще использовать http://xdelta.org/ вопрос в том, как этот delta патч наваять, не имея на руках исходного и патченного файлов.