Pull to refresh

Дилетант и back-инжиниринг. Часть 2: Каркас

Reading time 15 min
Views 16K


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

Я пытаюсь восстановить исходники по .dll-библиотеке и .pdb-базе. Использование IDA конечно принесло кое-какие результаты, но не удовлетворительные. Возможно я просто недостаточно усидчив. Поэтому я начал с другой стороны — с восстановления каркаса проекта библиотеки. Так как у меня есть .pdb-база я вполне могу это сделать. Теоретически. Теоретически, потому что в базу записывается информация с препроцессированых файлов, а не с исходников. А значит нужно работать дальше.

Наполнение


Начну рассказ с теории. Структурно .pdb-база представляет собой набор символов (любая переменная, структура, функция, перечисление, тип, все это — символы) связанных между собой. Символы делятся по типам, и в зависимости от типа могу иметь разные свойства. Считывая свойства можно получить описание структур, функций, переопределений, перечислений, констант, включая связи между всем этим, имена файлов и .obj-модулей в которых находятся функции, и много чего еще. Для доступа к символам существует DIA SDK (Debug Interface Access), она хорошо документирована и разобраться с ней не очень сложно. Единственная «проблема» — DIA из коробки доступен только для C/С++, и если хочется работать на .Net нужно будет поработать перегоняя интерфейс в .Net .dll, но это другая история. Можно просто найти готовый модуль. Лично я выбрал второй вариант найдя Dia2Lib.dll, но в ней некоторые функции переведены неверно (вместо массива в параметре некоторых функций всунули простую переменную).

Возможно есть какое-то готовое решение для генерации кода по .pdb-базе, но я его не нашел. И теперь пишу свое. Пишу я на C#, меньше мороки с памятью, хотя и ценой удобства работы с файлами. Для начала нужны были классы для описания символов. Стандартные (те что из Dia2Lib) немного неудобны. Точнее если хочется вертеть данными в трех степенях свободы они просто этого не выдержат.
Классы для обработки данных символов
    class Member    {
        public string name;
        public int offcet;           //сдвиг поля
        public ulong length;         //размер поля в байтах
        public string type;          //полный тип поля, с указателями, константами, выравниваем и т.д.
        public string access;        //уровень доступа
        public uint id;              //для идентификации одинаковых типов
    }

    class BaseClass    {
        public string type;
        public int offcet;             //для порядка наследования
        public ulong length;
        public uint id;
    }

    class Function    {
        public string name;
        public string type;
        public string access;
        public string filename;    //имя файла, где находится функция
        public uint id;
    }

    class Typedef    {
        public string name;
        public string type;
        public uint id;
    }

    class Enum    {
        public string name;
        public uint id;

        public SubEnum[] values;
    }

    class SubEnum    {
        public string name;
        public dynamic value;
        public uint id;
    }

    class VTable    {
        public ulong count;      //размер таблицы
        public string type;
        public uint id;
    }

    class SubStructure    {
        public string name;
        public uint id;
    }

    class Structure    {
        public string name;
        public uint id;

        public Member[] members;
        public BaseClass[] baseclass;
        public Function[] functions;
        public Typedef[] typedefs;
        public Enum[] enums;
        public VTable[] vtables;
        public SubStructure[] substructures;
    }

Банальным перебором символов можно заполнить массивы этих структур и получить основу для каркаса. После начинаются проблемы. Первая проблема, о ней уже говорилось, в базе записаны все структуры с препроцессированых файлов. Как например такая:
Первый пример не очень нужной структуры
struct /*id:2*/ _iobuf
{
	/*off 0x00000000 size:0004 id:5*/ public: char * _ptr;
	/*off 0x00000004 size:0004 id:8*/ public: signed int  _cnt;
	/*off 0x00000008 size:0004 id:5*/ public: char * _base;
	/*off 0x00000012 size:0004 id:8*/ public: signed int  _flag;
	/*off 0x00000016 size:0004 id:8*/ public: signed int  _file;
	/*off 0x00000020 size:0004 id:8*/ public: signed int  _charbuf;
	/*off 0x00000024 size:0004 id:8*/ public: signed int  _bufsiz;
	/*off 0x00000028 size:0004 id:5*/ public: char * _tmpfname;
};

