C++ Class Reversing
Плагин Class Informer собирает имена классов в сводную таблицу, которая помогает перемещаться между классами.
Для примера рассмотрим класс CPrpSolids, двойной клик переносит нас к следующей структуре,
Нулевой элемент на который имеется обращение из кода, является базовым элементом таблицы vftable.
Класс CPrpSolids наследуется от двух классов CPrpDataObject и tObject, поэтому часть вызовов в таблице относится к родительским классам. Часто родители обеспечивают некоторую общую функциональность класса, а собственные вызовы функций класса помогают разобраться с деталями реализации класса. Мне удобно разделить таблицу вызовов на собственные и унаследованные вызовы.
Для этого обратимся к первому родительскому классу tObject,
класс не имеет родителя и это очень хорошо. Переименовав имена функций базового класса, можно будет понять какие функции класс потомок наследует от родительского класса.
Второй класс CPrpDataObject наследуется от tObject, поэтому в таблице вызовов мы видим размеченные ранее вызовы,
Переименуем собственные вызовы CPrpDataObject,
И возвращаясь к CPrpSolids увидим собственные и унаследованные функции.
Таблицы vftable формируют цепочку наследования функций, но не являются описанием класса. Мы ничего пока не знаем о том как хранятся и изменяются переменные класса.
Процесс создания объекта класса, поможет понять какие переменные относятся к классу. Для этого найдем ссылки на vftable в коде (нажав кнопку X), где мы скорее всего обнаружим динамическое выделение памяти под экземпляр класса.
Анализатор IDA обнаружил две ссылки на vftable. Вторая ссылка приводит к нас к конструктору класса,
Обратите внимание, что переменной (*a1) сначала присваивается таблица вызовов CPrpDataObject, затем происходит повторное присваивание таблицы вызовов CPrpSolids. Так обеспечивается цепочка вызовов конструкторов базовых классов и так можно проследить иерархию формирования класса от родительских классов.
Конкретный тип класса определяется по последнему присваиванию перед return.
Определив конструктор класса CPrpSolids, посмотрим как он используется,
Сначала запрашивается требуемый объем памяти, затем проверяется действительно ли память была выделена и вызывается конструктор. По объему запрашиваемой памяти устанавливается размер экземпляра класса 144 байт, причем первые 4 из них хранят ссылку на vftable класса.
Создадим структуру CPrpSolids размером 144 байт. Обычно я создаю пустую структуру, которую наполняю данными размерами 8 байт.
Если требуется создать большую структуру, можно создать массив из элементов по 8 байт.
Первое поле field_0 это ссылка на таблицу vftable, можно задать ей общий тип (void*), который подходит для любого класса,
Для повышения читаемости, можно вместо void* указать ссылку на CPrpSolids::`vftable’, но к сожалению я так и не научился этого делать.
Определив структуру CPrpSolids теперь можно вернутся к конструктору и переименовать прототип функции от безличного типа QWORD к конкретному классу,
После последнего присваивания a1->vftable вызывается конструктор CPrpSolids в котором обычно инициализируются переменные.
Появление LODWORD подсказывает, что в коде происходит обращение к младшим 4 байтам исходно 8 байтовой структуры. Обычно следуя листингу, ввожу переменную более низкой разрядности, чтобы избежать появления LODWORD и HIDWORD. Инициализация переменной field_8 и далее некоторым длинным, ненулевым значением подсказывает, что мы имеем дело с плавающей точкой. Заменяя целочисленный 8 байтовый тип на 8 байтный тип Double, получим такой листинг.
Последнее улучшение это выяснить действительно ли функция возвращает значение. Для этого надо просмотреть как используется функция, используется ли функция с правой стороны вычислений или вызовах функций. Если такого не обнаруживается, правой кнопкой мышки выбираем remove return value. Прототип функции поменяется на void.
Следующая ссылка на таблицу вызовов CPrpSolids приводит нас к более масштабному коду,
Прослеживая изменения (*a1) определим, что мы имеем дело с классом CPrpSIN, часть которого содержит в себе класс CPrpSolids, который начинается с 6612 байта начиная от адреса (*a1). После назначения, вызывается уже знакомая sub_180749, в которой инициализируется структура CPrpSolids.
Прослеживая судьбу не только объекта класса CPrpSolids, но и обращение к объекту класса CPrpSIN позволяет понять, как используется в коде изучаемый класс.
Возвращаясь к таблице vftable собственных вызовов CPrpSolids, определим каким образом функция класса узнает о объекте класса. Конкретно для этой программы, выполняется простое наблюдение, что функция всегда принимает первым аргументом ссылку на объект. Рассмотрим первую функцию из vftable.
Появление free и уже знакомого размера 144 байт, подсказывает, что мы находимся в деструкторе класса, задача которого освободить выделенную ранее память. IDA не может знать с каким объектом работает функция, поэтому переименуем тип первого аргумента и переименуем в this.
Полезно провести уточнение типа для всех собственных функций класса и проследить за логикой использования переменных внутри класса.
Для данной программы мне повезло, одна из функций класса работает со строковыми переменными, которые позволяют восстановить исходные имена.
Немного подправив прототип функции sub_171936, получим имена переменных связанных со структурой,
Так мы смогли достать переменные класса CPrpSolids и познакомится с тем как реализуется концепция классов и объектов, внутри некоторой программы.