阅读目录
植物大战僵尸游戏分析:虚表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++; } |
最新评论
发表评论