Мало кому может пригодится структура из стандартной библиотеки. Но если их еще можно как-то отслеживать, то есть пример похуже.
Второй пример не очень нужной структуры
struct /*id:24371*/ std::allocator<std::_Tree_nod<std::_Tmap_traits<int,std::basic_string<char,std::char_traits<char>,std::allocator<char> >,std::less<int>,std::allocator<std::pair<int const ,std::basic_string<char,std::char_traits<char>,std::allocator<char> > > >,0> >::_Node>:/*0x0 id:24351*/ std::_Allocator_base<std::_Tree_nod<std::_Tmap_traits<int,std::basic_string<char,std::char_traits<char>,std::allocator<char> >,std::less<int>,std::allocator<std::pair<int const ,std::basic_string<char,std::char_traits<char>,std::allocator<char> > > >,0> >::_Node>
{
	//
	/*id:24362*/ public: __thiscall const std::_Tree_nod<std::_Tmap_traits<int,std::basic_string<char,std::char_traits<char>,std::allocator<char> >,std::less<int>,std::allocator<std::pair<int const ,std::basic_string<char,std::char_traits<char>,std::allocator<char> > > >,0> >::_Node * address (const std::_Tree_nod<std::_Tmap_traits<int,std::basic_string<char,std::char_traits<char>,std::allocator<char> >,std::less<int>,std::allocator<std::pair<int const ,std::basic_string<char,std::char_traits<char>,std::allocator<char> > > >,0> >::_Node &);
	//
	/*id:24364*/ public: __thiscall std::_Tree_nod<std::_Tmap_traits<int,std::basic_string<char,std::char_traits<char>,std::allocator<char> >,std::less<int>,std::allocator<std::pair<int const ,std::basic_string<char,std::char_traits<char>,std::allocator<char> > > >,0> >::_Node * address (std::_Tree_nod<std::_Tmap_traits<int,std::basic_string<char,std::char_traits<char>,std::allocator<char> >,std::less<int>,std::allocator<std::pair<int const ,std::basic_string<char,std::char_traits<char>,std::allocator<char> > > >,0> >::_Node &);
	//
	/*id:24367*/ public: __thiscall void allocator<std::_Tree_nod<std::_Tmap_traits<int,std::basic_string<char,std::char_traits<char>,std::allocator<char> >,std::less<int>,std::allocator<std::pair<int const ,std::basic_string<char,std::char_traits<char>,std::allocator<char> > > >,0> >::_Node> (const std::allocator<std::_Tree_nod<std::_Tmap_traits<int,std::basic_string<char,std::char_traits<char>,std::allocator<char> >,std::less<int>,std::allocator<std::pair<int const ,std::basic_string<char,std::char_traits<char>,std::allocator<char> > > >,0> >::_Node> &);
	//
	/*id:24372*/ public: __thiscall void allocator<std::_Tree_nod<std::_Tmap_traits<int,std::basic_string<char,std::char_traits<char>,std::allocator<char> >,std::less<int>,std::allocator<std::pair<int const ,std::basic_string<char,std::char_traits<char>,std::allocator<char> > > >,0> >::_Node> ();
	//:d:\program files\microsoft visual studio .net 2003\vc7\include\xmemory
	/*id:24374 */public: void __thiscall std::allocator<struct std::_Tree_nod<class std::_Tmap_traits<int,class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >,struct std::less<int>,class std::allocator<struct std::pair<int const ,class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > > >,0> >::_Node>::deallocate(struct std::_Tree_nod<class std::_Tmap_traits<int,class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >,struct std::less<int>,class std::allocator<struct std::pair<int const ,class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > > >,0> >::_Node *,unsigned int);
	//
	/*id:24376*/ public: __thiscall std::_Tree_nod<std::_Tmap_traits<int,std::basic_string<char,std::char_traits<char>,std::allocator<char> >,std::less<int>,std::allocator<std::pair<int const ,std::basic_string<char,std::char_traits<char>,std::allocator<char> > > >,0> >::_Node * allocate (unsigned int ,const void *);
	//:d:\program files\microsoft visual studio .net 2003\vc7\include\xmemory
	/*id:24378 */public: struct std::_Tree_nod<class std::_Tmap_traits<int,class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >,struct std::less<int>,class std::allocator<struct std::pair<int const ,class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > > >,0> >::_Node * __thiscall std::allocator<struct std::_Tree_nod<class std::_Tmap_traits<int,class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >,struct std::less<int>,class std::allocator<struct std::pair<int const ,class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > > >,0> >::_Node>::allocate(unsigned int);
	//
	/*id:24380*/ public: __thiscall void construct (std::_Tree_nod<std::_Tmap_traits<int,std::basic_string<char,std::char_traits<char>,std::allocator<char> >,std::less<int>,std::allocator<std::pair<int const ,std::basic_string<char,std::char_traits<char>,std::allocator<char> > > >,0> >::_Node *,const std::_Tree_nod<std::_Tmap_traits<int,std::basic_string<char,std::char_traits<char>,std::allocator<char> >,std::less<int>,std::allocator<std::pair<int const ,std::basic_string<char,std::char_traits<char>,std::allocator<char> > > >,0> >::_Node &);
	//:d:\program files\microsoft visual studio .net 2003\vc7\include\xmemory
	/*id:24384 */public: void __thiscall std::allocator<struct std::_Tree_nod<class std::_Tmap_traits<int,class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >,struct std::less<int>,class std::allocator<struct std::pair<int const ,class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > > >,0> >::_Node>::destroy(struct std::_Tree_nod<class std::_Tmap_traits<int,class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >,struct std::less<int>,class std::allocator<struct std::pair<int const ,class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > > >,0> >::_Node *);
	//
	/*id:24386*/ public: __thiscall unsigned int  max_size ();

