50.第二阶段x64游戏实战-代码实现特征码定位

免责声明:内容仅供学习参考,请合法利用知识,禁止进行违法犯罪活动!

本次游戏没法给

内容参考于:微尘网络安全

上一个内容:49.第二阶段x64游戏实战-封包-代码实现自动登录

现在找了很多基址了,但是游戏一更新,代码就会有变化,然后之前找的基址就没法用了,有得重新找,这是一个体力活,为了避免重新找就可以使用特征码进行定位,特征码定位也不是绝对的,可能需要多个版本才能让它不出错,如果特征代码进行了改动也还是需要重新找,所以基址这个东西它就是一个体力活无法避免,只能把麻烦降到最低

首先特征码搜索器的使用,比如像得到下图红框的基地址,这里要注意,要确保当前模块是游戏模块,否则会失败(x64dbg的问题)

选中一段代码,使用二进制复制

复制完:

40 53 48 83 EC 20 48 8D 05 FB 4B 6C 00 48 8B D9 48 89 01 F6 C2 01 74 06 FF 15 72 6A 6A 00

然后通过复制的二进制和代码做比较,如下图红框位置的内容,它们都是会变动的数据,就是游戏重启电脑重启就会变动的数据

然后把会变化的值改成??,这里的??表示通配符也就是说这里的内容是什么都可以

40 53 48 83 EC ?? 48 8D 05 ?? ?? ?? ?? 48 8B D9 48 89 01 F6 C2 ?? 74 ?? FF 15 ?? ??

然后如下图基址的获取公式,下一行代码地址加二进制006C4BFB(FB4B6C00)

使用x64dbg验证特征码,如下图鼠标右击选择搜索-》当前模块-》匹配特征

然后把上方改好的特征码复制到下图红框位置,然后点确定

点击确定之后,如下图红框,就可以找到代码位置了

然后用这个位置加9就能得到006C4BFB,然后在加4就能得到下一行代码的地址,然后用下一行代码的地址加006C4BFB就能得到基址了

特征码搜索器实现思路,首先通过注入得到游戏中代码地址,然后读取地址里的值(也就是读取代码),读取之后处理一下特征码中的通配符(也就是??),把??改成一个不会出现的值0xffff这个值一般不会出现

