C++虚表的hook

c++类实例中.第一个成员是一个指向virtual Routine的函数地址表,类似于ssdt表.类中调用虚函数的时候,会在虚函数表中查找相应的虚函数代码执行地址.


下面一段代码调用虚函数的反汇编代码

00291B54  |.  6A 0F         push 0xF
00291B56  |.  6A 0A         push 0xA
00291B58  |.  8B45 E0       mov eax,dword ptr ss:[ebp-0x20]
00291B5B  |.  8B10          mov edx,dword ptr ds:[eax]
00291B5D  |.  8B4D E0       mov ecx,dword ptr ss:[ebp-0x20]
00291B60  |.  8B42 04       mov eax,dword ptr ds:[edx+0x4]
00291B63  |.  FFD0          call eax                                 ;  TestVirt.CCLA::mul
00291B65  |.  3BF4          cmp esi,esp
00291B67  |.  E8 F40C0000   call TestVirt._RTC_CheckEsp
00291B6C  |.  6A 05         push 0x5
00291B6E  |.  6A 06         push 0x6
00291B70  |.  8B4D E0       mov ecx,dword ptr ss:[ebp-0x20]
00291B73  |.  E8 38020000   call TestVirt.CCLA::sub

其中 类TestVirt.CCLA中的 mul函数是虚函数.sub是普通函数.通过这段汇编代码可以发现两个函数的调用的不同.

首先我们知道了指向虚表的指针的地址,既指向类实例指针的偏移为0的位置.

假设类指针为p,调用类中第n个虚函数地址可以通过下面这段汇编代码

_asm{


mov eax,p

mov ebx,[eax]

mov eax,[ebx+0x4*n]//n从零计数

mov ecx,p //类中函数调用需要用ecx把类指针传进去.

cal eax 

}

hook思路:

自己写一个中转函数.用自己的中转函数覆盖掉虚表中的函数地址.

由于类中虚函数数量未知.

方案1:

覆盖函数的时候需要判断虚表中指向的内存区域是否是可执行区域.来确定虚表长度。

此法有缺陷,因为虚表中有为0的项.是否可考虑把0项排除?

方案2:假设虚表足够大,覆盖足够大的虚表数据.此法容易误伤.


最终:使用方案1,宁缺毋滥


假设虚表函数个数为m

申请一块内存用来当做假虚表:

dword *p=(*dword)virutalAllo(null,...,sizeof(dword)*m);


假设中转Hook函数为 vt_hook


把刚才申请的内存区域用vt_hook地址覆盖


for(int i=0;i<m;i++)

{

*(p+i)=(dword)vt_hook;

}


下面开始写vt_hook的实现代码。因为需要用到栈回溯来确定调用的函数序号。所以采用纯汇编写中转函数


_declspec(naked) void vt_hook()

{

push ebp

mov ebp,esp

pushad

pushfd


/**********

情况1

00291B60  |.  8B42 04       mov eax,dword ptr ds:[edx+0x4]
00291B63  |.  FFD0          call eax                                 ;  TestVirt.CCLA::mul

mov eax,[edx+0x4] 其中的4/4就是虚表的序号 下面的代码用来获取序号4


情况2

00291B47  |.  8B02          mov eax,dword ptr ds:[edx]
00291B49  |.  FFD0          call eax


*********/

mov eax,[ebp+0x4] // :ebp的值是pushad之前esp的值.而mov ebp,esp之前 [esp]=ebp.[esp+4]=ret返回地址.简而言之 [ebp+4]是call会跳地址.

sub eax,2 //ret会跳地址是eax,eax-2就是call eax这条指令的地址.为什么不减5?因为call eax指令长度就是2,不是5

mov ebx.eax

sub ebx,2

cmp [ebx],0x8B02  //8B02     00291B47  |.  8B02          mov eax,dword ptr ds:[edx]

je case1

mov ebx,eax

sub ebx,3

cmp [ebx],0x8B42 //判断长度3的  00291B60  |.  8B42 04       mov eax,dword ptr ds:[edx+0x4]

mov ebx,eax

sub ebx,6

cmp [ebx],0x8b82 //判断长度为6的00291C1C      8B82 AFA00000          mov eax,dword ptr ds:[edx+0xA0AF]

je case3


je case2

case1

mov edx,orig_vb      //原始虚表地址

mov eax,[edx]

mov edi,0 //序号0

case2:

mov edx,orig_vb

mov  eax,[ebx+2]

add eax,0x0ff

mov edi,eax

shr edi,3

add eax,edx

case3:

mov edx,orig_vb

add ebx,2

mov eax,[ebx]

add eax,edx


/*******************/

//上面通过栈回溯找到虚函数序号.

//edi 里面存放的是虚表序号

eax 里面是真实虚函数地址

pushad

push eax

push edi

call dosomething   //dosomething(dword OrigAddress,dword index)

add esp,0x8


popad




popfd

popad

mov esp,ebp

pop ebp

jmp  eax


}




阅读更多
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页