Представьте себе, вам необходимо доработать некую очень полезную программу без SDK, но по счастливому стечению обстоятельств рядом завалялся PDB файл.

(Беременным и детям не читать!)

Скажу сразу, выход есть (Ваш КО). То что комитет не в состоянии осилить десятками лет (рефлексия не нужна), ужасный M$ разработал/раздобыл 100 лет назад, а именно DIA SDK. В комплекте есть DIA2Dump.exe который порадует глаз любого художника. Остается доработать его напильником…

Для начала нам нужны кошечки.

ООП баян:
#define NOINLINE __declspec(noinline)

class IDrawable {
public:
   virtual void draw() = 0;
};

class Shape : public IDrawable {
public:
   NOINLINE Shape(int ix, int iy);
   NOINLINE virtual ~Shape();
   virtual void draw();
   inline void setXY(int ix, int iy) { x = ix, y = iy; }
   NOINLINE void someWork();
   int x;
   int y;
};

class Circle : public Shape {
public:
   NOINLINE Circle(int ix, int iy, int ir);
   virtual ~Circle();
   void draw();
   int r;
};

Shape::Shape(int ix, int iy) { x = ix; y = iy; }
Shape::~Shape() { }
void Shape::someWork() { printf("someWork\n"); }
void Shape::draw() { }

Circle::Circle(int ix, int iy, int ir): Shape(ix, iy), r(ir) { }
Circle::~Circle() { }
void Circle::draw() { printf("Circle\n"); }

int main()
{
    auto c = new Circle(0, 0, 10);
    c->draw();
    c->someWork();
    delete c;

    getchar();
    return 0;
}


Компилируем -> victim.exe / victim.pdb

Автор своими кривыми ручками немного доработал DIA2Dump (pdb-ripper на гитхабе, Achtung г*вн*код!!!) и теперь он выдает кое-что пригодное к использованию:

DIA2Dump.exe -rip -printCppProxy -m -g -d -rd -names "Shape;Rectangle;Circle" victim.pdb > victim.h

IDrawable
//UDT: class IDrawable @len=8 @vfcount=1
	//_VTable
	
	//@intro @pure @virtual vtpo=0 vfid=0 @loc=optimized @len=0 @rva=0
	//_Func: public void draw();
	
	//@loc=optimized @len=0 @rva=0
	//_Func: public void IDrawable(IDrawable * _arg0); 
	
	//@loc=optimized @len=0 @rva=0
	//_Func: public void IDrawable(const IDrawable & _arg0); 
	
	//@loc=optimized @len=0 @rva=0
	//_Func: public void IDrawable(); 
	
	//@loc=optimized @len=0 @rva=0
	//_Func: public IDrawable & operator=(IDrawable * _arg0); 
	
	//@loc=optimized @len=0 @rva=0
	//_Func: public IDrawable & operator=(const IDrawable & _arg0); 
//UDT;

class IDrawable {
public:
	void* _vtable;
	
	inline void draw() { 
		typedef void (IDrawable::*_fpt)(); 
		auto _f=xcast<_fpt>(get_vfp(this, 0)); 
		return (this->*_f)(); 
	}
	
	inline IDrawable * ctor() { return this; }
	inline void dtor() {}
};


Shape
//UDT: class Shape @len=16 @vfcount=2
	//_Base: class IDrawable @off=0 @len=8
	
	//@loc=optimized @len=0 @rva=0
	//_Func: public void Shape(const Shape & _arg0); 
	
	//@loc=static @len=20 @rva=4208
	//_Func: public void Shape(int ix, int iy); 
	
	//@intro @virtual vtpo=0 vfid=1 @loc=static @len=11 @rva=4304
	//_Func: public void ~Shape(); 
	
	//@virtual vtpo=0 vfid=0 @loc=static @len=3 @rva=4336
	//_Func: public void draw(); 
	
	//@loc=optimized @len=0 @rva=0
	//_Func: public void setXY(int _arg0, int _arg1); 
	
	//@loc=static @len=12 @rva=4320
	//_Func: public void someWork(); 
	
	//@loc=optimized @len=0 @rva=0
	//_Func: public Shape & operator=(const Shape & _arg0); 
	
	//@intro @virtual vtpo=0 vfid=1 @loc=optimized @len=0 @rva=0
	//_Func: public void * __vecDelDtor(unsigned int _arg0); 
	
	//_Data: this+0x8, Member, Type: int, x
	//_Data: this+0xC, Member, Type: int, y
//UDT;

class Shape : public IDrawable {
public:
	int x;
	int y;
	
	inline Shape * ctor(int ix, int iy) { 
		typedef Shape * (Shape::*_fpt)(int, int); 
		auto _f=xcast<_fpt>(_drva(4208)); 
		return (this->*_f)(ix, iy); 
	}
	
	inline void dtor() { 
		typedef void (Shape::*_fpt)(); 
		auto _f=xcast<_fpt>(get_vfp(this, 1)); 
		(this->*_f)(); 
	}
	
	inline void draw_impl() { 
		typedef void (Shape::*_fpt)(); 
		auto _f=xcast<_fpt>(_drva(4336)); 
		return (this->*_f)(); 
	}
	
	inline void draw() { 
		typedef void (Shape::*_fpt)(); 
		auto _f=xcast<_fpt>(get_vfp(this, 0)); 
		return (this->*_f)(); 
	}
	
	inline void someWork() { 
		typedef void (Shape::*_fpt)(); 
		auto _f=xcast<_fpt>(_drva(4320)); 
		return (this->*_f)(); 
	}
};


