一、概念
具体的描述大家可以在网上找,在这里我就不再罗嗦了,大致意思为:通过改变代码的执行流程达到某种目的。
二、背景
谈及逆向分析,基本就是分析一个在市场上已经发布的软件程序,而在这种情况下,我们往往是没有源代码的,但又想实现一些特殊的功能,此时HOOK就该登场了。
三、实现
本文以某老旧款游戏(以下称“Game”)为例,实现人物无敌的功能,简单说明HOOK的一些事情。
图片为项目(以下称“MyProject”)运行后的界面:
1、输入进程ID:
因为进程间的内存彼此独立,在MyProject中做的事情,Game是无法直接访问到的,所以需要依据进程ID得到Game的句柄,进而继续其他操作,该步骤调用API即可
DWORD dwPID;// 打开任务管理器就能看到
// 具体参数的意思 请看官方文档
HANDLE hGameProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID);
2、关键逻辑:
以无敌功能为例,在一个游戏中,当人物受到攻击时,会触发一个类似于这样的函数
void beAct(int damage)
{
// 其他业务代码
hp = hp - damage;
// 其他业务代码
}
只有这样才会导致血量发生变化,继而显现在游戏界面或人物信息的面板上,总之,这一句代码是一个十分基本的逻辑,所以无敌的思想就是不让这句代码执行,或者damage清零。
修改的时候还需要注意一个问题:有可能人家使用了面向对象的思想,人物和怪物属于同一个角色类class Role,所以在修改时还需要判断调用被攻击函数的指针是否是人物,否则会出现怪物也打不死的尴尬场面。
基于上面的分析,很显然已经不能通过逆向分析工具在原基础上进行修改了,否则破坏性太大。
3、实现:
分配新的内存空间,将无敌功能代码写在该内存,通过跳转到该内存来实现无敌。此时刚刚拿到的hGameProcess就派上用场了,因为你内存分配到MyProject里没用啊,Game访问不到
// 在Game的进程中分配内存,分配成功返回首地址,失败返回NULL
LPVOID lAdr = VirtualAllocEx(hGameProcess, NULL, 1000, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
内存分配好后,就该写代码了。问题又来了,Game是一个已经编译好的程序,是一个完全由机器码组成的文件,所以我们也只能直接在分配的内存中写入机器码(当然也是可以使用纯C++代码完成的,那个以后再说)
// 宏的值都是瞎写的
#define PTR_ROLE 0xaaaaaaaa // 人物指针
#define JUMP_FROM_GAME 0xbbbbbbbb // 从Game的这个地址处跳到分配的内存空间
#define JUMP_TO_GAME 0xcccccccc // 无敌功能执行完了还要回到Game并继续执行其他
char data[]
{
0x81,0xFE, //cpm esi
0x00,0x00,0x00,0x00, //即人物的地址 写出0方便后面使用下标修改 与前一句连接起来为 cmp esi 人物指针
0x75,0x02, //跳两个字节 即jne xxxxxxxx 根据具体执行指令所占字节长度修改02的值
0x31,0xFF, //xor edi,edi
0x8B,0xD0, //mov edx,eax
0x29,0xFA, //sub edx,edi
0x39,0xCA, //cmp edx,ecx
0xE9, //jmp
0x00,0x00,0x00,0x00 //要跳转回的地址 即要返回游戏原来本该继续执行的地址
};
//jmp指令 jmp后面跟着的值=要跳转到的目标地址-当前jmp指令的地址-5 没有为什么 算法规定
//计算准备跳回原游戏地址的机器码 即data中最后一组0的值
int iJumpReturnAdr = JUMP_TO_GAME - ((int)lAdr + 0x10) - 5;
//把计算出的值 写进data中
int* nCode = (int*)(data + 0x11);
nCode[0] = iJumpReturnAdr;
//同理 data中第一组的0的值也通过这样写入
nCode = (int*)(data + 0x2);
nCode[0] = PTR_ROLE;
//将无敌代码 写到新分配的内存中
WriteProcessMemory(hGameProcess, lAdr, data, sizeof(data), NULL);
//处理前面游戏准备跳过来的地方
int iJumpAdr = (int)lAdr - JUMP_FROM_GAME - 0x5;
nCode = (int*)(jumpCode + 0x1);
nCode[0] = iJumpAdr;
4、说明:
a、根据已经做出的逆向分析确定了这是面向对象,即人物和怪物同属一个类,beAct函数为成员函数,所以需要判断this指针;
b、跳转指令后面机器码的计算;
c、上述内容完成后,即可实现在MyProject的对话框中勾选“无敌”按钮,来控制是否需要无敌;
四、总结
大致流程图如下:
一个简单的无敌功能就通过HOOK实现了!