	structure /*id:24353*/ value_type;

	typedef /*id:24352*/std::_Allocator_base<std::_Tree_nod<std::_Tmap_traits<int,std::basic_string<char,std::char_traits<char>,std::allocator<char> >,std::less<int>,std::allocator<std::pair<int const ,std::basic_string<char,std::char_traits<char>,std::allocator<char> > > >,0> >::_Node> _Mybase;
	typedef /*id:24354*/std::_Tree_nod<std::_Tmap_traits<int,std::basic_string<char,std::char_traits<char>,std::allocator<char> >,std::less<int>,std::allocator<std::pair<int const ,std::basic_string<char,std::char_traits<char>,std::allocator<char> > > >,0> >::_Node * pointer;
	typedef /*id:24355*/std::_Tree_nod<std::_Tmap_traits<int,std::basic_string<char,std::char_traits<char>,std::allocator<char> >,std::less<int>,std::allocator<std::pair<int const ,std::basic_string<char,std::char_traits<char>,std::allocator<char> > > >,0> >::_Node & reference;
	typedef /*id:24357*/const std::_Tree_nod<std::_Tmap_traits<int,std::basic_string<char,std::char_traits<char>,std::allocator<char> >,std::less<int>,std::allocator<std::pair<int const ,std::basic_string<char,std::char_traits<char>,std::allocator<char> > > >,0> >::_Node * const_pointer;
	typedef /*id:24359*/const std::_Tree_nod<std::_Tmap_traits<int,std::basic_string<char,std::char_traits<char>,std::allocator<char> >,std::less<int>,std::allocator<std::pair<int const ,std::basic_string<char,std::char_traits<char>,std::allocator<char> > > >,0> >::_Node & const_reference;
	typedef /*id:24360*/unsigned int  size_type;
	typedef /*id:24361*/signed int  difference_type;

};

И даже если сделать фильтр на стандартные структуры-шаблоны, останется куча особенностей языка, которые разворачиваются или изменяются при трансляции. Как пример могу назвать пользовательские шаблоны.
Пример развертки шаблонов
struct /*id:16851*/ S_BVECTOR<D3DXVECTOR2>
{
	/*off 0x00000000 size:0016 id:9357*/ private: std::vector<D3DXVECTOR2,std::allocator<D3DXVECTOR2> > m_VECPath;
	/*off 0x00000016 size:0004 id:8*/ private: signed int  m_nCount;
	/*off 0x00000020 size:0004 id:8*/ private: signed int  m_nPos;

