免责声明:内容仅供学习参考,请合法利用知识,禁止进行违法犯罪活动!
本次游戏没法给
内容参考于:微尘网络安全
上一个内容: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;
}