免责声明:内容仅供学习参考,请合法利用知识,禁止进行违法犯罪活动!
内容参考于:易道云信息技术研究院
上一个内容:86.游戏改造-UI修正暴力分析
首先来到下图位置
一个函数上来就 ECX+4 这种不用想直接就看作为成员函数,按CTRL + f9再按f7来到调用它的地方可以看到是 MOV ECX,Sword2-1.0044A928 这样的传参可以看作为是一个类了
首先它ecx+4 放到了 EAX里,然后又做了 TEST EAX,EAX,所以ecx+4位置是一个布尔类型的值,代码可能就是
class UI{
unsigned 未知;
unsigned Show;// BOOL或bool +0x04
}
然后ECX+8放到了eax里面,是把ECX+8地址的值放到了eax里
class UI{
unsigned 未知;
unsigned Show;// BOOL或bool +0x4
unsigned UN; // 0x8
void ShowUI(){ // 00407A20 - 00407A30位置是一个成员函数假设这个函数叫ShowUI
if(Show)
}
}
然后打断点分析ecx+0x8位置是什么东西,现在ecx的值是447D90也是我们上面写的UI类的地址
然后它把下图红框位置的值(4357D4)读出来放到了EAX里,然后ADD ecx,0x8然后jmp了4357D4+0x8(eax+0x8),4357D4+0x8这样的写法可能是访问成员变量也可能是访问数组,jmp过去之后是一段完完整整可执行代码然后可以看出EAX+0x8的值是一个函数地址,它的值是通过UN指向的数据偏移了8字节得出来的所以这是典型虚函数的表现
跳转到4357D4位置可以看出是一个虚表,每一个值都是一个虚函数的函数地址
随便找一个值跳过去可以看出确实是一段完整的函数代码,所以这一定是虚函数了ECX+0x8就是调用的虚函数
虚函数是会出现在一个类的头部,所以可以得出UN是一个类,然后eax+0x8位置是虚表中第三个函数,然后调用虚函数一定会传它的指针,然后eax是通过ecx+8得到,然后00407A2A位置的代码进行的add ecx,8紧接着就jmp到虚函数了,然后现在的c++代码如下:
class UIEx{
}
class UI{
unsigned 未知;
unsigned Show;// BOOL或bool +0x4
UIEx ui; // 0x8,ecx+0x8
void ShowUI(){ // 00407A20 - 00407A30位置是一个成员函数假设这个函数叫ShowUI
if(Show) {
}
}
}
然后如果代码是如上的写法又很奇怪,它不是通过对象去调用的函数而是通过指针(0x4357D4这个地址)调用的,然后它有可能是多重继承,如下的写法,
class UIBase{
unsigned 未知;
unsigned Show;// BOOL或bool +0x4
}
class UIEx{
public virtual void _func1(); // 0x0
public virtual void _func2(); // 0x4
public virtual void _Show(); // 0x8
}
class UI:public UIBase, public UIEx{
void ShowUI(){ // 00407A20 - 00407A30位置是一个成员函数假设这个函数叫ShowUI
if(Show) {
_Show(); // 0x8
}
}
}
首先通过ECX+4访问了 基类(UIBase)的Show变量,然后通过ecx+0x8有访问了基类(UIEx)的_Show函数
00407A20 /$ 8B41 04 MOV EAX,DWORD PTR DS:[ECX+4]
00407A23 |. 85C0 TEST EAX,EAX
00407A25 |. 74 09 JE SHORT Sword2-1.00407A30
00407A27 |. 8B41 08 MOV EAX,DWORD PTR DS:[ECX+8]
00407A2A |. 83C1 08 ADD ECX,8
00407A2D |. FF60 08 JMP DWORD PTR DS:[EAX+8]
00407A30 \> C3 RETN
然后来到eax+0x8位置的函数(_Show函数)
0040BF70 . 56 PUSH ESI ; 保存的ESI的值
0040BF71 . 8BF1 MOV ESI,ECX ;ECX是UIEx类首地址
0040BF73 . 8B46 50 MOV EAX,DWORD PTR DS:[ESI+50] ; UIEx+0x50位置是一个变量
0040BF76 . 85C0 TEST EAX,EAX
0040BF78 74 11 JE SHORT Sword2-1.0040BF8B
0040BF7A . 8B06 MOV EAX,DWORD PTR DS:[ESI] ;ESI是UIEx首地址这是虚表,所以又把虚表给到了EAX
0040BF7C . FF50 0C CALL DWORD PTR DS:[EAX+C]
;然后这里调用了虚表函数UIEx+0xC位置又是一个函数
0040BF7F . 8B4E 5C MOV ECX,DWORD PTR DS:[ESI+5C] ;然后ESI+5C位置又是一个变量,通过下面的代码分析它是一个指针是一个类的指针
0040BF82 . 85C9 TEST ECX,ECX
0040BF84 . 74 05 JE SHORT Sword2-1.0040BF8B
0040BF86 . 8B11 MOV EDX,DWORD PTR DS:[ECX]
0040BF88 . FF52 08 CALL DWORD PTR DS:[EDX+8] ;这里调用了ECX这里是一个指针,又是调用虚表,它调用的又是当前函数(0040BF70),一个递归函数,可以看下面的 图1
0040BF8B > 8B4E 58 MOV ECX,DWORD PTR DS:[ESI+58] ;ESI+58位置又是一个指针可以看图2
0040BF8E . 5E POP ESI
0040BF8F . 85C9 TEST ECX,ECX
0040BF91 . 74 05 JE SHORT Sword2-1.0040BF98
0040BF93 . 8B01 MOV EAX,DWORD PTR DS:[ECX]
0040BF95 . FF60 08 JMP DWORD PTR DS:[EAX+8] ;又来了一次递归
0040BF98 > C3 RETN
翻译成c++代码
class UIBase{
unsigned 未知;
unsigned Show;// BOOL或bool +0x4
}
class UIEx{
public virtual void _func1(); // 0x0
public virtual void _func2(); // 0x4
public virtual void _Show(){ // 0x8
if(SHOW_UI){
_func3();
}
if(uiex)uiex->_Show();
if(uiex_f){
uiex_f->_Show();
}
}
public virtual void _func3(); // 0xC
// 前4字节指向虚表
unsigned UN[12];
// 0x50除0x4结果是14
unsigned SHOW_UI; // 0x50
unsigned UN1; // 0x54
UIEx* uiex_f; // 0x58
UIEx* uiex; // 0x5C,上面的汇编代码它是一个递归,可以想想UIEx是一个链表0x5c位置有指向下一个链表
}
class UI:public UIBase, public UIEx{
void ShowUI(){ // 00407A20 - 00407A30位置是一个成员函数假设这个函数叫ShowUI
if(Show) {
_Show(); // 0x8
}
}
}
然后把 0x5C(uiex)改成JMP发现那些小图标都没有了
然后把0x58(uiex_f)位置改成JMP,发现也有一部分图标没有了
把0x50(SHOW_UI)位置改为JMP发现全没了
可以总结出它的ui图标是通过链表调用同一个虚函数来展示的
图1:EDX虚表
图2