对CS1.6游戏的分析及心得

前言

前段时间分析了一下CS1.6游戏,一直没有时间整理思路写帖子,最近,由于疫情关系,忽然想起了这件事,又重新回顾了一下,写下这篇文章与网友们互相交流学习。
本文主要是讲一下指针扫描器和Ultimap的具体使用以及它的强大之处,然后讲一下自己分析游戏数据的一些心得,最后再用一个准星吸人的例子讲一下HOOK的心得

使用指针扫描器快速寻找基址

使用CE寻找基址,一般的思路就是

  • 查找游戏数据,找到数据的临时地址
  • 右键查看谁改变了这个地址指向的数据,得到一个地址X + 偏移
  • 然后我们再内存搜索地址X,得到很多地址Ys,一个个右键查看谁改变了这个地址Y指向的数据,刷新游戏
    然后再找地址

就这样,一层层的去上找,不断尝试,最终找到绿色的即为基址。可见这种找基址的方式非常的麻烦,这里介绍一个神奇的工具,CE的指针扫描器
我们开局游戏,添加几个机器人,(添加机器人的目的是,因为找到基址后,即便不刷新游戏,有的基址指向的数据是不断变化的,其实里面是敌人和队友的基址,为了排除这种情况,添加几个机器人)
拿HP做实现,找到HP的临时地址,右键->对这个地址进行指针扫描

可以发现找到了403个数据

然后我们进行筛选,这里我们可以通过最后一层偏移进行筛选,我们对HP数据右键->找出是什么改写了这个地址,扔个手雷使自己掉血


可以发现,最后一层偏移是160,现今HP是4,下面进行筛选



经过筛选后,进行排序,然后双击第一个,添加到数据列表,退出游戏重新打开,发现里面指向的数据仍然是自己的HP,它就是基址。
其实在筛选的过程中,还有一些其他技巧,比如这个例子,在找到改变HP的地址后,可以查看相关地区汇编代码来寻找倒数第2层偏移

比如这里,可以发现mov eax, [esi+04],而 eax + 160是HP的地址,所以可以判断倒数第2层偏移是4,这样在筛选的时候可以加上二层偏移,当然也可以再往上找寻找倒数第3层偏移,使筛选的结果更准确。

通过搜索部分数据猜测游戏结构体来分析其他数据

通过上面指针扫描器的方法,我们又找到了一些其他数据的基址

属性地址
坐标X[[[cstrike.exe+11069BC]+7C]+4]+8
坐标Y[[[cstrike.exe+11069BC]+7C]+4]+C
坐标Z[[[cstrike.exe+11069BC]+7C]+4]+10
HP[[[cstrike.exe+11069BC]+7C]+4]+160
是否掉血(0免疫伤害)[[[cstrike.exe+11069BC]+7C]+4]+16C
护甲[[[cstrike.exe+11069BC]+7C]+4]+1BC
阵营(1恐怖分子2反恐精英)[[cstrike.exe+11069BC]+7C]+1C8
我的金钱[[cstrike.exe+11069BC]+7C]+1CC
随地购物(1)[[cstrike.exe+11069BC]+7C]+3C0
准星指向(0墙1友2敌)[[cstrike.exe+11069BC]+7C]+710
当前枪子弹数目[[[cstrike.exe+11069BC]+7C]+5EC]+CC
当前枪后坐力[[[cstrike.exe+11069BC]+7C]+5EC]+100
是否正在使用武器[[[cstrike.exe+11069BC]+7C]+5EC]+D4
左键冷却[[[cstrike.exe+11069BC]+7C]+5EC]+B8

通过分析的这些数据可以猜测一下人物的结构体
[cstrike.exe+11069BC]+7c] 存放的是整个人物的基址
[[cstrike.exe+11069BC]+7c]+4] 存放的关于游戏内人物具体属性的具体地址
[[cstrike.exe+11069BC]+7c]+5EC] 存放的是使用武器的地址
我们通过猜测结构体,来到具体的内存位置,对人物进行操作,然后对内存进行观察,可以发现一些其他好玩的数据,这里我们来武器的地址为例

首先在地址列表中添加一个地址,指向当前的使用武器,通过切换武器来找到每个武器的地址,进而对相关内存区域进行对比
打开分析数据窗口,填入当前主武器,副武器,刀子的地址


