远程代码注入及DLL注入教程(InlineHook)---植物大战僵尸为例

远程代码注入及DLL注入教程

说明

​ 本人刚开始学习逆向,不知道有没有动力学下深去,这一块也没有详细的实战教学,学多少就上传多少,希望能给想学的朋友一点帮助吧,本教程想通过植物大战僵尸这一经典游戏来抛砖引玉,教大家如何编写一个单机辅助。

​ CE找数据的过程本教程不提供,本文着重讲述如何根据找到的数据实现无限阳光,无冷却,其他诸如秒杀僵尸,后台运行等均大同小异。

所需基础

CE找关键数据基址(网上教程很多)、基本汇编命令

涉及工具

VS、CheatEngine(CE,网上很多)、汇编转机器码工具(我在用的https://www.jb51.net/softs/629217.html)、DLL注入工具(网上很多)

开始

无限冷却

​ 假设第一个植物栏的冷却地址已找到,如图:
在这里插入图片描述
此时种下植物后暂停游戏,切到CE,选中冷却地址,右键-找出什么改写了这个地址,然后继续游戏,发现改写的代码:
在这里插入图片描述
点击显示汇编代码:
在这里插入图片描述
经过分析发现:如果还在冷却,那么在②处判断会跳走,把②处代码nop掉,让代码无论如何也会去Call③,发现植物已经实现无冷却(有很多种方法,比如把①修改为cmp eax,0),那么只需要如下代码即可实现:

BYTE noCD[2] = {0};
memset(noCD, 0x90, 2);//nop硬编码为0x90
WriteProcessMemory(hProcess, (LPVOID)(0x487296), noCD, 2, NULL);

无限阳光

像前面这种替换指令所用空间小于当前代码的方式,实现起来非常简单,但如果替换的汇编指令超过当前代码长度,将会导致后面指令被覆盖,引起崩溃,此时需要用到代码注入
选中找到的阳光值地址,右键-找出什么访问了这个地址
在这里插入图片描述
可以看到有两处频繁访问,我们选择访问次数较少的第二条-显示反汇编程序。
在这里插入图片描述
那就在这里注入吧,前面已经知道,阳光值的地址为[[[基址006A9EC0]->偏移768]->偏移5560],所以要注入的汇编代码可以写成:

push eax
mov eax,[006A9EC0]
mov eax,[eax+768]
mov [eax+5560],0xFFF // 0xFFF为阳光值,别太小就行
pop eax
mov eax,[esi+00005560] //恢复被覆盖掉的代码

而在注入处发现eax被赋值,所以push eax 及 pop eax可以删掉(实际情况实际分析)。
因此,运行流程为:
代码到达"PlantsVsZombies.exe"+89825(0x489825)处时,JMP到空闲空间,执行我们注入的汇编代码,JMP到"PlantsVsZombies.exe"+8982B处继续向后执行,两种方法的示意图:
在这里插入图片描述

远程代码注入

我们需要计算0x489825处 JMP的偏移,推导过程如下:

起跳地址(0x489825) + 5(JMP远跳占用固定5字节,0xE9 + 4字节偏移量) + 偏移量 = 目标地址

因此

偏移量 = 目标地址 - (起跳地址 + 5

目标地址需要我们手动申请,代码如下:

//第三个参数为申请大小
LPVOID virAddr = VirtualAllocEx(hProcess, NULL, 64, MEM_COMMIT, PAGE_EXECUTE_READWRITE);

那么偏移jmpOffset为:

DWORD jmpOffset = (DWORD)(virAddr) - ((DWORD)0x489825 + 5);

接下来组合0x489825处代码:

BYTE ByteOffset[4];
IntTo4Bytes(jmpOffset , ByteOffset); //int转4字节byte[]
BYTE JmpToNewCode[6] = {0};
JmpToNewCode[0] = 0xE9;         //jmp指令
memcpy(JmpToNewCode + 1, ByteOffset, 4);
JmpToNewCode[5] = 0x90;			//空余的1个字节用nop填充

到这一步时,我们先不要着急WriteProcessMemory写入,因为我们申请的virAddr中还没有写入我们的汇编代码,这时需要使用汇编代码转机器码工具(文中有下载地址),转成机器码,然后写入到virAddr中,如图:
在这里插入图片描述

BYTE machinecode[32] = { 161,192,158,106,0,139,128,104,7,0,0,199,128,96,85,0,0,255,15,0,0,139,134,96,85,0,0 };//27

为什么是32字节呢,因为我们的汇编代码还没有JMP跳回0x48982B,需要再加上5字节的JMP 偏移量,计算方法和上面思路一样:

//起跳地址:virAddr + 27   目标地址:0x48982B
DWORD returnoffset = (DWORD)(0x48982B) - ((DWORD)virAddr + 27 + 5); 

组合注入的机器码:

BYTE ByteOffset[4];
IntTo4Bytes(returnoffset, ByteOffset);
BYTE ReturnCode[5] = { 0 };
ReturnCode[0] = 0xE9;         //jmp指令
memcpy(ReturnCode + 1, ByteOffset, 4); //组合jmp
memcpy(machinecode + 27, ReturnCode, 5);//组合完整机器码

在注入前,先把原来汇编指令保存下来,方便还原代码。

BYTE OldCode[6] = { 0 };
ReadProcessMemory(hProcess, (LPVOID)0x489825, OldCode, 6, NULL)

OK,把machinecode写入到virAddr中:

WriteProcessMemory(hProcess, virAddr, machinecode, 32, NULL);

最后再修改注入点(0x489825):

WriteProcessMemory(hProcess, (LPVOID)0x489825, JmpToNewCode, 6, NULL);

至此,代码注入功能基本完毕,编译,运行,测试完美无限阳光。
恢复原来功能只需要:

WriteProcessMemory(hProcess, (LPVOID)0x489825, OldCode, 6, NULL);

DLL注入

DLL注入相比代码注入少了手动开辟内存以及计算跳回偏移的过程,更加简单,步骤如下:
VS创建DLL工程,在DLL_PROCESS_ATTACH中码功能:
在这里插入图片描述
内联代码:

BYTE OldJmp[6] = { 0 };
DWORD JmpBack = 0x48982B;

__declspec(naked) void InfinateSunshine()
{	
	// 不加 dword ptr ss 编译器会翻译成单字赋值,原因不明(求大佬解释)
	__asm
	{
		mov eax, dword ptr ss: [0x006A9EC0]
		mov eax, dword ptr ss : [eax + 0x00000768]
		mov dword ptr ss : [eax + 0x00005560], 0x00000FFF
		mov eax, dword ptr ss : [esi + 0x5560]
		jmp	JmpBack
	}
}

计算偏移方法和上面一致:

//计算偏移,InfinateSunshine 即为目标函数
DWORD offset = (DWORD)InfinateSunshine - (0x489825 + 5);
BYTE NewJmp[6] = {0};
NewJmp[0] = 0xE9;
BYTE ByteOffset[4];
IntTo4Bytes(offset, ByteOffset);
memcpy(NewJmp + 1, ByteOffset, 4);
NewJmp[5] = 0x90;

nRet = WriteProcessMemory(hProcess, (LPVOID)0x489825, NewJmp, 6, NULL);

编译后,用注入工具注入:
在这里插入图片描述
成功。
但是,如果你再卸载掉DLL,会发现游戏崩溃,原因为在卸载DLL时不会自动还原0x489825 处的代码,因此需要再添加逻辑,卸载时复原代码。
在这里插入图片描述在这里插入图片描述

再分析

后来经过分析,发现无限阳光插入处的代码还可以再优化,因为插入处的代码只有阳光访问时才会到达,如何判断呢,可以先用CE代码注入写一个判断代码,和当前的阳光地址做对比,加入一段没有意义的代码并设置断点,看能否断下,如果没有断下,那说明注入处只会被阳光访问,如图:
在这里插入图片描述
故[esi+00005560]一定为阳光地址,因此可以省掉使用基址加偏移的方式,注入处代码可以修改为更简单的:

mov [esi+00005560],0xffff  //确定为阳光唯一,直接赋值
mov eax,[esi+00005560] 	   //恢复被覆盖掉的代码

总结

代码注入及DLL注入的关键为
1、注入代码中不要破坏原先堆栈平衡,如果不确定寄存器里的值在我们使用之前是不是有用,最好先pushad再写自己的注入代码,最后popad
2、注入处的JMP偏移及返回的JMP偏移一定要计算准确,否则必出错,可以配合OD和CE进行调试检查。
  • 4
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
c hook注入dll是一种在Windows系统中实现函数钩子的技术。下面给出一个完整的例子: 首先,创建一个c文件,命名为hookdll.c,代码如下: #include <Windows.h> // 定义要hook的目标函数 typedef bool (WINAPI* ORIGINAL_FUNCTION)(LPCTSTR); ORIGINAL_FUNCTION OriginalFunction; // 定义hook的替代函数 bool WINAPI HookFunction(LPCTSTR lpFileName) { // 在这里编写你的hook函数逻辑 // 可以在这个函数中修改传入参数或返回值,实现钩子的目的 // ... // 调用原始函数 return OriginalFunction(lpFileName); } // Dll入口函数 BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { if (ul_reason_for_call == DLL_PROCESS_ATTACH) { // 加载kernel32.dll HMODULE kernel32 = LoadLibrary("kernel32"); if (kernel32 != NULL) { // 获取目标函数地址 OriginalFunction = (ORIGINAL_FUNCTION)GetProcAddress(kernel32, "函数名"); if (OriginalFunction != NULL) { // 修改函数的内存属性为可执行和可写 DWORD oldProtect; VirtualProtect(OriginalFunction, sizeof(ORIGINAL_FUNCTION), PAGE_EXECUTE_READWRITE, &oldProtect); // 修改函数的指针为hook函数的指针 *OriginalFunction = &HookFunction; // 还原函数的内存属性 VirtualProtect(OriginalFunction, sizeof(ORIGINAL_FUNCTION), oldProtect, &oldProtect); } // 释放kernel32.dll内存 FreeLibrary(kernel32); } } return TRUE; } 编译这个项目,得到一个dll文件,命名为hookdll.dll。 然后,创建一个使用目标dll的示例程序,例如使用了kernel32.dll中的某个方法。然后按照以下步骤实现hook注入: 1. 打开示例程序的源代码,编辑代码,添加以下代码段: #pragma comment(lib, "hookdll.lib") 2. 将hookdll.dll拷贝到示例程序的目录下。 3. 使用LoadLibrary函数在程序中动态加载hookdll.dll。 4. 调用示例程序中使用kernel32.dll中方法的代码,此时会执行被hook函数的替代函数。 通过以上步骤,我们就实现了c hook注入dll的完整例子。在hook函数中可以对传入的参数或返回值进行修改,实现我们想要的钩子效果。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值