	/*id:9360 */public: __thiscall S_BVECTOR<struct D3DXVECTOR2>::S_BVECTOR<struct D3DXVECTOR2>(class S_BVECTOR<struct D3DXVECTOR2> const &);
	/*id:9362 */public: __thiscall S_BVECTOR<struct D3DXVECTOR2>::S_BVECTOR<struct D3DXVECTOR2>(void);
	/*id:9364 */public: void __thiscall S_BVECTOR<struct D3DXVECTOR2>::resize(unsigned short);
	/*id:9366*/ public: __thiscall void addsize (unsigned short );
	/*id:9368 */public: void __thiscall S_BVECTOR<struct D3DXVECTOR2>::setsize(unsigned short);
	/*id:9369*/ public: __thiscall void setsizeNew (unsigned short );
	/*id:9370 */public: void __thiscall S_BVECTOR<struct D3DXVECTOR2>::clear(void);
	/*id:9371 */public: void __thiscall S_BVECTOR<struct D3DXVECTOR2>::push_back(struct D3DXVECTOR2 &);
	/*id:9373*/ public: __thiscall void pop_front ();
	/*id:9374*/ public: __thiscall void pop_back ();
	/*id:9375 */public: int __thiscall S_BVECTOR<struct D3DXVECTOR2>::size(void);
	/*id:9377 */public: bool __thiscall S_BVECTOR<struct D3DXVECTOR2>::empty(void);
	/*id:9379*/ public: __thiscall D3DXVECTOR2 * front ();
	/*id:9381*/ public: __thiscall D3DXVECTOR2 * next ();
	/*id:9382*/ public: __thiscall D3DXVECTOR2 * end ();
	/*id:9383 */public: struct D3DXVECTOR2 * __thiscall S_BVECTOR<struct D3DXVECTOR2>::operator[](int);
	/*id:9385*/ public: __thiscall void remove (signed int );
	/*id:9387 */public: __thiscall S_BVECTOR<struct D3DXVECTOR2>::~S_BVECTOR<struct D3DXVECTOR2>(void);
	/*id:9388*/ public: __thiscall void * __vecDelDtor (unsigned int );
};

Конечно все можно достаточно легко вернуть к первоначальному виду. Но ситуаций где нужна ручная обработка может быть достаточно много. Например для той библиотеки, которую я хочу использовать, в базе записано 2673 структур. Из них только около 250 реально нужных, остальное — развертки шаблонов std и прочие «стандартные» вещи. Остаётся только надеяться что все пройдет без проблем. Ладно, предположим, что заготовки для структур есть. Дальше нужно позаписывать их по файлам.

Генерация