StartAddress = GetModuleHandle(NULL);//获取模块地址
Msize = GetModuleLen(StartAddress);//获取模块大小
void 更新() {
/**
		第一个参数是用来描述当前特征码是谁的
		第二个参数是特征码
		第三个参数是偏移,比如使用特征码得到了一个地址是0x10000,然后我们想要的基址在0x1000E位置
		第四个参数是功能,有三个数,0代表正常继续进行计算,1表示获取当前地址,2获取内存地址里的值
		第五个参数,有些特征码会匹配出多个地址,它就是用来控制匹配几次
	*/
	QWORD 角色基地址 = GetPostitioningAddress(L"角色基地址", L"40 53 48 83 EC ?? 48 8D 05 ?? ?? ?? ?? 48 8B D9 48 89 01 F6 C2 ?? 74 ?? FF 15 ?? ??", 0x9,0, 1);
}
DWORD64 GetPostitioningAddress(wstring A_Name/*自定义定位名*/, wstring S_Code/*特征码*/, int Shifting/*偏移*/, DWORD size/*分类 0代表正常继续进行计算  1代表获取当前地址就写出去 2读一层写出去  */,int SeacherNum) {

	//std::vector<DWORD64> ResultArray;  //容器
	Call_OutputDebugA("--------------------------------------------------\n");
	DWORD64 address = 0;
	DWORD64 m_addr;
	DWORD A_data;
	QWORD m_data;

	int index = 0;
	if (!S_Code.empty()) {
		while ((index = S_Code.find(' ', index)) != wstring::npos)//取空格
		{
			S_Code.erase(index, 1);
		}
	}


	bool sc = SC_SeacherCode(S_Code, (QWORD)StartAddress/*模块地址*/, (QWORD)((QWORD)StartAddress + Msize)/*结束地址*/, SeacherNum, address);

	if (sc == 0) {
	}
	if (sc >= 1) {

		m_addr = address + Shifting;//地址=找到的地址+偏移 

		if (size == 1) {
			return m_addr;
		}

		A_data = ReadDwords(m_addr);//得到代码编码


		if (A_data >= 0x80000000) {//判断是否为负数  0xffffffff -1      
			//0-7fffffffff

			m_data = 0xffffffff00000000 + A_data;//补齐负数

		}
		else {
			m_data = A_data;
		}

		Call_OutputDebugW(L"%s:%p\n", A_Name.c_str(), m_data);

		if (size == 2) {
			return m_data;
		}

		address = m_addr + m_data + 4;
	}
	return address;
}
BOOL SC_SeacherCode(wstring Tzm,  DWORD64 StartAdress, DWORD64 EndAdress, int SeacherNum, DWORD64& RetAdress)
{
#define MEMSIZE 4096

	if (SeacherNum == 0)SeacherNum = 1;


	if (Tzm.size() % 2 != 0) {//48 89 58 08 4889681048897018
		return FALSE;
	}

	int 特征码字节总数 = Tzm.size() / 2;//获取字符串字母个数,/2 给两个字母一组的个数

	WORD* 特征码 = new WORD[特征码字节总数];//创建个WORD(2字节)数组(成员是两个字母一组的个数)
	memset(特征码, 0, 特征码字节总数);//清零数组内存

	for (UINT i = 0; i < Tzm.size(); i += 2) {

		//循环赋值给pCode数组,i每次循环+2, 0 2 4 6 8 ....
		wstring strCode = Tzm.substr(i, 2);//根据i来取字符串2个字母

		if (strCode == L"??") { 
			//判断取出来的字母是否为??,如果是,以0xffff形式写进strCode[i/2],这里的i的序列需要0 1 2 3 4 ....  所以根据i+=2 除以2 ,得到序列
			特征码[i / 2] = 0xffff;
		}
		else {
			//如果不是,写进正常的2个字母
			swscanf_s(strCode.c_str(), L"%02X", &特征码[i / 2]);
			/**
				如果 strCode.c_str() 的值是112233
				第一次循环执行 swscanf_s(strCode.c_str(), L"%02X", &特征码[i / 2]); 后
					特征码的值就变成了 11 00
				第二次循环
					特征码的值就变成了 11 00 22 00
				第三次循环
					特征码的值就变成了 11 00 22 00 33 00
			*/
		}
	}



	int NowSeacherNum = 0;//以得到次数
	BYTE 游戏中的代码[MEMSIZE] = { 0 };//创建大小4096的byte 数组  存放整个模块的二进制编码


	for (DWORD64 游戏中代码起始位置 = StartAdress; 游戏中代码起始位置 < EndAdress;) {//判断读取对比是否结束
		//for 循环 CodeAdress从模块基地址开始,到结束地址

		memset(游戏中的代码, 0, MEMSIZE);//给bRead数组清零

		for (size_t i = 0; i < MEMSIZE; i++)
		{
			//循环给bRead数组成员赋值,值来自CodeAdress+(0-4096)的内存地址的值
			游戏中的代码[i] = ReadBytes(游戏中代码起始位置 + i);

		}

		int 游戏中代码索引 = 0;//用于下面for循环,和i作用相似

		for (; (游戏中代码起始位置 + 游戏中代码索引 <= EndAdress) && (游戏中代码索引 < MEMSIZE - 特征码字节总数); 游戏中代码索引++) {
			//(CodeAdress + AsmSite <= EndAdress)判断是否对比到内存结尾  (AsmSite < MEMSIZE - CodeSize) 判断AsmSite的值和4096-特征码数组成员数是否还够
			BOOL IsFindCode = TRUE;//判断是否找到

			for (int 特征码索引 = 0; 特征码索引 < 特征码字节总数; 特征码索引++) {// 循环比较特征码和游戏中的代码,这段代码看不懂说明,不同C/C++数据类型的操作,这里只能尽力去描述
				/**
					特征码[特征码索引],如果特征码的值是 11 00 22 00 33 00
					特征码索引的值是1
					这时得到的值是22 00,因为 特征码 是 WORD* 类型1表示一个 WORD 的大小,WORD的大小是2,这里的1也就是2
				*/
				if (特征码[特征码索引] != 0xffff && 游戏中的代码[游戏中代码索引 + 特征码索引] != (BYTE)特征码[特征码索引]) {//循环判断 特征码数组成员是否为?? 同时 目标内存数组成员和特征码数组成员对比失败
					IsFindCode = FALSE;//判断没找到
					break;//跳出当前循环
				}
				//此循环在没有break的情况下,运行结束,就说明匹配成功,自动退出循环
			}
			if (IsFindCode == TRUE) {//判断找到
				NowSeacherNum++;//得到次数加1
				if (SeacherNum == NowSeacherNum) {//SeacherNum外面传进来的参数,用于得到第SeacherNum次的地址
					RetAdress = 游戏中代码起始位置 + 游戏中代码索引;//CodeAdress + AsmSite计算当前地址

					return TRUE;//查找成功退出
				}
			}

		}
		游戏中代码起始位置 = 游戏中代码起始位置 + 游戏中代码索引;//每次CodeAdress+4096,直到 EndAdress
	}
	return FALSE;
}


img

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值