我们切换武器和左键点击,观察内存,对3个数据进行对比
我们可以很明显的发现3个变化,即+D4这个区域,如果为1的话,表示当前武器正在使用
+B8这个位置,如果持续点击左键的话,这个位置的数据会增加

我们将刀子+B8的数据锁定为-1,就会出现2秒17刀的效果,,,

通过这种方式,不断进行内存数据的对比,也可以发现很多其他好玩的数据,感兴趣的网友自行尝试。这里讲解的主要目的主要是通过猜测游戏结构体来获取游戏的相关数据

使用Ultimap分析手雷CALL实现自定义手雷爆炸位置

CE有个强大的功能是Ultimap,Ultimap的功能其实是一个筛选代码的功能

类似于CE的内存数据筛选,只不过Ultimap是代码筛选,可以搜索程序中的CALL, 比如我们要找手雷爆炸的CALL,可以点击开始,然后晃动窗口,人物随机走动,发射子弹等操作,然后点代码没有运行,筛选除去很多没用的代码,然后扔个手雷,暂停游戏,点代码没有运行,等手雷爆炸后,点代码已经运行,上图中的 代码没有调用计数 可以筛选函数执行的次数,比如可以扔2个手雷等爆炸后,次数设置为2,点击代码没有调用计数按钮就可以筛选

启用Ultimap需要开启DBVM,这个需要电脑支持才可以,点击帮助,关于,可以看到电脑是否支持

强烈建议使用CE7.0版本,因为我用其他版本的时候,使用Ultimap会失败,甚至有的版本显示系统不支持DBVM,但是7.0不会。还有如果成功起开之后,Banch目标始终为0的话,需要用管理员身份打开目标程序,比如这里需要以管理员身份运行CS1.6

下面来找手雷爆炸CALL来实现自定义手雷爆炸的位置
我们按照上述的步骤进行一点点筛选

最终我们找到了这个地址

对其进行HOOK即可改变手雷爆炸位置

通过分析堆栈找到准星指向敌人基址,实现吸人

前面已经说过[[cstrike.exe+11069BC]+7C]+710 在这个地址处存放着准星指向的相关数据,比如如果对准敌人,这个地址的数据是2,对准队友是1,对准墙什么的是0,我想实现一个只要准星对准敌人敌人或队友就可以把他吸过来的功能,这个时候就需要获取准星指向人的地址

对 [[cstrike.exe+11069BC]+7C]+710 下断,看是什么改写了这个地址,游戏中添加3个机器人,控制台启用bot_stop 1使机器人停止行动

我们把准星从墙移动到队友身上

浏览内存区域

这里我最初是企图通过向上查汇编代码的方式来看看能不能找到队友基址的函数,但是失败了
虽然没有找到,但是肯定有,于是,换种思路,能不能先找到队友基址,然后再去内存中搜

我们在这个那个地址处下断点,将准星移动到敌人上,程序断下,搜索敌人基址

然后再把准星移动到队友身上,程序断下,发现地址又变为了队友的基址

再观察程序运行到此处时ESP,[ESP- 0XD8]就是指向人基址

mp.dll+9C0EC - 89 7D 00 - mov [ebp+00],edi

当然指向墙的时候,[esp - 0XD8]存放的是其他的数据,因此我们对此HOOK的时候需要判断一下数据是不是人物基址

BOOL IsPeopleBase(DWORD base)  
{
	if (dwPointPeopleBase > 0){
		if (!(0XF0000000 & dwPointPeopleBase))//人物基址前4位通常是0
			return TRUE;
		try{//如果不是的话也有可能是人物基址,判断血量即可
			float blood = *(float *)(*(DWORD *)(dwMyBase + 4) + 160);
			if (blood < 100 && blood > 0)
				return TRUE;
		}
		catch (...)	{
			return FALSE;
		}
	}	
	return FALSE;
}

这里贴出按E吸人,按T瞬移过去的全部代码

#include "stdafx.h"

#define PATCHLENGTH	6
#define OFFSET 0X9C0C9			//HOOK mp.dll的偏移位置

//0AD2C0C9    50              push eax
//0AD2C0CA    6A 00           push 0x0
//0AD2C0CC    51              push ecx
//0AD2C0CD    6A 01           push 0x1
typedef struct _stPos{
	float x;
	float y;
	float z;
}stPos, *pstPos;