Circle
//UDT: class Circle @len=24 @vfcount=2
	//_Base: class Shape @off=0 @len=16
	
	//@loc=optimized @len=0 @rva=0
	//_Func: public void Circle(const Circle & _arg0); 
	
	//@loc=static @len=34 @rva=4352
	//_Func: public void Circle(int ix, int iy, int ir); 
	
	//@virtual vtpo=0 vfid=1 @loc=optimized @len=0 @rva=0
	//_Func: public void ~Circle(); 
	
	//@virtual vtpo=0 vfid=0 @loc=static @len=12 @rva=4464
	//_Func: public void draw(); 
	
	//@loc=optimized @len=0 @rva=0
	//_Func: public Circle & operator=(const Circle & _arg0); 
	
	//@intro @virtual vtpo=0 vfid=1 @loc=optimized @len=0 @rva=0
	//_Func: public void * __vecDelDtor(unsigned int _arg0); 
	
	//_Data: this+0x10, Member, Type: int, r
//UDT;

class Circle : public Shape {
public:
	int r;
	
	inline Circle * ctor(int ix, int iy, int ir) { 
		typedef Circle * (Circle::*_fpt)(int, int, int); 
		auto _f=xcast<_fpt>(_drva(4352)); 
		return (this->*_f)(ix, iy, ir); 
	}
	
	inline void dtor() { 
		typedef void (Circle::*_fpt)(); 
		auto _f=xcast<_fpt>(get_vfp(this, 1)); 
		(this->*_f)(); 
	}
	
	inline void draw_impl() { 
		typedef void (Circle::*_fpt)(); 
		auto _f=xcast<_fpt>(_drva(4464)); 
		return (this->*_f)(); 
	}
	
	inline void draw() { 
		typedef void (Circle::*_fpt)(); 
		auto _f=xcast<_fpt>(get_vfp(this, 0)); 
		return (this->*_f)(); 
	}
};


Что тут интересного?

//UDT: class IDrawable @len=8 @vfcount=1

//@intro @pure @virtual vtpo=0 vfid=0 @loc=optimized @len=0 @rva=0
//_Func: public void draw();
	
// @intro --> introducing virtual function (метод объявлен впервые)
// @pure virtual --> очевидно
// @vtpo --> virtual table pointer offset (крестопроблемы, может быть несколько VT)
// @vfid --> virtual function id in VT
// @loc=optimized --> вырезано за ненадобностью
// @rva=0 --> relative virtual address

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

class IDrawable {
	// без VT никуда
	void* _vtable;
	
	// this->_vtable[func_id]()
	inline void draw() { 
		typedef void (IDrawable::*_fpt)();
		auto _f=xcast<_fpt>(get_vfp(this, 0)); 
		return (this->*_f)(); 
	}
};

// THIS IS SPARTA!!!
__forceinline void* get_vtp(void* obj) { 
	return *((void**)obj); 
}
__forceinline void* get_vfp(void* obj, size_t id) { 
	return *((void**)((uint8_t*)get_vtp(obj) + id * sizeof(void*))); 
}

Для невиртуальных методов необходимо извлечь RVA из PDB файла и ткнуть носом компилятор:

//UDT: class Shape @len=16 @vfcount=2

//@loc=optimized @len=0 @rva=0 <--- тут ничего не выгорит, компилятор заинлайнил метод
//_Func: public void setXY(int _arg0, int _arg1); 

//@loc=static @len=12 @rva=4320 <--- ВОТ ОН, ЗДОРОВЕННЫЙ ЯЗЬ
//_Func: public void someWork(); 

class Shape : public IDrawable {
	inline void someWork() { 
		typedef void (Shape::*_fpt)(); 
		auto _f=xcast<_fpt>(_drva(4320)); 
		return (this->*_f)(); 
	}
};

Что такое _drva? Мы знаем только RVA (смещение) функции относительно базы, а нужен полноценный виртуальный адрес:

void* _image_base = GetModuleHandleA(nullptr); // hello DYNAMICBASE

__forceinline void* _drva(size_t off) { 
	return ((uint8_t*)_image_base) + off; 
}

Зачем xcast? Методы в плюсах вызываются по __thiscall и необходимо доступно объяснить компилятору кто тут самый умный. По факту кастуем void* в указатель на метод, this передается скрыто первым параметром:

typedef void (Shape::*_fpt)(); 
auto _f=xcast<_fpt>(_drva(4320)); 
(this->*_f)();
		
// NUCLEAR MINEFIELD!!!
template<typename TOUT, typename TIN>
__forceinline TOUT xcast(TIN in)
{
    union
    {
        TIN in;
        TOUT out;
    }
    u = { in };
    return u.out;
}

Далее нужно внедрить наш собственный код в процесс victim.exe используя стандартные техники которые тут обсуждаться не будут. Смысл один — некий код будет выполнен в адресном пространстве victim.exe.

void injected_func() {
	void* _image_base = GetModuleHandleA(nullptr);
	Circle* obj = (Circle*)malloc(sizeof(Circle));
	obj->ctor(0, 0, 999);
	((IDrawable*)obj)->draw();
	obj->dtor();
	free(obj);
}

Остается хукнуть удобный метод/функцию где есть доступ к нужным объектам… Или найти глобальные переменные :D

PROFIT…

===

Как эта фигня используется в реальности:

Z2l0aHViLmNvbS93b25nZmVpL2FjLXBsdWdpbg==

aHR0cHM6Ly93d3cueW91dHViZS5jb20vd2F0Y2g/dj1SZUg1U0tmUEtTOA==

Далее автор упоролся по хардкору и решил не просто доработать некую очень полезную программу, а отреверсить ее полностью :D