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

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

Advanced: Тема повышенной сложности или важная.

Страницы: 1 2 3 4 512 Следующая »
#30
(Правка: 4:19) 4:09, 18 июля 2021

0xBADCODE
> там и редактирование dll, и загрузка в секцию данных загруженной игры своего
> объектного файла с перенастройкой адресов.

Понятно. Для DOS DPMI не подойдёт.

Удалось задействовать INT1 (debug trap) для вызова API инжектора. Команда занимает всего 1 байт и вместе с push WORD (передача номера функции API) суммарно займёт 4 байта! Допустимо использовать НЕ больше 5 байт (для затирания косвенных вызовов функций игры).

Правда не всё оказалось просто. DOS4GW не давал установить обработчик прерывания на INT1 функцией 0x205 и кидал эксепшн.  Пришлось воспользоваться функцией 0x203 и установить обработчик исключения.

Суммарно выходит следующее:

1) Загрузка кода инжектора и его API - внешняя программа, читающая бинарник с файла: она записывает  его в адрес 0x400000 (начало 4-го мегабайта).  Бинарник получен с помощью Watcom C.

2) Инсталляция инжектора в коде игры (в main) - вызываем функцию по адресу 0x400000 (это входная точка инжектора для установки его  API):

mov     eax, 400000h
call    eax

3) Вызов API инжектора в коде игры в местах оригинальных вызовов процедур:

Было(5 байт):

call sub_0123455

Стало (4+1 байт):

push WORD PTR 0x1234   ;номер API инжектора
int1             ; прерывание-исключение
nop            ; один свободный байт остался!

Вначале была идея номер API сохранять в 16-битном регистре, но тогда его содержимое разрушится. Поэтому решено было использовать стек.  Правда, со стека в обработчике надо снимать это значение.

Код установки эксепшена и его обработчики.

Cи-часть:

extern void SystemHandler(void);

void UserHandler(void)
{
  //тут анализируем номер API и вызываем свои функции, которые надо вызвать вместо функций игры
}

void SetPMException(u8 exception,u32 handler) //DPMI функция - устанавливает обработчик эксепшена
{
 _asm
 {
  mov   ax,0x203
  mov   bl,exception
  push  cs
  pop   cx
  mov   edx,handler
  int   0x31
 }
}

void MyMain(void) //точка входа 0x400000 - установка инжектора
{
 SetPMException(0x01,(u32)SystemHandler);
}

Ассемблерная чать:

.CODE

EXTRN  UserHandler_:PROC

PUBLIC  SystemHandler_

SystemHandler_:

  pushf
  pushad
  push    ds
  push    es
  push    fs
  push    gs

  call    UserHandler_

  pop    gs
  pop    fs
  pop    es
  pop    ds
  popad
  popf

  retf

END

Игра находится в адресном пространстве 0x0 - 0x3FFFFF (первые 4 МБ)
Инжектор: 0x400000 - 0x7FFFFF (вторые 4 МБ)

Такое распределение было выбрано исходя из того, чтобы игра случайно не затёрла код инжектора.
Сама игра требует 4 МБ памяти как минимум (анализ просмотренного дампа памяти показал обилие развёрнутых спрайтов и текстур...)


#31
6:57, 18 июля 2021

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

#32
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 байта.

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

#33
(Правка: 13:19) 13:19, 18 июля 2021

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

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

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


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

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


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

#34
(Правка: 13: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)   

#35
(Правка: 13:34) 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 сношаться?  Или это пререготива ДОС-экстендера - он решает куда грузить сегменты?

#36
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

#37
(Правка: 13:51) 13:36, 18 июля 2021

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

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

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

#38
(Правка: 13:54) 13:52, 18 июля 2021

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

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

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

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

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

НИКАК.

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

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

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

#39
(Правка: 13:56) 13:56, 18 июля 2021

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

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

#40
(Правка: 13:58) 13:56, 18 июля 2021

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

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

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

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

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

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

#42
(Правка: 14:20) 14:01, 18 июля 2021

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

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

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

#43
(Правка: 15:48) 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)) 

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

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

А в С++ как?

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

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

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