1.稳定注入姿势
2.处理TenRpcs检测CLL
3.处理TCJ
4.探测GameRpcs
笔者最近迷上LOL,中低分段总是有脚本,被虐的很惨
然后笔者去市场上买来几款外挂,配合防封,然后小逆了一下,发现市场上的防封软件大多是通对游戏 LoadLibraryExW 挂钩子,让某些检测模块不加载,在这种情况下,阻断了通讯,这样可以稳定的玩游戏5局左右,之后就会封7天或者三年,如果处理了TenRpcs里的检测CALL(技能释放一类的)就是7天,如果没有处理就是3年,然后经过笔者多处咨询,发现市面上没有能稳超过10把的防封,于是笔者想看看TP到底是什么手段检测的,于是我与LOL迂回了半月之久,废话不多说,进入正题
1.注入姿势:
笔者采用 MDL内存映射形式向游戏进程写入了我的DLLShellCode ,用 NtCreateThreadEx 创建远程线程,游戏未发现,目前这个注入姿势是稳定的
2.处理检测CALL:
笔者在游戏主模块处发现游戏挂了70个钩子指向TenRpcs.dll,不解释,盘他
BOOL RecoveryCode(ULONG32 地址, BYTE* 字节)
{
BYTE 判断 = { NULL };
DWORD 基址 = 0x400000;
DWORD 修改前的页面保护属性 = NULL;
RtlCopyMemory(&判断, LPVOID(基址 + 地址), 1);
if (判断 != 0xE8)
{
全局变量::当前大区未开放检测数++;
return FALSE;
}
if (VirtualProtect(LPVOID(基址 + 地址), 5, PAGE_EXECUTE_READWRITE, &修改前的页面保护属性))
{
RtlCopyMemory(LPVOID(基址 + 地址), 字节, 5);
if (VirtualProtect(LPVOID(基址 + 地址), 5, PAGE_EXECUTE, &修改前的页面保护属性))
{
全局变量::处理检测成功次数++; //std::cout << "√ " << hex << 基址 + 地址 << std::endl;
return TRUE;
}
}
全局变量::处理检测失败次数++; //std::cout << "X " << hex << GetLastError() << std::endl;
return FALSE;
}
VOID 处理检测()
{
LPVOID 内存地址 = NULL;
DWORD 函数地址 = NULL;
DWORD 模块句柄 = NULL;
DWORD 修改前的页面保护属性 = NULL;
do
{
窗口句柄::游戏窗口句柄 = FindWindow("RiotWindowClass", NULL);
} while (!窗口句柄::游戏窗口句柄);
///此处过检测代码由于危害腾讯安全给告知于19.02.05 21:30分删除此部分代码
string Message = "处理失败 " + to_string(全局变量::处理检测失败次数) + " 个检测点\n成功处理 " + to_string(全局变量::处理检测成功次数) + " 个检测点\n当前大区未开放的检测点有 " + to_string(全局变量::当前大区未开放检测数) + " 个\n\n是否继续游戏???";
if (MessageBox(窗口句柄::游戏窗口句柄, Message.c_str(), "", MB_ICONQUESTION | MB_YESNO | MB_TOPMOST) != IDYES)
exit(0);
}
到这里的时候,笔者进行了游戏,发现封三年,于是笔者不开挂,只干检测一样被封三年,说明了什么
对!没错 CRC!不得不过 TP 检测系统的CRC有漏洞,过CRC代码如下,你会惊呆的
VirtualProtect(LPVOID(0x401000), 0x1028000, PAGE_EXECUTE, &修改前的页面保护属性)
哈哈,就是这么简单!这个为什么!咱就不多解释了,不是傻瓜都看得懂原理,我直接贴代码通俗易懂!
通过以上小操作后,单挂我自己的防封没有三年了,然后开始下一项工作,TCJ!
TCJ是干嘛的?在这里我科普一下,他会 EnumWindowProc 枚举你所有窗口,OpenProcess ReadFile… 太多了
总是TCJ负责一些外部检测以及内部检测,包括你注入的DLL文件,MD5哈希,窗口标题,类名,PE头,等等 … 太多了
所以,想要完全达到防封状态,只干掉检测CALL还不行,必须要处理TCJ
于是我模仿了市场外挂 向 LoadLibraryExW 挂钩子,让TCJ在加载列表里消失,游戏不加载TCJ了,进入游戏爽
但是没爽多久5分钟后游戏弹出错误窗口,强制关闭,于是笔者调试跟踪到执行退出函数的点,发现在GameRpcs里,哇,还有这个东西,我都没注意!
于是分析了一下,这个GameRpcs非常不起眼,但是他起到重要的作用,他负责与TCJ和TenRpcs通讯!如果其中有一个模块有异常,他就会强制关闭游戏(本地命令)不是来自服务器的断开链接!而且通过笔者的CE遍历,遍历到了GameRpcs还会收集对局信息,包括当前玩家按下执行右键命令次数,玩家按下技能右键次数,等等信息,大家可以通过CE去搜索增加的值就可以,说到这里,我感觉这个检测系统好强大,收集这些数据是这是要人工分析吗??
笔者后续再写是怎么过的GameRpcs,先去看春晚了,大家新年快乐!
回顾一下前文提到的东西
游戏主要检测分为5大块
1.CALL检测 ->由TenRpcs负责
2.文件及内存检测 - > 由TCJ负责
3.客户行为游戏数据检测以及本地封包加密解密发送回传 - > 由GameRpcs负责
4.本地机器码查询 - > 由PolicyProbe负责
5.调试游戏等保护 -> 由TerSafe负责
我们需要处理的东西是1.2.3
机器码解决办法是 老办法 钩住 LoadLibraryExW 让 PolicyProbe 不在载入列表里即可过掉机器码检测
TerSafe 我们不做处理,没有必要
接下来我们编写一个注入到游戏大厅的DLL,然后转换成ShellCode,为了安全起见,先要把大厅的保护干掉
代码如下
VOID 干游戏大厅检测() //稳妥起见还是要干掉把
{
INT 判断计次 = NULL;
BYTE 判断字节 = NULL;
DWORD 临时变量 = NULL;
DWORD 修改前的页面保护属性 = NULL;
ULONG32 模块地址 = NULL;
do
{
模块地址 = ULONG32(GetModuleHandle("TenRpcs.dll"));
} while (!模块地址);
do
{
RtlCopyMemory(&判断字节, LPVOID(模块地址 + /*此处地址屏蔽*/), sizeof(判断字节));
} while (判断字节 != 0x68);
临时变量 = /*此处地址屏蔽*/;
BYTE TenRpcs检测点1[] = { 0xE9, 0x60, 0x06, 0x00, 0x00, };
if (VirtualProtect(LPVOID(模块地址 + 临时变量), sizeof(TenRpcs检测点1), PAGE_EXECUTE_READWRITE, &修改前的页面保护属性))
{
RtlCopyMemory(LPVOID(模块地址 + 临时变量), &TenRpcs检测点1, sizeof(TenRpcs检测点1));
if (VirtualProtect(LPVOID(模块地址 + 临时变量), sizeof(TenRpcs检测点1), PAGE_EXECUTE, &修改前的页面保护属性))
{
RtlZeroMemory(TenRpcs检测点1, sizeof(TenRpcs检测点1));
}
}
CloseHandle(保护检测线程句柄);
}
干掉这个检测点之后 游戏大厅的保护系统就崩溃了,包括TerSafe,所有检测不会初始化,不知道大厅检测什么,为了安全起见还是干掉把
一定要在第一时间注入,否则保护初始化完成就没机会了
VOID 等待游戏()
{
INT 对局计次 = 1;
HWND 窗口句柄 = NULL;
DWORD 游戏进程ID = NULL;
ULONG32 模块句柄 = NULL;
INT 文字颜色[4] = { FOREGROUND_BLUE, FOREGROUND_GREEN, FOREGROUND_RED, FOREGROUND_INTENSITY };
do
{
Sleep(100);
窗口句柄 = FindWindow("RCLIENT", "League of Legends");
} while (!窗口句柄);
AllocConsole();
srand((int)time(0));
freopen("conout$", "w", stdout);
CONSOLE_CURSOR_INFO Cursor_info = { 1, 0 };
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 文字颜色[rand() % 3 + 1]);
//DeleteMenu(GetSystemMenu(GetConsoleWindow(), FALSE), SC_CLOSE, MF_BYCOMMAND);
JMP1:
std::cout << std::endl;
std::cout << "[+] 正在等待游戏对局中" << std::endl;
do
{
Sleep(10);
游戏进程ID = GetPID("League of Legends.exe");
} while (!游戏进程ID);
if (内存注入远程线程DLL(游戏进程ID, hexData1, sizeof(hexData1)))
{
if (内存注入远程线程DLL(游戏进程ID, hexData2, sizeof(hexData2)))
std::cout << "[+] 第" << 对局计次 << "场游戏对局已开始" << std::endl;
}
do
{
Sleep(100);
游戏进程ID = GetPID("League of Legends.exe");
} while (游戏进程ID);
对局计次++;
goto JMP1;
}
注入之后执行我们的循环代码,判断游戏是否启动
LOL过检测技術
启动后第一时间注入我们的 ShellCode 并执行
DLL转ShellCode用010Editor即可
初步尝试1:抹掉TCJ
TCJ模块加载失败后,游戏正常运行5分钟后自动退出,上一篇文章说到是GameRpcs的问题然后尝试方案2
初步尝试2:抹掉GameRpcs+TCJ
正常游戏,不会掉线,大概打了3-4局之后,被服务器断开链接,封7天
初步尝试3:正常加载 GameRpcs+TCJ 在TCJ线程入口处返回Error
游戏正常运行,7分钟左右掉线,打完一局掉5次线,和尝试1没有太大区别,检测处理0状态,笔者难受的打了5局,不会封7天也不会封三年,但是太难受
再次尝试4:改TCJ线程里的Sleep,Push 64 Push1470 等这些参数
游戏正常运行,不会掉线,三年,说明了还是检测到了外挂,于是我把参数范围扩大到四字节,Sleep(1000000000)例如这样,笔者脑子坏掉了,开始这样做的时候想没有通讯肯定会7天,事实证明了我脑子进水了,的确是7天,没有必要做这个举动
到这里,我静下心来想,有没有一个完美的办法,既能让TCJ检测不到外挂特征,又能正常的与GameRpcs通讯呢
可行的办法1. 钩住所有TCJ调用的重要的Fun包括
LOL过检测技術
笔者检查了一下,他的导入全都是外挂检测相关,如果要一个一个去处理,那我的工作量太大了,没有半个月一个月弄不完
然后又想有没有什么简单的方法,想想想了一晚上,最后想出了一个办法,预知结果如何,下次分解,今天先讲到这里
代码段没法发了,因为里面有别人的东西(发了之后避免不了一些不法分子会利用,所以这里只分享处理检测流程和思路)
目前整体已经完善好了,写这个帖子不是教大家怎样去过检测写外挂,而是教大家一些好的思路
核心代码执行流程
LOL过检测技術
1.过机器码: 目前2种办法,一种替换DLL,另一种是让返回值随机,因为我总调试游戏
我直接替换了DLL因为有时候进游戏没加载我忘记加载我的DLL,就完蛋了,如果不商业的话,建议用替换DLL方法,放到Game目录即可,DLL在附件中 2.处理游戏检测CALL,例如释放技能,走路,等等,已经定位了几个重要的检测CALL,处理后发现还有很多检测CALL,但是不知道再检测什么东西,所以全盘了,如果出问题再注释,也方便,这个在第一篇说过了,这里不再细说
3.处理GameRpcs的CRC,处理CRC的思路也很多,这里我用的是转移大法,然后再创建一个线程循环清除GameRpcs记录的数据信息
VOID 清除记录()
{
DWORD 计次变量 = 0;
while (GameRpcsBase)
{
Sleep(10000);
RtlCopyMemory(LPVOID(GameRpcsBase + 0x1713A4), &计次变量, sizeof(DWORD));//时间计时
RtlCopyMemory(LPVOID(GameRpcsBase + 0x17141C), &计次变量, sizeof(DWORD));//鼠标计次
RtlCopyMemory(LPVOID(GameRpcsBase + 0x171424), &计次变量, sizeof(DWORD));//键盘计次
计次变量++;
}
}
TCJ(可以不清Buffer,直接改Buffer指向一片干净的内存区域)
监视TCJ读了哪块内存,HOOK,如果当前读内存的地址 大于 你要保护的模块地址 或 小于 你要保护的模块地址 + 模块大小,则清空他的Buffer
LOL过检测技術
HOOK FindWindow 或者写个 EnumWindowsProc 还有要HOOK CreateFile 做好外部检测防御,其实就这些了,到结帖,已经过掉全部检测,其实就这些了
算上三局教程 10局 突然觉得开挂没有意思了,没继续打了,过几天研究研究 apex英雄 再次感谢 DebugSp 大神提供的思路
|