游戏安全实验室 首页 技术入门 查看内容

 阅读目录

植物大战僵尸游戏分析:虚表HOOK在植物大战僵尸中的使用

发布于:2016-7-5 15:37   |    159429次阅读 作者: 管理员    |   原作者: TP   |   来自: 原创

在游戏外挂编写中,大部分HOOK 都会被游戏保护检测到,所以外挂编写者往往会通过其他方式HOOK来达到自己的目的,而我们也需要了解各种HOOK以更好的做好防御与对抗工作。

[虚表]

在HOOK中虚表HOOK 用的较为少,因为虚表中有些为数据段地址,有些为虚函数地址,在定位虚表和确定虚表长度上存在一定难度。

在实现虚表HOOK之前,我们先来了解虚函数

随便写一个demo:

class A

 

{

 

public:

int iVal1;

int iVal2;

virtual void print1()

{

printf("A.print1\n");

}

 

virtual void print2()

{

printf("A.print2\n");

}

 

virtual void print_all()

{

printf("A.ival1 = &d\tA.ival2 = %d\n",iVal1,iVal2);

}

 

};

 

 

 

class B

{

public:

int iVal1;

int iVal2;

virtual void print1()

{

printf("B.print1\n");

}

 

virtual void print2()

{

printf("B.print2\n");

}

 

virtual void print_all()

{

//cout<<"iVal1(A) = "<<iVal1<<"\t\t"<<"iVal2(A) = "<<iVal2<<endl;

printf("B.ival1 = &d\tB.ival2 = %d\n",iVal1,iVal2);

}

 

};

void main()

{

A a;

B b;

a.iVal1 = 1;

a.iVal2 = 2;

b.iVal1 = 3;

b.iVal2 = 4;

a.print1();

a.print2();

a.print_all();

 

b.print1();

b.print2();

b.print_all();

}

 

我们OD载入:

观察堆栈

发现在调用虚函数时,会将this指针存放在ecx

我们在数据窗口中查看this指针

发现this指针其实存放的为一个地址,存放的为是虚函数对应的地址。

于是,我们可以很清楚的了解到 this指针与虚表存在这样的关系


 

有了上面的基本概念之后如果想要实现虚表HOOK,那么通过上图很容易知道有两种思路:1更换_vfptr指针,让它指向我们所构建的假虚表中

2更改虚函数指针,指向我们设置的函数流程

(具体思路可参照下图)

 

 

上述demo简单的表述了虚函数的调用,但我们涉及到虚函数继承的时候,情况可能稍微复杂些:

我们简单写个demo:

class A

{

public:

int iVal1;

int iVal2;

virtual void print1()

{

printf("A.print1\n");

}

 

void print2()

{

printf("A.print2\n");

}

 

virtual void print_all()

{

printf("A.ival1 = %d\tA.ival2 = %d\n",iVal1,iVal2);

}

 

};

 

 

 

class B : public A

{

public:

int iVal1;

int iVal2;

virtual void print1()

{

printf("B.print1\n");

}

 

virtual void print2()

{

printf("B.print2\n");

}

 

virtual void print_all()

{

printf("B.ival1 = %d\tB.ival2 = %d\n",iVal1,iVal2);

}

 

};

 

class C : public B

{

public:

virtual void print1()

{

printf("c class\n");

}

};

 

void main()

{

C c;

A* pa;

B* pb;

pb = &c;

pa = &c;

 

pa->iVal1 = 1;

pa->iVal2 = 2;

 

pb->iVal1 = 3;

pb->iVal2 = 4;

//pb->print1();

pa->print1();

pa->print2();

pa->print_all();

 

pb->print1();

pb->print2();

}

运行结果为

OD载入分析虚函数继承

 

Main函数如下:

 

009A42CE    8D4D E8         lea ecx,dword ptr ss:[ebp-0x18]                        ; 传入C类的this指针

009A42D1    E8 3DCEFFFF     call vttest.009A1113                                   ; 构造C

009A42D6    8D45 E8         lea eax,dword ptr ss:[ebp-0x18]                        ; 取c.this

009A42D9    8945 D0         mov dword ptr ss:[ebp-0x30],eax                        ; pb = &c

009A42DC    8D45 E8         lea eax,dword ptr ss:[ebp-0x18]                        ; 取c.this

009A42DF    8945 DC         mov dword ptr ss:[ebp-0x24],eax                        ; pa = &c

