既然要玩逆向,驱动那我们就算不懂但至少要了解下C/C++语言,C++是一门支持OO的语言,对面想对象的软件开发提供了丰富的支持库.但要高效 正确的使用C++中的继承 多态等语言的特性,必须对这些特性性的底层实现有一定得了解.
其实就核心概念而言,C++的对象模型的核心概念并不多,但最核心的是虚函数.虚函数是在程序运行时刻定义的函数,虚函数的地址是不能在编译时刻确定的,它只能在调用即将进行之前加以确定.对所有虚函数引用通常放在一个专用数组--虚函数表中,每个至少使用一个虚函数的对象里面都具有虚函数表指针.虚函数通常通过指向虚函数表的指针间接的加以调用.
将实例的普通成员改编为虚函数调用,来看看VC在虚函数上面是如何处理的.
- class CSum
- {
- public:
- virtual int Add(int a,int b)
- {
- return (a+b);
- }
- virtual int Sub(int a,int b)
- {
- return (a-b);
- }
- };
- void main()
- {
- CSum* pCSum =new CSum;
- pCSum->Add(1,2);
- pCSum->Sub(1,2);
- }
这是C的一个简单代码,我们看反汇编后的代码是如何的样式,
- push esi
- push 4
- call 00401060
- add esp,4 ;为新建对象实力分配4字节内存
- test eax,eax
- je short 00401019
- mov dword ptr [eax],4050A0 ;将4050A0h写入创建的对象实例中4050A0h是CSum类虚函数表的指针,表中的元素是CSum类的虚函数,他们指向CSum函数的成员
- mov esi,eax ;esi是4050A0hCSum类虚函数表的指针
- jmp short 0040101B
- xor esi,esi ;用NULL指向对象实例指针,该分支在内存分配失败才会来到,空指针将激活SEH
- 0040101B:
- mov eax,dword ptr [esi] ;eax=**Add[]
- push 2
- push 1
- mov ecx,esi ;ecx=this
- call dword ptr [eax] ;对虚函数的调用,此时eax=**Add(),也就是CALL 401040
- mov edx,dword ptr [esi]
- push 2
- push 1
- mov ecx,esi
- call dword ptr [edx+4]
- pop esi
- retn
这段代码首先调用new函数分配class所需内存,调用成功后,eax保存了分配的内存指针,然后将对象实例指向CSum类虚函数表4050A0h.
VTBL里有两组数据
[VTBL]=401040h
[VTBL+4]=401050h
进一步看看这两个指针是什么内容,
;401040h内容如下:Add函数
- mov eax,dword ptr [esp+8]
- mov ecx,dword ptr [esp+4]
- add eax,ecx
- retn 8
401050h函数如下:Sub 函数
- mov eax,dword ptr [esp+4]
- mov ecx,dword ptr [esp+8]
- sub eax,ecx
- retn 8
原来虚函数是通过浙西iangxuhanshubiaode指针间接地加以调用的,程序仍然使用ecx作为this指针的载体传递给虚成员函数,并且利用两次间接寻址,得到虚函数的正确地址,从而执行
- mov eax,dword ptr [esi] ;eax=*指针=**Add()
- push 2
- push 1
- mov ecx,esi ;ecx=this
- call dword ptr [eax]