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

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

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

Страницы: 14 5 6 712 Следующая »
#60
(Правка: 10:41) 10:06, 21 июля 2021

Gradius
> Отключить декорирование в С++ или заставить ассемблер генерить имя в
> зависимости от указанной конвенции ?
Поиск в яндексе по запросу "Отключить декорирование в С++" ведёт к каким то обсуждениям
и, в частности, на такой топик Декорирование имен функций в DLL (местную тему, кстати, яндекс тоже отслеживает :)
Может, что то из предложенных рекомендаций, окажется полезным и наиболее действенным.

P.S. Пока, как понимаю, эксперименты проходят и с какой то собираемой игрой в Watcom по исходникам (с разными способами скрещивания/добавления кода в/к ней из IDA) и изучение выхлопа компилятора в коде "франкенштейна"? :)


#61
11:11, 21 июля 2021

KPG
> P.S. Пока, как понимаю, эксперименты проходят и с какой то собираемой игрой в
> Watcom по исходникам (с разными способами скрещивания/добавления кода в/к ней
> из IDA) и изучение выхлопа компилятора в коде "франкенштейна"? :)

Уже нет. Работа ведётся с игрой, которую нужно перевести в Си-код. Есть только EXE с символьной таблицей имён.
Сейчас ощупываются все инструменты и методы на предмет быстрой трансляции EXE в C/C++.

Выбор пал на IDA+Hex-Rays.
Честно говоря, я разочарован. Hex-rays во всю неверно ставит число аргументов функций - из-за этого ломается стек и игра не идёт.  Только ручное раскуривание асм-листинга даёт рабочий Си-код.

Проиллюстрирую на реальном примере из игры:

1.  Есть дизассемблированная функция из игры (большой код буду прятать в спойлеры):

+ Показать

2. А вот то, что выдал Hex-Rays:

int __usercall setup_game@<eax>(int a1@<edx>, __int16 a2@<cx>, __int16 a3@<di>, __int16 a4@<si>)
{
  int v4; // eax
  __int16 v5; // ax
  int result; // eax

  glass_map = (int)byte_2176BC;
  LoadAllData(a4, (int)startup_files);
  load_all_tmaps(a3, a4);
  LOWORD(v4) = load_all_3d_files();
  setup_host(v4, a1);
  colour_lookup = FindColor((unsigned __int8 *)palette, 0, 0, 0);
  byte_1A3DA1 = FindColor((unsigned __int8 *)palette, 0x3Fu, 0x3Fu, 0x3Fu);
  byte_1A3DA2 = FindColor((unsigned __int8 *)palette, 0x3Fu, 0, 0);
  byte_1A3DA3 = FindColor((unsigned __int8 *)palette, 0, 0x3Fu, 0);
  byte_1A3DA4 = FindColor((unsigned __int8 *)palette, 0, 0, 0x3Fu);
  byte_1A3DA5 = FindColor((unsigned __int8 *)palette, 0x3Fu, 0x3Fu, 0);
  byte_1A3DA6 = FindColor((unsigned __int8 *)palette, 0, 0x3Fu, 0x3Fu);
  byte_1A3DA7 = FindColor((unsigned __int8 *)palette, 0x3Fu, 0, 0x3Fu);
  InitMusic(a2, a3, a4);
  InitSound(a2, a3, a4);
  calibrate_digistick(v5);
  setup_a_sprite(logo, logo_end, logo_data);
  fill_stack();
  level = MyAlloc(a3, 66136);
  result = level + 0x10000;
  effect_list = level + 0x10000;
  return result;
}

3. Прототипы функций от Hex-Rays:

__int16 void __cdecl LoadAllData(int a2);
unsigned int void __usercall load_all_tmaps(__int16 a1, __int16 a2);
__int16  load_all_3d_files();
int void __fastcall setup_host(int a1, int a2);
char __cdecl FindColor(unsigned __int8 *a1, unsigned __int8 a2, unsigned __int8 a3, unsigned __int8 a4);
void __usercall InitMusic(__int16 a1, __int16 a2, __int16 a3);
void __usercall InitSound__int16 a1, __int16 a2, __int16 a3);
__int16*/ __fastcall calibrate_digistick(__int16 a1);
unsigned int __cdecl setup_a_sprite(unsigned int a1, unsigned int a2, unsigned int a3);
void ** void fill_stack(void);
int __cdecl MyAlloc(__int16 a1, int a2);