HMODULE hMpdll = 0;							//保存mp.dll的基址
HMODULE hModule = 0;						//保存cstrike.exe的基址
BYTE byHookPatch[PATCHLENGTH] = {};			//保存打的补丁
DWORD dwHookAddr;							//保存HOOK的地址
DWORD dwHookRetAddr = 0;					//保存HOOK函数的返回点
BOOL IsHooked = FALSE;
DWORD dwPointPeopleBase;					//保存指向人的基址
DWORD dwMyBase;								//保存我的基址
pstPos pMyPos;								//我的坐标
pstPos pOtherPos;							//指向人坐标

void __declspec(naked) HookProc()
{
	__asm {
		pushad;
		pushfd;

		mov eax, dword ptr ss : [esp + 0X24 - 0XD8];	
		mov dword ptr ds : [dwPointPeopleBase], eax;	//保存指向人物的基址
		
		popfd;
		popad;

		push eax;
		push 0x0;
		push ecx;
		push 0x1;
		jmp dword ptr ds:[dwHookRetAddr];
	}
}

void GetHookInfo() {
	hMpdll = GetModuleHandle("mp.dll");
	hModule = GetModuleHandle(NULL);
	dwHookAddr = (DWORD)hMpdll + OFFSET;
	dwHookRetAddr = dwHookAddr + PATCHLENGTH;
	memset(&byHookPatch[0], 0X90, PATCHLENGTH);
	byHookPatch[0] = 0XE9;
	*(DWORD *)&byHookPatch[1] = (DWORD)HookProc - dwHookAddr - 5;
}

void SetHook() {
	DWORD dwOldProtect;
	if (!IsHooked) {
		VirtualProtect((LPVOID)(hMpdll + OFFSET), PATCHLENGTH, PAGE_EXECUTE_READWRITE, &dwOldProtect);
		WriteProcessMemory(GetCurrentProcess(), (LPVOID)dwHookAddr, byHookPatch, PATCHLENGTH, NULL);
		VirtualProtect((LPVOID)(hMpdll + OFFSET), PATCHLENGTH, dwOldProtect, NULL);
		IsHooked = TRUE;
	}
}

void GetPosInfo() {
	dwMyBase = *(DWORD *)((DWORD)((DWORD *)*((DWORD *)((DWORD)hModule + 0X11069BC))) + 0X7C);
	pMyPos = pstPos(*(DWORD *)(dwMyBase + 4) + 8);
}

void CopyPos(pstPos a, pstPos b)
{
	a->x = b->x;
	a->y = b->y;
	a->z = b->z;
}

BOOL IsPeopleBase(DWORD base)
{
	if (dwPointPeopleBase > 0){
		if (!(0XF0000000 & dwPointPeopleBase))
			return TRUE;
		try{
			float blood = *(float *)(*(DWORD *)(dwMyBase + 4) + 160);
			if (blood < 100 && blood > 0)
				return TRUE;
		}
		catch (...)	{
			return FALSE;
		}
	}	
	return FALSE;
}

DWORD WINAPI ThreadFunc(LPVOID lParam)
{
	GetHookInfo();
	SetHook();;
	GetPosInfo();
	while(TRUE){
		if (GetAsyncKeyState('E')){			//按E吸人
			if (IsPeopleBase(dwPointPeopleBase)) {
				pOtherPos = pstPos(*(DWORD *)(dwPointPeopleBase + 4) + 8);
				CopyPos(pOtherPos, pMyPos);
			}
		}
		else if (GetAsyncKeyState('T')) {	//按T瞬移至指向人身边
			if (IsPeopleBase(dwPointPeopleBase)) {
				pOtherPos = pstPos(*(DWORD *)(dwPointPeopleBase + 4) + 8);
				CopyPos(pMyPos, pOtherPos);
			}
		}
		Sleep(100);
	}
	return 0;
}

BOOL APIENTRY DllMain( HMODULE hModule,DWORD  ul_reason_for_call,LPVOID lpReserved){
    switch (ul_reason_for_call){
    case DLL_PROCESS_ATTACH:
		CreateThread(NULL, 0, ThreadFunc, NULL, 0, NULL);
		break;
    }
    return TRUE;
}

成果展示

最终,经过不断尝试,自己另外还制作了一个CS1.6的小辅助

实现功能的视频演示: https://www.bilibili.com/video/av68192542

辅助源码:https://github.com/LiZiQiang191/CS1.6_Hook

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值