009A42E2    8B45 DC         mov eax,dword ptr ss:[ebp-0x24]                        ; 取a.this

009A42E5    C740 04 0100000>mov dword ptr ds:[eax+0x4],0x1                         ; pa->iVal1 = 1

009A42EC    8B45 DC         mov eax,dword ptr ss:[ebp-0x24]

009A42EF    C740 08 0200000>mov dword ptr ds:[eax+0x8],0x2                         ; pa->iVal2 = 2

009A42F6    8B45 D0         mov eax,dword ptr ss:[ebp-0x30]

009A42F9    C740 0C 0300000>mov dword ptr ds:[eax+0xC],0x3                         ; pb->iVal1 = 3

009A4300    8B45 D0         mov eax,dword ptr ss:[ebp-0x30]

009A4303    C740 10 0400000>mov dword ptr ds:[eax+0x10],0x4                        ; pb->iVal2 = 4

009A430A    8B45 DC         mov eax,dword ptr ss:[ebp-0x24]                        ; a.this

009A430D    8B10            mov edx,dword ptr ds:[eax]                             ; 将a.this存放在其存期中

009A430F    8BF4            mov esi,esp

009A4311    8B4D DC         mov ecx,dword ptr ss:[ebp-0x24]                        ; 传入a.this指针

009A4314    8B02            mov eax,dword ptr ds:[edx]                             ; 传入虚表地址

009A4316    FFD0            call eax                                               ; pa->print1()

009A4318    3BF4            cmp esi,esp

009A431A    E8 4ECEFFFF     call vttest.009A116D                                   ; CheckEsp

009A431F    8B4D DC         mov ecx,dword ptr ss:[ebp-0x24]                        ; 传入a.this

009A4322    E8 4FCFFFFF     call vttest.009A1276                                   ; pa->print2()

009A4327    8B45 DC         mov eax,dword ptr ss:[ebp-0x24]

009A432A    8B10            mov edx,dword ptr ds:[eax]

009A432C    8BF4            mov esi,esp

009A432E    8B4D DC         mov ecx,dword ptr ss:[ebp-0x24]                        ; 虚表指针

009A4331    8B42 04         mov eax,dword ptr ds:[edx+0x4]                         ; 第二个虚函数

009A4334    FFD0            call eax                                               ; pa->printall()

009A4336    3BF4            cmp esi,esp

009A4338    E8 30CEFFFF     call vttest.009A116D                                   ; CheckEsp

009A433D    8B45 D0         mov eax,dword ptr ss:[ebp-0x30]                        ; b.this

009A4340    8B10            mov edx,dword ptr ds:[eax]

009A4342    8BF4            mov esi,esp

009A4344    8B4D D0         mov ecx,dword ptr ss:[ebp-0x30]

009A4347    8B02            mov eax,dword ptr ds:[edx]

009A4349    FFD0            call eax                                               ; pb->print1()

009A434B    3BF4            cmp esi,esp

009A434D    E8 1BCEFFFF     call vttest.009A116D                                   ; CheckEsp

009A4352    8B45 D0         mov eax,dword ptr ss:[ebp-0x30]

009A4355    8B10            mov edx,dword ptr ds:[eax]

009A4357    8BF4            mov esi,esp

009A4359    8B4D D0         mov ecx,dword ptr ss:[ebp-0x30]

009A435C    8B42 08         mov eax,dword ptr ds:[edx+0x8]                         ; 第3个虚函数

009A435F    FFD0            call eax                                               ; pb->print2()

009A4361    3BF4            cmp esi,esp

009A4363    E8 05CEFFFF     call vttest.009A116D                                   ; CheckEsp

 

 

 

我们步入构造函数分析,进入A构造函数分析

然后接着构造其派生B

紧接着是C

 

通过分析我们可以发现在基类构造完成之后,派生类的构造函数会改写基类虚表指针,来实现虚函数特性。在我们调用虚函数的时候就直接从虚函数表中调用。

如果派生类中没有对应的虚函数,则在派生类虚表中直接使用基类的虚函数地址。

使用非虚函数时,就直接使用硬编码之后直接call。

 

 