Так вот, несмотря на простоту данного кода, он работать не будет!

4. Мой исправленный рабочий вариант - предельно адекватное и без всякой лишней хероты:

extern "C" void __cdecl LoadAllData(int a2);
extern "C" void __usercall load_all_tmaps(void );
extern "C" void  load_all_3d_files();
extern "C" void __fastcall setup_host(void  );
extern "C" char __cdecl FindColor(unsigned __int8 *a1, unsigned __int8 a2, unsigned __int8 a3, unsigned __int8 a4);
extern "C" void __usercall InitMusic(void );
extern "C" void __usercall InitSound(void);
extern "C" void __fastcall calibrate_digistick(void);
extern "C" void __cdecl setup_a_sprite(unsigned int a1, unsigned int a2, unsigned int a3);
extern "C"  void fill_stack(void);
extern "C" int __cdecl MyAlloc( int a2);

void CPP_setup_game(void)
{
  glass_map = (int)byte_2176BC;
  LoadAllData( (int)startup_files );
  load_all_tmaps();
  load_all_3d_files();
  setup_host();
  colour_lookup = FindColor((unsigned __int8 *)palette, 0, 0, 0);
  byte_1A3DA1 = FindColor((unsigned __int8 *)palette, 0x3Fu, 0x3Fu, 0x3Fu);
  byte_1A3DA2 = FindColor((unsigned __int8 *)palette, 0x3Fu, 0, 0);
  byte_1A3DA3 = FindColor((unsigned __int8 *)palette, 0, 0x3Fu, 0);
  byte_1A3DA4 = FindColor((unsigned __int8 *)palette, 0, 0, 0x3Fu);
  byte_1A3DA5 = FindColor((unsigned __int8 *)palette, 0x3Fu, 0x3Fu, 0);
  byte_1A3DA6 = FindColor((unsigned __int8 *)palette, 0, 0x3Fu, 0x3Fu);
  byte_1A3DA7 = FindColor((unsigned __int8 *)palette, 0x3Fu, 0, 0x3Fu);
  InitMusic();
  InitSound();
  calibrate_digistick();
  setup_a_sprite(logo, logo_end, logo_data);
  fill_stack();
  level = MyAlloc(66136);
  effect_list = level + 0x10000;
}

5. Переменные:
extern "C" int glass_map;     
extern "C" char byte_2176BC[4096]; 
extern "C" char startup_files[15];
extern "C" char colour_lookup;  
extern "C" int palette;     
extern "C" int logo;     
extern "C" int logo_end;   
extern "C" int logo_data;   
extern "C" int level;     
extern "C" int effect_list;   
extern "C" char byte_1A3DA1;    
extern "C" char byte_1A3DA2;    
extern "C" char byte_1A3DA3;    
extern "C" char byte_1A3DA4;    
extern "C" char byte_1A3DA5;    
extern "C" char byte_1A3DA6;    
extern "C" char byte_1A3DA7;    

Игра заработала благодаря тому, что я разрулил вручную конвенции вызовов, глядя что творится со стеком в асм-листинге. 

Финальный вывод: Hex-Rays - говно.
Не получается автоматической генерации С-исходников!

Неужели нельзя разгребание стека делать правильно автоматически?

#62
(Правка: 11:38) 11:22, 21 июля 2021

Gradius
> Сейчас ощупываются все инструменты и методы на предмет быстрой трансляции EXE в
> C/C++.

См. "реверс танчиков". Ведь, там, пардон - не о совсем танчиках, и не совсем о реверсе.

#63
(Правка: 11:41) 11:36, 21 июля 2021

Gradius
> Неужели нельзя разгребание стека делать правильно автоматически?
Наверное "Не шмогла" :)

P.S. На форуме wasm.in разместил ссылку с одноимённой темой на местное обсуждение

#64
(Правка: 12:49) 12:48, 21 июля 2021

Есть ещё такой плагин для ИДЫ - Snowman: https://derevenets.com/
Это тоже декомпилятор.

Попробовал его.  В отличие от HEX-Rays, он разобрал стек вызовов на верхнем примере верно.  Вот его выхлоп:

+ Показать

Из очевидных минусов :

1) переменные сделал локальными и присвоил им значения.  Лучше бы как к ячейке памяти обращался онтносительно базы.

2) переменные называются не так как в листинге ИДы. Это заставляет заниматься парсингом результата.

