Search
Write a publication
Pull to refresh

Comments 5

Ага. Интересно.

Можно пример декомпиляции кода на VB6 с вызовом внешних функций из С библиотек. С войной с соглашениями вызовов и угадыванием сигнатур?

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

Использование LLVM для целей декомпиляции же я считаю вообще ошибкой, эта штука проектировалась для ОПТИМИЗАЦИИ, а в этой задаче нужна ДЕОПТИМИЗАЦИЯ - для человекочитаемости. Нужно распутивать граф управления в том числе дублированием блоков кода (это насколько знаю умеет только fernflower), приводить switch case к нормальному виду из мешанины if и прочее.

Ни один из декомпиляторов нормально не поддерживает объектный и шаблонный код на С++, результат - месиво просто.

Тут совсем так немножечко решил написать свой декомпилятор и дизассемблер:пока только для x16-x32 - "заболел" ретро играми (DOS, Windows, Atari)

Часть из моего ТЗ

10.1. Функциональные критерии

  • Корректное декодирование инструкций x86 (16/32 бита) на реальных бинарных примерах, включая все addressing modes, префиксы, переходы, вызовы, арифметику, работу с памятью, стеком, портами, флагами, прерываниями.

  • Соответствие формата вывода выбранному синтаксису (по умолчанию — Intel/MASM/TD), поддержка всех вариантов записи операндов, префиксов, сегментов, смещений, литералов.

  • Корректная работа виртуализации (отображение только видимых строк), отсутствие артефактов, задержек, ошибок при прокрутке, обновлении диапазона.

  • Корректная интеграция с кастомным hex-редактором, поддержка всех событий, навигации, выделения, обновления данных.

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

  • Подробные логи для отладки, анализ ошибок, предупреждений, событий, производительности.

  • Документация и примеры использования для всех основных сценариев, модулей, интерфейсов, расширений.

10.2. Нефункциональные критерии

  • Время декодирования 1000 инструкций — не более 100 мс на среднестатистическом ПК (Intel i5, 8 ГБ RAM), при больших объёмах — линейное масштабирование.

  • Время обновления диапазона строк — не более 50 мс, отсутствие заметных задержек при прокрутке, навигации.

  • Потребление памяти — не более 50 МБ на 1 МБ кода, оптимизация хранения промежуточных данных.

  • Корректная обработка ошибок и исключений, отсутствие сбоев, утечек памяти, зависаний.

  • Соответствие архитектурным принципам (разделение логики и UI, использование интерфейсов, абстракций, точек расширения).

...
18.4. Ограничения и реалистичные цели

Создание такого анализатора — задача, сравнимая по сложности с разработкой компилятора или переводчика между двумя сложнейшими языками. x86-ассемблер допускает произвольные переходы, самомодифицирующийся код, нестандартные прологи/эпилоги, что делает автоматическое определение границ и назначения функций крайне сложным. Даже лучшие инструменты (IDA, Ghidra) не всегда справляются идеально и всегда оставляют место для ручной корректировки.

В связи с этим:

  • Анализатор позиционируется как "помощник", а не "заменитель эксперта".

  • Основная задача — автоматизация рутинных операций (поиск функций, построение call graph, подсказки по паттернам), а не "магическое" понимание смысла любого кода.

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

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

  • Вся работа анализатора должна быть прозрачной для пользователя: показывать, почему принято то или иное решение, давать возможность "откатить" или скорректировать результат.

18.5. Польза для реверса и обучения

  • Существенно ускоряет первичный разбор неизвестного бинарного кода.

  • Помогает быстро выявить ключевые функции, точки входа, связи между частями программы.

  • Позволяет отслеживать реальные пути исполнения, видеть "живое" поведение функций.

  • Делает процесс обучения ассемблеру более наглядным: пользователь видит, как реально исполняется код, какие участки "живые", а какие — только "на бумаге".

  • Обеспечивает удобную платформу для накопления и обмена знаниями (база сигнатур, паттернов, пользовательских комментариев).

mov eax, [ebx]
add eax, 4
call eax

Это даже не боль - мелочь кошачья и легко понимается

  1. Берём значение из памяти по адресу в ebx → кладём в eax

  2. Прибавляем 4 к этому значению.

  3. Вызываем функцию по получившемуся адресу.

Такой шаблон очень характерен для C++ при вызове виртуальных функций через полиморфный объект

class Animal {
  public:
    virtual void speak() { cout << "Animal sound\n"; }
    virtual void move() { cout << "Animal moves\n"; }  // ← это вторая виртуальная функция
 };

Animal* animal = new Dog();
animal->move();  // ← вот этот вызов может компилироваться в эти 3 строки

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

Так я исходил из того, что знаю. Есть два пути, короткий и длинный:
1. Короткий:
1.1. То, что я написал и без вариантов

2. Длинный
2.1. Открываем DIE (Github)
2.2. Открываем ImHex (Github)
Согласно пунктам 2.1. и 2.2. получили базовую инфу о программе
2.3. Если у нас C то:

С (пример)
struct GameModule {
    void (*init)();
    void (*update)();
    void (*render)();
};

GameModule* mod = ...;
mod->update(); // → mov eax, [ebx] → add eax, 4 → call eax

2.4. Если у нас C++ - пример уже написан
2.5. Есть вариант что это указатели на функции, которые хранятся в структуре, загруженной из DLL, но обычно, они так редко записываются
3. Открываем Гидру, Иду или другое...

Итог:
Если видишь, что ebx указывает на объект, а [ebx] — на глобальную таблицу, и так везде — тогда можно сказать: "Да, это vtable." В противном случае лучше сказать: "индиректный вызов через таблицу функций".

Sign up to leave a comment.

Articles