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



Минулого разу я описав початок моїх відносин із зворотного розробкою. Минуло ще трохи часу і ось, в деякій мірі, результат моїх досліджень.

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

Наповнення


Почну розповідь з теорії. Структурно .pdb-база являє собою набір символів (будь-яка змінна, структура, функція, перерахування, тип, все це — символи) пов'язаних між собою. Символи поділяються за типами, і в залежності від типу можу мати різні властивості. Зчитуючи властивості можна отримати опис структур, функцій, переопределений, перерахувань, констант, включаючи зв'язки між усім цим, імена файлів і .obj-модулів в яких знаходяться функції, і багато чого ще. Для доступу до символів існує DIA SDK (Debug Access Interface), вона добре документована і розібратися з нею не дуже складно. Єдина «проблема» — 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-файл або.о-файл в залежності від компілятора. За допомогою 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 і код.срр, але є й інші варіанти. Наприклад структура має тільки хедер, або файл з кодом представлений з розширенням .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-ів, немає фільтру для стандартних шаблонів і заміни їх на підключення бібліотек, немає внутрішньої структури файлу, автор дно і нехай не генерує говнокод і багато іншого. Коротше роботи тут ще завалом. На цьому поки все, код генератора я докладаю, може комусь він стане у нагоді.
Дилетантський кодогенератор на гітхабі

Такі справи.

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.