3) Огромный минус: не пишет конвенцию вызовов в прототипах функций, что делает невозможным вызов этих функций прямо из кода игры.

После доработки исходник стал выглядеть так:

+ Показать

Пришлось сделать дефайны на ячейки памяти и подписать функциям конвенцию вызовов.

Код не заработал.  Ошибка очевидна:

LoadAllData("data\\glass.dat");

Какого-то хера дизассемблер возомнил себя умным и высчитал строку вместо смещения.  А прикол в том, что туда должен передаваться указатель const char*  - там список этих файлов!

Правильный вариант:

LoadAllData((int)0x00195A64);
  или с предыдущего поста:
  LoadAllData( (int)startup_files );

Тоесть передаётся указатель на строку, которая располагается в ресурсах игры,  а не создавать новую обрубочную строку с названием одного файла.

Вот как выглядит эта строка на самом деле:

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

И эти ресурсы надо все загружать!

gudleifr
> См. "реверс танчиков". Ведь, там, пардон - не о совсем танчиках, и не совсем о
> реверсе.

Бегло прочёл, но не понял как оно может решить задачу?

KPG
> P.S. На форуме wasm.in разместил ссылку с одноимённой темой на местное
> обсуждение

Спасибо! :)

#65
12:57, 21 июля 2021

Gradius
> как оно может решить задачу?
Оно поможет ее правильно поставить.

#66
(Правка: 2:49) 2:46, 22 июля 2021

Ещё раз дал шанс декомпилятору Hex-Rays себя показать с лучшей стороны.  Ожидания не оправдались.

Процедура отрисовки 3D-спрайтов с учётом поворота, переноса, анимации. Много состояний и вычислений. Никаких вызовов функций кроме memset.

Результат: Си-шный вариант процедуры работает неверно - спрайты растягиваются в полоску во весь экран.

Декомпилированный вариант Hex-Rays: более 10 000 строк! - Cprocedure

Вариант на ассемблере (без доработки не компилируется): тоже более 10 000 строк - ASMprocedure

Выходит, даже на тупо одной процедуре с расчётами завалился: Hex-Rays облажался по-полной..

#67
8:29, 23 июля 2021

1) Удалось заставить работать ассемблерный кусок функции, что выше.

Были 2 проблемы:

a) Порча памяти - игра затирала инжектор, когда аллоцировала данные. Из-за этого всё валилось в Bad Opcode.  Отодвинул код инжектора по-дальше.

б) Инструкции pusha и popa почему-то ваткомом делаются с префиксом 0x66, помогло явное указание размера операндов pushad/popad

2) Удалось отвязаться от main() игры - теперь вызывается Си-шный main() и в нём часть функций уже свои.
Включая игровой цикл.

Игра теперь используется как библиотека, из которой дёргаются оставшиеся пока ещё неотреверсенные функции.

Hex-rays по-прежнему неверно определяет число аргументов - приходится переправлять, глядя на АСМ-листинг.

Дальнейшие стратегии:

1) Использовать Hex-rays по мере возможности.

2) Если не прёт - пока использовать АСМ-вариант функций.

3) Отслеживать цепочку измененных функций и строить граф по выполненным заменам

#68
(Правка: 11:00) 10:56, 23 июля 2021

Продолжаю разгребать ужасы Hex-Rays...

На этот раз в стек попали члены структуры, которые декомпилятор "успешно"  декомпилировал до первой строки.

Код на ассемблере:

load_all_tmaps  proc near               ; CODE XREF: setup_game+24тЖСp

                push    ebx
                push    esi
                push    edi
                push    ebp
                mov     ebp, esp
                sub     esp, 34h
                mov     dword ptr [ebp-8], 0 ; DWORDPTR0

loc_1EFB07:                             ; CODE XREF: load_all_tmaps+73тЖУj
                mov     eax, [ebp-8]
                push    eax
                mov     eax, offset aDataTex02dDat ; "data\\tex%02d.dat"
                push    eax
                lea     eax, [ebp-34h]
                push    eax
                call    sprintf
                add     esp, 0Ch
                mov     eax, [ebp-8]
                shl     eax, 2
                mov     edx, offset tmaps
                add     edx, eax
                mov     [ebp-18h], edx
                mov     dword ptr [ebp-14h], 0 ; DWORDPTR0
                mov     dword ptr [ebp-10h], 0 ; DWORDPTR0
                mov     word ptr [ebp-0Ch], 0 ; WORDPTR0
                mov     word ptr [ebp-0Ah], 0 ; WORDPTR0
                lea     eax, [ebp-34h]
                push    eax
                call    LoadData
                add     esp, 4
                mov     [ebp-4], eax
                mov     eax, [ebp-8]
                inc     dword ptr [ebp-8] ; DWORDPTR0
                cmp     word ptr [ebp-4], 0 ; WORDPTR0
                jle     short loc_1EFB69
                cmp     dword ptr [ebp-8], 10h ; DWORDPTR0
                jb      short loc_1EFB07

