前置内容:
50.网游逆向分析与插件开发-游戏反调试功能的实现-TP、NP等反调试驱动的原理-CSDN博客
码云地址(master分支):https://gitee.com/dye_your_fingers/sro_-ex.git
码云版本号:87e45c4acc2e842f147ce0e037731fc5a139e047
代码下载地址,在 SRO_EX 目录下,文件名为:SRO_Ex-设置主线程为隐藏调试破坏调试通道.zip
链接:https://pan.baidu.com/s/1W-JpUcGOWbSJmMdmtMzYZg
提取码:q9n5
--来自百度网盘超级会员V4的分享
HOOK引擎,文件名为:黑兔sdk.zip
链接:https://pan.baidu.com/s/1IB-Zs6hi3yU8LC2f-8hIEw
提取码:78h8
--来自百度网盘超级会员V4的分享
以 网游逆向分析与插件开发-代码保护壳的优化-修改随机基址为固定基址-CSDN博客 它的代码为基础进行修改
看完前置内容应该知道为何游戏厂家都要做驱动,不做驱动也确实没法搞,没有下手的机会,但天无绝人之路它还有一个口子用来做反调试,这个口子将来能不能用不知道,这个口子本身就是Windows未公开的api在文档里看它它不是给我们应用层的,它本身是一个驱动层的函数,首先要回顾一下它调试的逻辑,调试的本质下断点的本质就是int3,int3本质就是异常,有了异常以后调试器会接收到异常,然后就给一个处理的机会,有了异常之后它是先找调试器,处理完之后再找调试器,第一次调试器不理它第二次就会崩,这个时候利用驱动层的这个函数能够把线程设置成在调试器下设置为隐藏就是我不响应调试器,就是说面对调试器的时候我就认为我是没有调试器的,实际带来的后果是当这个线程一旦如果说发生不调试的话,这时是附加了调试器的,比如说一个int3的断点本质上是产生的异常,既然会产生异常,调试器又不处理,这个异常是调试器给的,但是调试器不处理,现在除了崩溃就没第二个选择了。
这种口子之类的知识没有地方去找,只能一点点的积累。
通过调用下方的AntiDebug函数(ZwSetInformationThread内核函数)就实现了隐藏线程,效果就是调试器下断点之后游戏会崩溃退出,原因就是通过 ZwSetInformationThread 函数把游戏主线程设置成对于调试器来说是隐藏状态,通俗点说就是游戏出轨了并且被调试器现场捉奸,然后调试器跟游戏离婚了,所以出现int3异常之后调试器不响应游戏,然后游戏只好自己处理异常,然后由于游戏中没有处理异常的代码,游戏又去找调试器,调试器还是不理它,然后就崩溃关闭了,然后下方的代码就破坏了调试的通道,实际使用的时候要猥琐,不能跟 AntiDebug函数里光明正大的用,说它光明正大的原因,首先 ZwSetInformationThread 常量字符串,就可以在字符串表里看出来,还有 ntdll.dll 也是一个常量在字符串表里也能看出来,对于有经验的人来说,看到 ntdll.dll 和 ZwSetInformationThread之后就能知道你在干嘛,所以说 ZwSetInformationThread 函数地址通过登录器传递进去是最好的
GameProtect.cpp文件的修改,修改了 GameProtect函数,新加 AntiDebug函数、ZwSetInformationThreadPtr函数指针
#include "pch.h"
#include "GameProtect.h"
GameProtect* _protect;
extern int client;
unsigned _stdcall GetFunctionAddress(int index) {
//CString txt;
//txt.Format(L"接收到:%d", index);
//AfxMessageBox(txt);
return _protect->GetAddress(index);
}
unsigned GameProtect::GetAddress(int index)
{
//CString txt;
unsigned result = (unsigned)this->_EntryCode[index];
//txt.Format(L"index:%d获取地址:%x", index, result);
// AfxMessageBox(txt);
return result;
}
unsigned GameProtect::GetAddressHide(unsigned _eip)
{
//CString txt;
for (int i = 0; i < _HideCount; i++){
if (_HideCode[i].Start == _eip) {
return (unsigned)_EntryCode[_HideCode[i].Index];
}
}
//txt.Format(L"index:%d获取地址:%x", index, result);
// AfxMessageBox(txt);
return 0;
}
GameProtect::GameProtect()
{
//AfxMessageBox(L"122222");
_protect = this;
AntiDebug();
if (!InitEntryCode()) {
AfxMessageBox(L"程序加载失败!");
ExitProcess(0);
}
CString txt;
txt.Format(L"111");
AfxMessageBox(txt);
}
bool GameProtect::MulCheckBySempore()
{
auto hMuls = OpenSemaphore(SEMAPHORE_ALL_ACCESS, FALSE, L"system_seamp");
if (!hMuls) {
hMuls = CreateSemaphore(0, 3, 3, L"system_seamp");
}
if (WaitForSingleObject(hMuls, 0) == WAIT_TIMEOUT) return true;
return false;
}
void GameProtect::CheckMult()
{
if (MulCheckBySempore()) {
AfxMessageBox(L"当前客户端启动已经超过最大数量");
ExitProcess(0);
}
}
LONG _stdcall PVEHandl(PEXCEPTION_POINTERS val) {
if (val->ExceptionRecord->ExceptionCode == STATUS_BREAKPOINT) {
unsigned _eip = val->ContextRecord->Eip;
unsigned _eipReal = _protect->GetAddressHide(_eip);
/* CString txt;
txt.Format(L"PVEHandl当前地址:%X", _eipReal);
AfxMessageBox(txt);*/
if (_eipReal) {
val->ContextRecord->Eip = _eipReal;
return EXCEPTION_CONTINUE_EXECUTION; // 继续执行
}
else return EXCEPTION_CONTINUE_SEARCH;
}
return EXCEPTION_CONTINUE_SEARCH;
}
bool GameProtect::InitEntryCode()
{
TCHAR FileModule[0x100];
GetModuleFileName(NULL, FileModule, 0x100);
auto hFile = CreateFile(FileModule, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
return false;
}
DWORD dRead;
DWORD filelen = GetFileSize(hFile, &dRead);
char* _data = new char[filelen];
if (ReadFile(hFile, _data, filelen, &dRead, NULL)) {
char* _dataBegin = _data;
unsigned* _uRead = (unsigned*)(_data + filelen - 4);
for (int i = 0; i < filelen - _uRead[0]-4; i++) // 解密数据
{
_data[_uRead[0] + i] = _data[_uRead[0] + i] ^ 0x23;
}
//unsigned* _uRead = (unsigned*)_data[filelen - 4];
filelen = _uRead[0];// 真实的文件大小
_uRead = (unsigned*)(_data + filelen);
unsigned code_count = _uRead[0];
_data = _data + filelen + sizeof(code_count);
PCODEContext _ContextArrys = (PCODEContext)_data;
for (int i = 0; i < code_count; i++)
{
if (_ContextArrys[i].hide) {
_HideCount++;
}
}
if (_HideCount > 0) {
AddVectoredExceptionHandler(1, PVEHandl);
_HideCode = new HIDE_CODE[_HideCount];
_HideCount = 0;
}
_data = _data + sizeof(CODEContext) * code_count;
_EntryCode = new LPVOID[code_count];
for (int i = 0; i < code_count; i++)
{
char* _tmpByte = new char[_ContextArrys[i].len + 2];
_EntryCode[i] = _tmpByte;
_tmpByte[0] = 0x9D;
_tmpByte[1] = 0x61;
/*CString txt;
txt.Format(L"当前地址:%X", _EntryCode[i]);
AfxMessageBox(txt);*/
unsigned offset = sizeof(_ContextArrys[i].r_count) * _ContextArrys[i].r_count;
memcpy((char*)_EntryCode[i] + 2, _data + offset, _ContextArrys[i].len);
unsigned short* rel = (unsigned short*)_data;
for (int x = 0; x < _ContextArrys[i].r_count; x++)
{
unsigned* _callAddr = (unsigned*)((char*)_EntryCode[i] + rel[x] + 1 + 2);
_callAddr[0] = _callAddr[0] - (unsigned)_callAddr - 4;
// AfxMessageBox(L"这里代码存在问题,后面改");
}
_data = _data + offset + _ContextArrys[i].len;
DWORD dOld;
VirtualProtect(_EntryCode[i], _ContextArrys[i].len, PAGE_EXECUTE_READWRITE, &dOld);
if (_ContextArrys[i].hide) {
_EntryCode[i] = (LPVOID)((unsigned)_EntryCode[i] + 2);
_HideCode[_HideCount].Index = i;
_HideCode[_HideCount].Start = _ContextArrys[i].start;
_HideCount++;
}
}
delete[]_dataBegin;
}
else return false;
auto hMod = GetModuleHandle(NULL);
unsigned addMod = (unsigned)hMod;
unsigned addReset = addMod + 0xC2EFFC;
DWORD dOld = GetFunctionAddress(0);
// ::VirtualProtect((LPVOID)addReset, 4, PAGE_EXECUTE_READWRITE, &dOld);
// ::VirtualProtect(this->_GameCode, 0x1000, PAGE_EXECUTE_READWRITE, &dOld);
unsigned* read = (unsigned*)addReset;
read[0] = (unsigned)this->_EntryCodeEx;
//_EntryCode[1] = GetFunctionAddress;
read = (unsigned*)(this->_EntryCodeEx + 1);
read[0] = (unsigned)GetFunctionAddress - 5 - (unsigned)(this->_EntryCodeEx);
return true;
}
/*
第一个参数是线程的id
第二个参数是给一个0x11,0x11就代表把线程设置成隐匿的状态
*/
typedef NTSTATUS (NTAPI* ZwSetInformationThreadPtr)(DWORD,DWORD,DWORD,DWORD);
void GameProtect::AntiDebug()
{
/**
这个函数在ntdll..dll里
这个位置真正使用时不能这样写,因为这样写了之后,会被人看出来
最好的方式是通过启动器计算好传递过来,传递过来就是悄无声息了
相当于调用了一个函数,但破解者不知道,调用函数还可以猥琐点
把它拷贝到我们的内存空间里调用。
*/
auto hNtdll = LoadLibrary(L"ntdll.dll");
if (hNtdll) {
ZwSetInformationThreadPtr zwSetInformationThreadPtr;
/**
GetProcAddress通过导出表获取函数
*/
zwSetInformationThreadPtr = (ZwSetInformationThreadPtr)GetProcAddress(hNtdll, "ZwSetInformationThread");
zwSetInformationThreadPtr((DWORD)GetCurrentThread(), 0x11, 0x0, 0x0);
}
}
GameProtect.h文件的修改,新加AntiDebug函数
#pragma once
typedef struct CODEContext {
unsigned start;
bool hide;
unsigned short r_count; // 重定位信息,call、jmp这样的需要重定位的数据的个数
unsigned short len; // 代码的长度
}*PCODEContext;
typedef struct HIDE_CODE {
unsigned Start;
unsigned Index;
}*PHIDE_CODE;
class GameProtect
{
public:
unsigned GetAddress(int index);
unsigned GetAddressHide(unsigned _eip);
GameProtect();
private:
int _HideCount = 0;
PHIDE_CODE _HideCode;
LPVOID* _EntryCode;
int _CodeCount{};
char _EntryCodeEx[8]{(char)0xE8, (char)0x00, (char)0x00,(char)0x00, (char)0x00, (char)0xFF, (char)0xE0};
bool MulCheckBySempore();
public:
void CheckMult(); // 检测有没有多开
public:
bool InitEntryCode(); // 释放保护代码数据
private:
void AntiDebug();
};