在分析普通多重继承和虚拟多重继承的时候,情况会更复杂(详情请参加《虚函数多重继承分析》

 

 

[PlantsVsZombies]虚表定位

结合之前的分析,我们应该很容易做出结论,根据基址+偏移找到的地址,其实就是植物大战僵尸中的一个this指针

如果之前有逆向过植物大战僵尸,很多时候发现都有一个这样的指针传入

我们数据窗口跟随0x1B0B4BA8

在跟随0x725a30

 

我们IDA看一下会更清楚

成功定位虚表

 

[PlantsVsZombies]虚表HOOK实现

 

单一HOOK 指定虚函数:

如果只是单独hook部分虚函数的话,实现起来很简单,直接将虚函数的地址改为我们想要的地址,之后在我们构造的函数中在jmp到之前的地址中。

 

HOOK整个虚表

 

通过更改指针对象来实现虚表替换:

 

我们将新建一份虚表,在虚表中存放着跳转地址,为了标记从哪个虚表中跳转,我们构造一份跳转表,在里面记录从哪一个偏移跳过来的,在之后的恢复也方便。(参考《游戏外挂攻防艺术》中虚表hook章节)

 

 

构建跳转表

typedef struct _JMP_TABLE_ITEM

{

UCHAR uPuhsad; //保存堆栈

UCHAR uPushfd; //保存堆栈

UCHAR uMovEax[5]; //将偏移存放在eax

UCHAR uPushEax; //将eax保存在堆栈中

UCHAR uJmp[5]; //统一跳转到我们指定的地址中

UCHAR uSave;

}JMP_TABLE_ITEM, *PJMT_TABLE_ITEM;

 

初始化跳转表(摘自《游戏外挂攻防艺术》hook虚表章节)

 

在用替换虚表指针的方法中,因为并非从游戏一开始便初始化了虚表指针,所有如果从游戏开始,通过注入我们的dll来修改指针会造成游戏崩溃。一种解决思路是,将游戏主线程挂起:

判断最早启动的线程作为游戏主线程

 

GetThreadTimes(hThread, &creation_time, &exit_time, &kernel_time, &user_time);

if(CompareFileTime(&creation_time, &prev_creation_time) == -1) 

{

SuspendThread(hThread);

//做你想做的....

ResumeThread(hThread); 

}

 

还有一种方法是通过修改虚函数地址(如下),因为在游戏开始前,这些地址就已经固定在了游戏的.rdata段,所以直接修改函数地址会更加稳定,不会造成因为挂起线程而存在的可能使游戏崩溃的现象。

 

通过修改虚函数地址实现HOOK

 

借助上述跳转表思路

for (int dwloop = 0; dwloop < nVirtualTableNum; dwloop++)

{

//保存每一条对应的虚表地址

ReadProcessMemory(handle,(PVOID)pdwVtbase,&dwVt[dwloop],sizeof(DWORD),&dwReadByte);

printf("\naddr[%d] = %x\n",dwloop,dwVt[dwloop]);

//跳转表赋值

pJmpTbItem->uPuhsad = 0x60;

 

pJmpTbItem->uPushfd = 0x9c;

pJmpTbItem->uMovEax[0] = 0xB8;

*((DWORD* )&(pJmpTbItem->uMovEax[1])) = dwloop;

pJmpTbItem->uPushEax = 0x50;

pJmpTbItem->uJmp[0] = 0xE9;

//下面放监控函数pdwMonitouFn中存放监控函数

printf("MonitourFn = %x\t offset = %x\n",MonitouCall,FIELD_OFFSET(JMP_TABLE_ITEM,uSave));

 

*((DWORD *)&(pJmpTbItem->uJmp[1])) = (DWORD)(MonitouCall) - ((DWORD)(pJmpTbItem)+FIELD_OFFSET(JMP_TABLE_ITEM,uSave));

WriteProcessMemory(handle,(PVOID)pdwVtbase,&pJmpTbItem,sizeof(DWORD),&dwWriteByte);

printf("pjmp = %x\t*addr = %x \taddrnew = %x\n",pJmpTbItem,*pdwVtbase,pdwVtbase);

pdwVtbase++;

pJmpTbItem++;

}


*转载请注明来自游戏安全实验室(GSLAB.QQ.COM)

分享到:
踩0 赞0

收藏

上一篇:植物大战僵尸游戏分析:限制功能相关逻辑分析

下一篇:植物大战僵尸游戏分析:阳光点击及收集相关逻辑分析

最新评论
B Color Image Link Quote Code Smilies

发表评论