loc_1EFB69:                             ; CODE XREF: load_all_tmaps+6DтЖСj
                mov     eax, [ebp-8]
                dec     eax
                mov     no_tmaps, eax
                mov     esp, ebp
                pop     ebp
                pop     edi
                pop     esi
                pop     ebx
                retn
load_all_tmaps  endp

Декомпилер радостно выдал:

unsigned int __usercall load_all_tmaps@<eax>(__int16 a1@<di>, __int16 a2@<si>)
{
  unsigned int result; // eax
  char v3[28]; // [esp+0h] [ebp-34h] BYREF
  int *v4; // [esp+1Ch] [ebp-18h]
  int v5; // [esp+20h] [ebp-14h]
  int v6; // [esp+24h] [ebp-10h]
  __int16 v7; // [esp+28h] [ebp-Ch]
  __int16 v8; // [esp+2Ah] [ebp-Ah]
  unsigned int v9; // [esp+2Ch] [ebp-8h]
  int v10; // [esp+30h] [ebp-4h]

  v9 = 0;
  do
  {
    sprintf(v3, aDataTex02dDat, v9);
    v4 = &tmaps[v9];
    v5 = 0;
    v6 = 0;
    v7 = 0;
    v8 = 0;
    v10 = LoadData(a1, a2, (int)v3);
    ++v9;
  }
  while ( (__int16)v10 > 0 && v9 < 0x10 );
  result = v9 - 1;
  no_tmaps = v9 - 1;
  return result;
}

Хватило ума только  до v3 разобрать.  Остальное с v4... по v8 получилось бредовым и не связным.  Игра не работает с такой процедурой. А в начале хохма была aDataTex02dDat - вообще константной строкой заменил :)

Раскурил  передачу структуры в стек - получился рабочий исправленный вариант:

void CPP_load_all_tmaps(void)
{
  char v3[28+16]={0};

  unsigned int v9;
  int v10;              

  v9 = 0;
  do
  {
    sprintf((_DWORD)v3, (const char*)aDataTex02dDat, v9);

   u32 esp=(u32)v3;

   *(u32*)(esp+0x1C)=(u32)&tmaps[v9];
   *(u32*)(esp+0x20)=0;
   *(u32*)(esp+0x24)=0;
   *(u16*)(esp+0x28)=0;
   *(u16*)(esp+0x2A)=0;

    v10 = LoadData( (int)v3);
    ++v9;
  }
  while ( (__int16)v10 > 0 && v9 < 0x10 );

  no_tmaps = v9 - 1;
}

Вывод - не только имя файла передаётся в процедуре, но и адрес на некий tmaps, да и ещё 4 нулевых параметра.

Интересно, Ильфак это читает?  Наверное уши от сдыда все горят )))

#69
19:06, 24 июля 2021

Перечень успешно отреверсенных функций игры.  Вплоть до функций CLIB или голого железа.

Особо попотеть пришлось, когда возвращаемое значение передаётся нестандартным образом - некоторые DOS-функции любят передавать результат в регистре BX. Ну и IDA конечно такое не разгребёт.

Расколупал менеджер памяти - процедуры расщепления , объединения областей памяти. Дети, родители. Верхняя память и нижняя(для звукового DMA)

1 | Дизассемблер IDA Pro 7.5 для восстановления исходного кода игры (C/C++)
#70
3:55, 26 июля 2021

Продолжаю реверсить игру.

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

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

На картинке показан расчёт вершин треугольника. Но в функцию (расположена в памяти по адресу 0x40000A) передаются только X-координаты вершин.  Не работало.  Важно было понять, что в качестве параметров передаются указатели на структуру - вершина struct {int X, int Y}.

Исправленный рабочий вариант:

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

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

Ну и классика жанра ошибка на сравнении знаковых типов - игра зацикливалась и не выходила из цикла:

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

