该反外挂系统是某游戏公司的,只是好奇如有得罪,请谅解及联系版主删除!
pig x因为听别人说有一牛b的反外挂系统,故而好奇分析之,由于没有壳让我十指大动,逐一分析之。
看过后,逐步了解其工作原理,现将其工作原理简单阐述下:
收到服务器返回的包,要求客户端根据包的内容进行解析和执行,将正确的结果返回服务,以到达验证的目的。
因为服务器返回的执行代码,让客户端正确加载dll后执行,所以服务器可以让你客户端执行它想检查外挂或者
其它意图的程序哦?可不可能让你执行木马?理论上是可以的,但是估计官方不会自己扇自己耳光吧!
1,首先是初始化数据,服务器返回第一包,里面包含即将要调用的dll数据和函数名称,目的是让后面的要调用的
代码作前期初始化工作。每个特殊含义的数据,都有自己的ID,后面根据ID进行解析出正确的fuc。
贴上解析数据,并进行数据解析的函数:复制内容到剪贴板代码:
DWORD _CallRetEax(LPBYTE pData)
{
DWORD dwRetEax = 0xFFFFFFFF;
INT nPox = 0;
CHAR* pRecvBuffer = NULL ;
INT nLen;
RetPoxLen pRetPoxLen;
INT n1stType = 0;
INT n;
INT n1stLen = 0;
INT nLastType = 0;
INT nTypeID = 0;
INT n1stTypeLen = 0;
INT n2scTypeLen = 0;
INT n2sc = 0;
INT nLastSum = 0;
INT nLast = 0;
INT nLastLen = 0;
WORD wInsertId = 0;
CHAR* pCallBuffer= NULL;
DWORD CallAddr;
WORD wFixPox = 0;
WORD wFixId = 0;
WORD wFindFixId = 0;
GS_CHECKPTR(pData);
pRecvBuffer = (CHAR*)(pData);
nPox = 0;
nLen = *(WORD*)(pRecvBuffer + nPox);
nPox += 8;
pRetPoxLen = _RetLen(pRecvBuffer + nPox);
nPox += pRetPoxLen.nPox;
//大类个数
n1stType = pRetPoxLen.nLen;
for (n = 0; n < n1stType ; n++)
{
nLastLen = 0;
pRetPoxLen = _RetLen(pRecvBuffer + nPox);
nPox += pRetPoxLen.nPox;
//大类长度
n1stLen = pRetPoxLen.nLen;
//大类长度
n1stTypeLen = *(WORD*)(pRecvBuffer + nPox);
nLastLen +=2;
n1stTypeLen -= 2;
//ID
nTypeID = *(WORD*)(pRecvBuffer + nPox + nLastLen);
nLastLen +=2;
wInsertId = _FidUseId(nTypeID);
if(wInsertId == 0xFFFF)
{
wInsertId = _FidNoUseId();
}
m_szRetCall[wInsertId].wId = nTypeID;
n1stTypeLen -= 2;
//读法类型
nLastType = *(WORD*)(pRecvBuffer + nPox + nLastLen);
nLastLen +=2;
n1stTypeLen -= 2;
switch(nLastType)
{
case 0://读STRING 初始化API名称+数据
nPox += nLastLen;
memcpy(m_szRetCall[wInsertId].szRetBuffer ,pRecvBuffer + nPox, n1stTypeLen);
m_szRetCall[wInsertId].pRetBuffer = m_szRetCall[wInsertId].szRetBuffer;
nPox += n1stTypeLen;
break;
case 1://DWORD+DWORD 初始化API对应关系
nPox += nLastLen;
m_szRetCall[wInsertId].w1stId = *(WORD*)(pRecvBuffer + nPox);
nPox +=2;
m_szRetCall[wInsertId].w2secId = *(WORD*)(pRecvBuffer + nPox);
nPox +=2;
break;
case 2://开始执行CALL
//nPox += nLastLen;
//dwRetEax = _CallBufferId(nTypeID);
__logger.Trace("RetEax = %p /n", m_pHook->_dwRetvalue);
__logger.Trace("-------------------------------------/n");
__logger.Trace("END/n/n");
__logger.Fflush();
break;
default://对CALL开始修正
//初始化传过来的API
_InitApiDate();
//开始修正CALL BUFFER
if(nLastLen < nLastType )
{
//读出要修正地方的个数
nLastSum = *(WORD*)(pRecvBuffer + nPox + nLastLen);
nLastLen += 2;
n1stTypeLen -= 2;
//循环读出修改的内容
for(nLast = 0; nLast < nLastSum; nLast++)
{
wFixPox = *(WORD*)(pRecvBuffer + nPox + nLastLen);
nLastLen +=2;
wFixId = *(WORD*)(pRecvBuffer + nPox + nLastLen);
nLastLen +=2;
n1stTypeLen -= 4;
//修改CALL BUFFER的API地址
wFindFixId = _FidUseId(wFixId);
if(wFindFixId != 0xFFFF)
{
//修改为0的地方
if(m_szRetCall[wFindFixId].dwApiAddr != 0)
{
if( *(BYTE*)(pRecvBuffer + nPox + wFixPox - 1 ) == 0x0E8)
{
//CALL XXXXX
CallAddr = (DWORD)m_szCallBuffer;
*(DWORD*)(pRecvBuffer + nPox + wFixPox) = (DWORD)(m_szRetCall[wFindFixId].dwApiAddr - CallAddr -(wFixPox - nLastSum*4 - 8) - 4);
__logger.Trace("Pox:%p CALL %s /n",wFixPox - nLastSum*4 - 8, m_szRetCall[_FidUseId(m_szRetCall[wFindFixId].w2secId)].szRetBuffer);
__logger.Fflush();
}else if( *(WORD*)(pRecvBuffer + nPox + wFixPox - 2 ) == 0x15FF)
{
//CALL [XXXXX]
*(DWORD*)(pRecvBuffer + nPox + wFixPox) = (DWORD)&(m_szRetCall[wFindFixId].dwApiAddr);
__logger.Trace("Pox:%p CALL %s /n",wFixPox - nLastSum*4 - 8, m_szRetCall[_FidUseId(m_szRetCall[wFindFixId].w2secId)].szRetBuffer);
__logger.Fflush();
}else if( *(BYTE*)(pRecvBuffer + nPox + wFixPox - 2 ) == 0x8B ||
*(BYTE*)(pRecvBuffer + nPox + wFixPox - 1 ) == 0xA1 )
{
//MOV RB32 , [XXXXX]
*(DWORD*)(pRecvBuffer + nPox + wFixPox) = (DWORD)&(m_szRetCall[wFindFixId].dwApiAddr);
__logger.Trace("Pox:%p MOV RB32, %s /n",wFixPox - nLastSum*4 - 8, m_szRetCall[_FidUseId(m_szRetCall[wFindFixId].w2secId)].szRetBuffer);
__logger.Fflush();
}else if( *(BYTE*)(pRecvBuffer + nPox + wFixPox - 1 ) == 0x0B8 ||
*(BYTE*)(pRecvBuffer + nPox + wFixPox - 1 ) == 0x0B9 ||
*(BYTE*)(pRecvBuffer + nPox + wFixPox - 1 ) == 0x0BA ||
*(BYTE*)(pRecvBuffer + nPox + wFixPox - 1 ) == 0x0BB ||
*(BYTE*)(pRecvBuffer + nPox + wFixPox - 1 ) == 0x0BC ||
*(BYTE*)(pRecvBuffer + nPox + wFixPox - 1 ) == 0x0BD ||
*(BYTE*)(pRecvBuffer + nPox + wFixPox - 1 ) == 0x0BE ||
*(BYTE*)(pRecvBuffer + nPox + wFixPox - 1 ) == 0x0BF
)
{
//MOV RB32 , XXXXX
*(DWORD*)(pRecvBuffer + nPox + wFixPox) = (DWORD)&(m_szRetCall[wFindFixId].dwApiAddr);
__logger.Trace("Pox:%p MOV RB32, %p = %p /n",wFixPox - nLastSum*4 - 8, m_szRetCall[_FidUseId(m_szRetCall[wFindFixId].w2secId)].szRetBuffer);
__logger.Fflush();
}
}else
{
//此为读常量,非API地址的情况 (地址的地址)
*(DWORD*)(pRecvBuffer + nPox + wFixPox) = (DWORD)&(m_szRetCall[wFindFixId].pRetBuffer);
__logger.Trace("Pox:%p [%p] = %p /n",wFixPox - nLastSum*4 - 8,m_szRetCall[wFindFixId].szRetBuffer,*(DWORD*)m_szRetCall[wFindFixId].szRetBuffer);
__logger.Fflush();
}
}else
{
__logger.Trace("Pox:%p CALL [addr] = %p /n", wFixPox - nLastSum*4 - 8, wFixId);
__logger.Fflush();
dwRetEax = 0;
return dwRetEax;
}
}
}
//余下为CALL BUFFER
nPox += nLastLen;
ZeroMemory(m_szCallBuffer,sizeof(m_szCallBuffer));
__logger.Trace("/nCallBufferAddr: %p CallBufferLen: %p /n/n", &m_szCallBuffer, n1stTypeLen);
memcpy(m_szCallBuffer ,pRecvBuffer + nPox, n1stTypeLen);
m_szRetCall[wInsertId].pCallBufferAddr = (CHAR*)m_szCallBuffer;
nPox += n1stTypeLen;
break;
}
}
GS_EXIT:;
return dwRetEax;
}2,经过上面的解析后,根据数据进行函数初始化复制内容到剪贴板代码:
void _InitApiDate()
{
HINSTANCE hInstLibrary;
CHAR* cDllName;
CHAR* cApiName;
INT nFindId;
for(INT i = 0;i <= CALL_BUFFER_NO; i++)
{
if( m_szRetCall[i].wId != 0 &&
m_szRetCall[i].w1stId != 0 &&
m_szRetCall[i].dwApiAddr == 0 &&
m_szRetCall[i].w2secId != 0
)
{
nFindId = _FidUseId(m_szRetCall[i].w1stId);
cDllName = m_szRetCall[nFindId].szRetBuffer;
hInstLibrary = LoadLibrary(cDllName);
nFindId = _FidUseId(m_szRetCall[i].w2secId);
cApiName = m_szRetCall[nFindId].szRetBuffer;
m_szRetCall[i].dwApiAddr = (DWORD)GetProcAddress(hInstLibrary,cApiName);
FreeLibrary(hInstLibrary);
}
}
return;
}3,根据
case 2://开始执行CALL
去调用要执行的函数代码复制内容到剪贴板代码:
INT _CallBufferId(DWORD dwId)
{
CHAR* pCallBuffer;
INT nRet;
DWORD dwFindUseId;
BOOL bRetPortect;
DWORD dwOldType;
pCallBuffer = (CHAR*)m_szCallBuffer;
dwFindUseId = (DWORD)_FidUseId(dwId);
bRetPortect = VirtualProtect(m_szCallBuffer, 8190, PAGE_EXECUTE_READWRITE, &dwOldType);
if(m_szRetCall[dwFindUseId].pCallBufferAddr != 0)
{
__asm
{
pushad
pushfd
mov eax, pCallBuffer
call eax
mov nRet, eax
popfd
popad
}
}
bRetPortect = VirtualProtect(m_szCallBuffer, 8190, dwOldType, &dwOldType);
return nRet;
}这里返回的eax就是反外挂需要的数据,服务器根据此数据来判断你是否作弊。到此,这反外挂系统简单流程就是如此了。
专门看了下送来执行的几个函数,发现它们在检查这些数据:
1)代码crc
2)窗体判断
3)hookdll判断
这些都是反外挂的小功能fuc,当然官方想加什么就加什么,应该非常好diy的,故该游戏的外挂比较少,也是该系统的功能,
浅析到此为止,如有深入的分析和见解,请发到论坛上来吧