Для начала нужны сами файлы для записи. Немного теории. При компиляции каждый исходник с кодом после препроцессора переводится, с помощью компилятора, в машинные коды. С каждого исходника с кодом получается .obj-файл или .o-файл в зависимости от компилятора. При помощи DIA SDK можно получить список всех файлов с каждого .obj-модуля (короче весь список того, что подключаются в #include). О том, как получить список файлов, было рассказано в прошлой статье (ну как рассказано… в общем, там есть код). Если говорить языком дилетанта — из каждого .obj-модуля можно получить имя исходника, которым был модуль когда-то (у них будут имена одинаковые), и вель список подключаемых библиотек (сюда входят все файлы кроме .cpp, хотя бывают и исключения). После создания общей структуры, и связывания частей между собой, можно приступать к записи структур.

Напрямую получить имя файла, в котором была структура при ее существовании в виде исходника, насколько я знаю, нельзя. Но можно узнать по каким файлам была раскидана реальзация методов структуры. Поэтому я предлагаю просто собирать все файлы, в которые входят функции-методы, выбрать из них тот что будет хедером, записать туда описание, и связать остальные файлы с хедером. Но при получении имени исходника, в которой находится метод может быть неприятный или баг, или проявление ошибки файла. Для получения имени сначала нужно найти по RVA (relative virtual address) список линий исходников, и потом по этому списку линий найти файл в котором есть эти линии. Но иногда количество линий отвечающих методу равно нулю, но имя файла всё равно находится. Причем обычно неправильное имя. Обычно это проявляется при анализе конструктора. Может конструктор просто не рассматривать…
Пример структуры с битым расположением конструктора
// Над каждой функцией записано имя файла-исходника откуда функция родом. Файлы перед описанием структуры - просто перезапись всех исходников, но без повторений.

//e:\????\kop\project\mindpower\sdk\src\mpfont.cpp
//e:\????\kop\project\mindpower\sdk\src\i_effect.cpp
//e:\????\kop\project\mindpower\sdk\include\i_effect.h
struct /*id:9920*/ CTexList
{
	/*off 0x00000000 size:0002 id:1138*/ public: unsigned short  m_wTexCount;
	/*off 0x00000004 size:0004 id:1778*/ public: float  m_fFrameTime;
	/*off 0x00000008 size:0016 id:9726*/ public: std::vector<std::vector<D3DXVECTOR2,std::allocator<D3DXVECTOR2> >,std::allocator<std::vector<D3DXVECTOR2,std::allocator<D3DXVECTOR2> > > > m_vecTexList;
	/*off 0x00000024 size:0028 id:98*/ public: std::basic_string<char,std::char_traits<char>,std::allocator<char> > m_vecTexName;
	/*off 0x00000052 size:0004 id:8384*/ public: IDirect3DTexture8 * m_lpCurTex;
	/*off 0x00000056 size:0004 id:8130*/ public: MindPower::lwITex * m_pTex;

	//:e:\????\kop\project\mindpower\sdk\src\mpfont.cpp[0]
	/*id:9921*/ public: __thiscall void CTexList::CTexList (const CTexList &);
	//:e:\????\kop\project\mindpower\sdk\src\i_effect.cpp[3]
	/*id:9927*/ public: __thiscall void CTexList::CTexList ();
	//:e:\????\kop\project\mindpower\sdk\src\i_effect.cpp[2]
	/*id:9929*/ public: __thiscall void CTexList::~CTexList ();
	//:e:\????\kop\project\mindpower\sdk\src\i_effect.cpp[3]
	/*id:9930*/ public: __thiscall void CTexList::SetTextureName (const std::basic_string<char,std::char_traits<char>,std::allocator<char> > &);
	//:e:\????\kop\project\mindpower\sdk\src\i_effect.cpp[16]
	/*id:9932*/ public: __thiscall void CTexList::GetTextureFromModel (CEffectModel *);
	//:e:\????\kop\project\mindpower\sdk\src\i_effect.cpp[25]
	/*id:9934*/ public: __thiscall void CTexList::CreateSpliteTexture (signed int ,signed int );
	//:e:\????\kop\project\mindpower\sdk\src\i_effect.cpp[16]
	/*id:9936*/ public: __thiscall void CTexList::GetCurTexture (S_BVECTOR<D3DXVECTOR2> &,unsigned short  &,float  &,float );
	//:e:\????\kop\project\mindpower\sdk\src\i_effect.cpp[2]
	/*id:9938*/ public: __thiscall void CTexList::Reset ();
	//:e:\????\kop\project\mindpower\sdk\src\i_effect.cpp[7]
	/*id:9939*/ public: __thiscall void CTexList::Clear ();
	//:e:\????\kop\project\mindpower\sdk\src\i_effect.cpp[6]
	/*id:9940*/ public: __thiscall void CTexList::Remove ();
	//:e:\????\kop\project\mindpower\sdk\include\i_effect.h[12]
	/*id:9941*/ public: __thiscall void CTexList::Copy (CTexList *);
};

Обычно, что и не удивительно, структуры находятся в двух файлах, хедер.h и код.cpp, но есть и другие варианты. Например структура имеет только хедер, или файл с кодом представлен с расширением .inl, или структура вообще нигде, по мнению .pdb-базы, не записана. Я использовал следующий алгоритм. Если в списке файлов, в который входит структура, есть хедер — пишем структуру в хедер, и подключаем его к файлу с кодом, если он есть. Проходим по структуре, составляя список всех типов, что используются. Если тип — структура, и для нее есть список файлов — подключаем хедер этой структуры, иначе записываем эту структуру в начало файла. Есть еще один неприятный момент: структуры очень любят дублироваться. Не имею ни малейшего понятия, почему многие их них встречаются по несколько раз, причем одна за другой (на самом деле не одна за другой, между ними есть много стандартных шаблонов, но если включить фильтр, то одна за другой). При этом свойства\методы у таких структур совпадают, а отличаются они только порядковым номером. Лично я просто отсортировал массив с структурами за именами структур, и при переборе всех элементов сравнивал имя текущего с именем предыдущего. И все заработало.

Результат


Хоть все и заработало, но, естественно, не так как хотелось бы. Оно конечно насоздавало кучу файлов, которые в общем отображали, как я надеюсь, структуру исходного проекта, но там такая каша…
Один из сгенерированных файлов - lwitem.h
//Для удобства читания и уменьшения обьема текста методы удалены
#ifndef __MINDPOWER::LWITEM__
#define __MINDPOWER::LWITEM__

#ifndef _MINDPOWER::LWIRESOURCEMGR_
#define _MINDPOWER::LWIRESOURCEMGR_
struct MindPower::lwIResourceMgr:MindPower::lwInterface
{
	//57 методов
};
#endif

#ifndef _MINDPOWER::LWISCENEMGR_
#define _MINDPOWER::LWISCENEMGR_
struct MindPower::lwISceneMgr:MindPower::lwInterface
{
	//15 методов
};
#endif

#ifndef _MINDPOWER::LWLINKCTRL_
#define _MINDPOWER::LWLINKCTRL_
struct MindPower::lwLinkCtrl
{
	//3 меода
};
#endif
#include lwitypes2.h

#ifndef _STD::ALLOCATOR<STD::BASIC_STRING<CHAR,STD::CHAR_TRAITS<CHAR>,STD::ALLOCATOR<CHAR> > >::REBIND<T>_
#define _STD::ALLOCATOR<STD::BASIC_STRING<CHAR,STD::CHAR_TRAITS<CHAR>,STD::ALLOCATOR<CHAR> > >::REBIND<T>_
struct std::allocator<std::basic_string<char,std::char_traits<char>,std::allocator<char> > >::rebind<T>
{
	typedef std::allocator<std::_List_nod<std::basic_string<char,std::char_traits<char>,std::allocator<char> >,std::allocator<std::basic_string<char,std::char_traits<char>,std::allocator<char> > > >::_Node *> other;
};
#endif

#ifndef _MINDPOWER::LWIPRIMITIVE_
#define _MINDPOWER::LWIPRIMITIVE_
struct MindPower::lwIPrimitive:MindPower::lwInterface
{
        //46 методов
};
#endif
#include d3dx8math.h

#ifndef _STD::_NUM_FLOAT_BASE_
#define _STD::_NUM_FLOAT_BASE_
struct std::_Num_float_base:std::_Num_base
{
        //16 свойств-констант
};
#endif

#ifndef _MINDPOWER::LWIITEM_
#define _MINDPOWER::LWIITEM_
struct MindPower::lwIItem:MindPower::lwInterface
{
        //26 методов
};
#endif

#ifndef _MINDPOWER::LWITEM_
#define _MINDPOWER::LWITEM_
struct MindPower::lwItem:MindPower::lwIItem
{
        //12 свойств
        //34 метода
};
#endif
#endif

Основные ошибки: нету namespace-ов, нету фильтра для стандартных шаблонов и замены их на подключения библиотек, нету внутренней структуры файла, автор дно и пусть не генерит говнокод и многое другое. Короче работы тут еще завалом. На этом пока все, код генератора я прилагаю, может кому-то он пригодится.
Дилетантский кодогенератор на гитхабе

Такие дела.
Tags:
Hubs:
+13
Comments 3
Comments Comments 3

Articles