Исправленный рабочий вариант:

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

То-ли проблема была со знаковым типом , или  - undefined behaivour - проверять не стал - внёс определённость в код и пошёл дальше.

Плюс вылезла попутно новая проблема:  арифметика с заёмами не декомпилируется  полностью:

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

Написание своей функции не привело пока к успеху:

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

Ну и напоследок - перечень новых отреверсенных функций игры:

1 | Дизассемблер IDA Pro 7.5 для восстановления исходного кода игры (C/C++)
#71
15:56, 26 июля 2021

Ватком не перестаёт удивлять.  У него по умолчанию типы беззнаковые.

Вот такой пример:

char p=-8;
if(p==-8)Printf("Yea");
else Printf("No!");

выдаст в ваткоме "No!".  В то время как в mingw выдаёт: "Yea"

Согласно стандарту K&R  по дефолту какие типы знаковые или нет?  Кто нарушил стандарт - Watcom или mingw?

В настройках ваткома поставить галку, чтоб по дефолту было как надо:

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

#72
(Правка: 16:10) 16:08, 26 июля 2021

Gradius
> Согласно стандарту K&R  по дефолту какие типы знаковые или нет?

K&R:

При преобразовании символьных переменных в целые возникает один тонкий момент. Дело в том, что сам язык не указывает, должны ли переменным типа char соответствовать численные значения со знаком или без знака. Может ли при преобразовании char в int получиться отрицательное целое? К сожалению, ответ на этот вопрос меняется от машины к машине, отражая расхождения в их архитектуре. На некоторых машинах (PDP-11, например) переменная типа char, крайний левый бит которой содержит 1, преобразуется в отрицательное целое ("знаковое расширение"). На других машинах такое преобразование сопровождается добавлением нулей с левого края, в результате чего всегда получается положительное число.

#73
(Правка: 19:26) 19:24, 26 июля 2021

int - по стандарту знаковый.
char - не определено

С char ситуация сложнее. Стандарт устанавливает три различных типа: char, signed char, unsigned char. В частности, указатель типа (signed char *) не может быть неявно приведён к типу (char *).

Хотя формально это три разных типа, но фактически char эквивалентен либо signed char, либо unsigned char — на выбор компилятора (стандарт ничего конкретного не требует).

и да, в гцц это переключается с помощью -fsigned-char

#74
7:12, 27 июля 2021

kipar, gudleifr, спасибо!

Впредь буду иметь это ввиду, чтобы в самом начале вносить ясность и не натыкаться на "сюрпризы".

Продолжаю реверсить код игры.

1) В очередной раз пришлось столкнуться с неправильной работой программы, из-за того, что при декомпиляции неправильно привелись типы.

Проблемный код ниже:

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

Красным цветом подчёркнут неверный типкаст, из-за  чего происходила блокировка цикла (зацикливание навсегда) из-за того, что условие, отмеченное зелёным цветом, не срабатывало.

scrollsave[0] -это char. (8 бит)
scrollpos[0] - это short. (16 бит)

Hex-rays расширил размер операнда справа - в (unsigned int), из-за чего нарушился алгоритм программы:

Если чары знаковые, то код связанный со scroll_tube() не выполнялся.  А если чары беззнаковые, то происходило зацикливание в ветке выше (из-за невыполнения условия, помеченного  зелёным цветом).

Исправленный рабочий вариант такой (особо важен типкаст слева у HIBYTE(...)  ):

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

Это стоило мне много времени, пришлось раскрутить ассемблер проблемного куска и переписать его в Си.

Сразу стало видно где знаковые сравнения (JG, JL, ... ), а где беззнаковые (JA, JB,...)  В данном случае - беззнаковые сравнения:

Восстановленный вручную код из ассемблера - нижний цикл.  Массив имитирует фрейм стека:

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

2)  Процедура с нестандартной передачей параметров: 2 параметра через стек по типу __cdecl, а остальные через регистры:

вызов:

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

сама процедура:

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

Высчитал сколько параметров передаются дополнительно и сделал имитацию вызова на ассемблере вначале:

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

Затем избавился от ассемблера:

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

Всё работает!

3) Перечень новых декомпилированных и успешно отлаженных процедур:

1 | Дизассемблер IDA Pro 7.5 для восстановления исходного кода игры (C/C++)
Страницы: 14 5 6 712 Следующая »
ФлеймФорумПрограммирование