Плагин ida_medigate C++ для IDA Pro
[ТОС]
Реверс-инжиниринг скомпилированного кода C++ — занятие неинтересное. Статическое обратное проектирование скомпилированного кода C++ разочаровывает. Основная причина, которая усложняет задачу, — это виртуальные функции. В отличие от скомпилированного кода C, здесь нет четкого потока кода. Слишком часто можно потратить много времени, пытаясь понять, как будет вызываться следующая виртуальная функция, вместо того, чтобы просто увидеть функцию, как в скомпилированном коде C.
Когда кто-то исследует виртуальную функцию, количество времени, которое ему или ей нужно потратить, чтобы найти ее внешнюю ссылку, не имеет смысла.
После слишком большого количества проектов C++ RE я сдался и решил, что мне нужен гибкий (Python) и стабильный (который я могу легко поддерживать) инструмент для такого типа исследований. Большая часть этого плагина была написана в январе 2018 года, а недавно я решил почистить пыль и добавить поддержку новых классов IDA (7.2).
Этот плагин не предназначен для постоянной работы «из коробки», а является еще одним инструментом для реверсера.
Плагин состоит из двух частей:
Эта первая часть не зависит от второй части, поэтому плагин можно использовать для обратного проектирования двоичного файла, не содержащего RTTI, путем определения этих классов вручную на основе API плагина.
Уникальность плагина заключается в том, что он использует ту же среду, с которой уже знаком исследователь, не добавляет никаких новых меню или объектов и основан на известных строительных блоках IDA (структура, объединение, тип для членов структуры и т. д.). ) — это позволяет плагину поддерживать абстрагирование C++ для каждой архитектуры, которую поддерживает IDA .
Примечание. Анализатор RTTI анализирует RTTI x86/x64 g++, но его структура позволяет легко добавить поддержку большего количества архитектур и компиляторов.
plugins/
). Скопируйте medigate_cpp_plugin
в каталог plugins
и добавьте путь к исходному коду в файл idapythonrc.py
Скопируйте plugins/ida-referee/referee.py
в тот же каталог.
Предполагая, что исходный двоичный исходный код имеет следующий вид ( examples/a.cpp
):
using namespace std ;
class A {
public:
int x_a;
virtual int f_a ()=0;
};
class B : public A {
public:
int x_b;
int f_a (){x_a = 0 ;}
virtual int f_b (){ this -> f_a ();}
};
class Z {
public:
virtual int f_z1 (){cout << " f_z1 " ;}
virtual int f_z2 (){cout << " f_z2 " ;}
};
class C : public B , public Z {
public:
int f_a (){x_a = 5 ;}
int x_c;
int f_c (){x_c = 0 ;}
virtual int f_z1 (){cout << " f_z3 " ;}
};
int main ()
{
C *c = new C ();
c-> f_a ();
c-> f_b ();
c-> f_z1 ();
c-> f_z2 ();
return 0 ;
}
Двоичный файл удален, но содержит RTTI.
Когда мы просто загружаем бинарный файл, main
функция ( sub_84D
в 32-битной версии) выглядит так:
Запустите анализатор RTTI g++ и запустите его, используя:
from ida_medigate.rtti_parser import GccRTTIParser
GccRTTIParser.init_parser()
GccRTTIParser.build_all()
Теперь обновите структуру C (см. раздел «Примечания»), приведите v0
к C *
и снова декомпилируйте:
В случаях, когда RTTI отсутствует, наша инфраструктура по-прежнему позволяет вручную определить класс C++. Для того же примера (examples/a32_striped) вы можете вручную создать структуру B, затем выбрать ее виртуальную таблицу и ввести
from ida_medigate import cpp_utils
cpp_utils.make_vtable("B")
make_vtable
также может получить vtable_ea
и vtable_ea_stop
вместо выбранной области.
Затем создайте структуру C и примените наследование:
cpp_utils.add_baseclass("C", "B")
Теперь вы можете перестроить виртуальную таблицу класса C, выбрав ее и набрав:
cpp_utils.make_vtable("C")
Добавьте структуру Z, перестройте ее виртуальную таблицу, а теперь самое интересное:
cpp_utils.add_baseclass("C", "Z", 0x0c, to_update=True)
, которые применяют наследование C от Z по смещению 0x0c, а также обновляют структуру (см. примечания).
Последнее, что осталось, это обновить вторую виртуальную таблицу C, ту, которая реализует интерфейс Z. Отметьте эту виртуальную таблицу и введите:
cpp_utils.make_vtable("C", offset_in_class=0x0c)
ida_medigate знает, что эта виртуальная таблица является виртуальной таблицей класса Z, и результат будет:
Конечный результат такой же, как и в случае с RTTI:
Когда создается новая структура vtable (анализатором RTTI или вручную пользователем), каждая функция, которая еще не изменилась, переименовывается, декомпилируется и присваивает своему первому аргументу значение this
.
Двойной щелчок по члену структуры, который соответствует такой функции, приведет к переходу к этой функции, и пользователь сможет прочитать поток кода C++ удобным способом!
Каждое изменение имени или типа функции или соответствующего члена-указателя функции в виртуальных таблицах перехватывается и синхронизируется между всеми ними. Это означает, например, что пользователь может изменить тип элемента vtable через окно декомпилятора, и этот новый тип (прототип) также будет применен к целевой функции.
В строке 15 на предыдущем изображении есть вызов B::sub_9A8 (B::f_b в исходном коде). Этот аргумент функции равен B *
:
Но эта функция также может быть вызвана экземпляром C
(приведение вверх). мы хотим увидеть виртуальную функцию, которую будет вызывать ее экземпляр. Предположим, что существует много потенциальных производных классов, поэтому приведение this
к C *
не всегда возможно. По этой причине мы реализуем объединение для каждого базового класса, у которого есть сыновья, имеющие другую виртуальную таблицу. Можно выбрать отображение другой производной виртуальной таблицы производных B
, нажав alt+y (ярлык для выбора другого члена объединения):
поэтому в конечном итоге мы можем «приводить» только определенные вызовы к различным виртуальным функциям:
Святой Грааль разочарованных реверс-инженеров C++. Мы сохраняем внешние ссылки от виртуальных функций к членам структуры vtable, которая их представляет!
Объединение этого с ida-referee
позволяет нам отслеживать все внешние ссылки вызовов виртуальных функций!
Ограничение: мы можем отслеживать только те виртуальные вызовы, которые уже декомпилированы. К счастью, автоанализ умеет заполнять тип аргумента между функциями, поэтому с помощью итеративного процесса приведения большего количества аргументов -> декомпиляции всех соответствующих функций -> повторного чтения кода и приведения большего количества аргументов (...) эта возможность становится действительно мощный!
То, как мы отмечаем члены структуры как подклассы в IDAPython, не синхронизируется сразу с IDB. Наш хак заключается в том, чтобы отредактировать структуру, чтобы запустить синхронизацию. Вы также можете использовать
utils.refresh_struct(struct_ptr)
который добавляет фиктивное поле в конец структуры, а затем отменяет его определение.