好多人研究bypass.我对这些也很感兴趣.今天在一个韩国人的博客上.看到了一篇关于Anit Hacking Games的文章.配合自己的一些心得.分享给大家.这篇文章的大部分内容都将不是我的原创.It's from Dual..貌似他是个女的还..我没敢确定.好废话不说了.切入正题.
首先我们要知道一些反外挂系统的基本原理.论坛上已经有非常多的文章.更有象逆向Xtrap之类的文章.希望大家好好学习学习.
这篇文章将只介绍ring3级反调试的一些简单的方法..是给初级得人看的..
下面的方法比较简陋.还不能和NP等反外挂系统相提并论.只当增加一些编程的技巧给大家好了:)
其实往简单里说.我们可以通过下面几种方法判断是不是有调试器在调试我们的游戏.如果发现直接ExitProcess().
第一.比较弱智的.通过查找窗口和类名.看看有没有一些调试器.下面的代码写的很漂亮.封装的也很好.你可以拿去用.
char szWindowDataBase[][30] = {"CheatEngine",
"AutoPlay",
"GameHack",
"Memory Search",
"TSearch"};
char szClassDataBase[][30] = {"CheatEngine",
"AutoPlay",
"GameHack",
"Memory Search",
"TSearch"};
//以上是窗口和类名.你可以多写几个
BOOL CALLBACK EnumWinProc(HWND hwnd,LPARAM lparam)
{
char szWindow[255];
char szClass[255];
if(GetParent(hwnd)) return TRUE;
GetWindowText(hwnd,szWindow,255);
GetClassName(hwnd,szClass,255);
for(int i = 0; i < cnt; i++)
{
if(!strnicmp(szWindowDataBase[i],szWindow,strlen(szWindowDataBase[i]))) //判断是否有匹配的标题名
{
MessageBox(NULL,"发现作弊程序.\n游戏将退出!!","警告",MB_ICONSTOP);
ExitProcess(-1);
}
}
for(i = 0; i < cnt; i++)
{
if(!strnicmp(szClassDataBase[i],szClass,strlen(szClassDataBase[i]))) //判断是否有匹配的窗口类名
{
MessageBox(NULL,"发现作弊程序.\n游戏将退出!!","警告",MB_ICONSTOP);
ExitProcess(-1);
}
}
return TRUE;
}
第二.上面的方法实在简陋.我们想一想.如果调试器运行了.必然在内存中.对.我们这次要在内存中寻找它.
char StringDataBase[][30] = {"CheatEngine",
"AutoPlay",
"GameHack",
"Memory Search",
"TSearch",
"This"};
//依然用这个笨办法.
EnumProcesses(ProcessesID,sizeof(ProcessesID),&dbNeeded); //得到进程ID.
for(int i = 0; i < dbNeeded / sizeof(DWORD); i++)
{
if((ProcessesID[i] == GetCurrentProcessId()) || ProcessesID[i] == 4) continue; //不检测自己
hProcess = OpenProcess(PROCESS_VM_READ|PROCESS_QUERY_INFORMATION,FALSE,ProcessesID[i]); //打开进程
if(hProcess)
{
EnumProcessModules(hProcess,&hMod,sizeof(hMod),&dbNeeded2);
GetModuleBaseName(hProcess,hMod,szProcess,sizeof(szProcess)); //GetModuleBaseName???
p = struct_si.lpMinimumApplicationAddress; //SYSTEM_INFO struct_si;
do
{
VirtualQueryEx(hProcess,p,&struct_mbi, sizeof(struct_mbi));
newbuf = malloc(struct_mbi.RegionSize); //MEMORY_BASIC_INFORMATION struct_mbi;
if(newbuf)
{
ReadProcessMemory(hProcess,p,newbuf,struct_mbi.RegionSize,&ret);
if(ret)
{
for(DWORD i = (DWORD)newbuf; i <= (DWORD)newbuf+(DWORD)struct_mbi.RegionSize; i++)
{
for(int j = 0; j < cnt; j++)
{
if(!strnicmp((char *)StringDataBase[j],(char *)i,strlen((char *)StringDataBase[j])))
{
MessageBox(NULL,"发现作弊程序","警告",MB_ICONSTOP);
CloseHandle(hProcess);
ExitProcess(-1);
}
}
}
}
}
free(newbuf);
*((PDWORD)&p) += (DWORD)struct_mbi.RegionSize;
}while(p < struct_si.lpMaximumApplicationAddress); //从内存地址中做个大循环.我不太喜欢do while >_<
}
CloseHandle(hProcess);
}
这么多层循环看的真晕..大家不必明白.就是从内存中找那些名字而已.思路简单.但实现起来确实比较困难.我不知道还有什么其他的方法...
第三.事件查询.作弊的东东肯定会留下痕迹的..
BOOL CheckByFileMapName()
{
char szFileMapDataBase[][30] = {"DUALMEM",
"TSearch",
"CheatEngine",
"GameHack"
};
//总觉得这种方法欠妥.不过可以考虑放在一个文件中.你的反外挂系统可以象病毒库一样.时时更新!
HANDLE hFileMap;
LPVOID pMapFile;
for(int cnt = 0; ;cnt++)
if(!szFileMapDataBase[cnt][0])
break;
for(int i = 0; i < cnt; i++)
{
hFileMap = CreateFileMapping((HANDLE)INVALID_HANDLE_VALUE,NULL,
PAGE_READWRITE,0,1,szFileMapDataBase[i]);
if(GetLastError() == ERROR_ALREADY_EXISTS) //竟然用这种方法..我一直只用GetLastRrror来调试程序..= =||
{
MessageBox(NULL,"发现作弊程序","警告",MB_ICONSTOP);
ExitProcess(-1);
}
}
return TRUE;
}
BOOL CheckByEventName()
{
char szEventDataBase[][30] = {
"User stopped search",
"Debugger Loaded",
"TSearch.ServerLoaded"
};
HANDLE hEvent;
for(int cnt = 0; ;cnt++) //Counting Pattern
if(!szEventDataBase[cnt][0])
break;
for(int i = 0; i < cnt; i++)
{
if(OpenEvent(EVENT_ALL_ACCESS,FALSE,szEventDataBase[i]))
{
MessageBox(NULL,"发现作弊程序","警告",MB_ICONSTOP);
ExitProcess(-1);
}
}
return TRUE;
}
BOOL CheckByMutexName() //其实这个跟第一个大同小意
{
char szMutexDataBase[][30] = {
"TSearch",
"CheatEngine",
"GameHack"
};
HANDLE hEvent;
for(int cnt = 0; ;cnt++) //Counting Pattern
if(!szMutexDataBase[cnt][0])
break;
for(int i = 0; i < cnt; i++)
{
CreateMutex(NULL,FALSE,szMutexDataBase[i]);
if(GetLastError() == ERROR_ALREADY_EXISTS)
{
MessageBox(NULL,"发现作弊程序","警告",MB_ICONSTOP);
ExitProcess(-1);
}
}
return TRUE;
} 第四.光检测还不够.万一别人反汇编你的程序.给你把这些nop掉了.你不是白忙活了.所以我们得自检!
以下就是CRC检测的方法.只是个演示程序.
int main(int argc, char* argv[])
{
int a;
DWORD checksum = 0xE1E4CDE2; //这里根据你的文件而定.
__asm
{
pushad
pushfd
mov esi,offset StartAddressOfCheck
mov ecx,offset EndAddressOfCheck
sub ecx,offset StartAddressOfCheck
xor eax,eax
xor ebx,ebx
Check_Loop:
mov ebx, [esi]
add eax,ebx
rol eax,1
inc esi
loop Check_Loop
cmp eax,checksum
jne EndAddressOfCheck //跳过去就好玩啦:)
popfd
popad
}
StartAddressOfCheck: //入口检测
a = 5;
if(a > 10)
{
MessageBox(NULL,"You beat this program!!","Congratulation",64);
return 0; //这里只是一个演示
}
EndAddressOfCheck:
__asm
{
popfd
popad
}
MessageBox(NULL,"You are Fucking me??","Cracker!!",MB_ICONWARNING); //这句话相当的有意思^_^
return 0;
}
另外.在You first pe pack这篇文章中.也提到了相关的反调试技术.让我们来参考以下.
附一.其实Windows已经为我们提供了一个API来检测是否被调试.IsDebuggerPresent()就是它!当你的程序被调试时.它会返回一个非0的值!希望做外挂的不要HOOK掉这个函数:)
附二.判断SoftICE之类的软件.注意.因为在98和NT下.驱动是不一样的.所以写法也不一样.判断操作系统必不可少.虽然现在98基本消失了.
在NT中
if(CreateFile( "\\\\.\\NTICE", GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,
NULL)!=INVALID_HANDLE_VALUE)
{
You are Fucking me?? //继续引用那句话:)
}
在98中
if(CreateFile( "\\\\.\\SICE", GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,
NULL)!=INVALID_HANDLE_VALUE)
{
You are Fucking me?? //:p
}
附三.翻译了又翻译.怎么都觉得不好.没能表达出它的含义.大体意思就是怎么找父进程再检查它是不是EXPLORER.EXE.实在觉得翻译的不怎么样.所以把原文贴出来.没什么难的单词.大家看看就明白了.:)
原文: Probe Processes: Sometimes, it needs to search for specific process or isolates to specific process. It was demonstrated how finding parent process and check if it is EXPLORER.EXE and killing all parent process except Explorer windows in sample source code.
void GetFileNameFromPath(char* szSource) //这个函数应该收藏
{
char *szTemp=strrchr(szSource,'\\');
if(szTemp!=NULL)
{
szTemp++;
DWORD l=DWORD(strlen(szTemp))+1;
CopyMemory(szSource,szTemp,l);
}
}
void AntiDebug()
{
char lpszSystemInfo[MAX_PATH];
HANDLE hSnapshot=NULL;
DWORD PID_child;
DWORD PID_parent,PID_explorer;
HANDLE hh_parnet = NULL;
PROCESSENTRY32 pe32 = {0};
pe32.dwSize = sizeof(PROCESSENTRY32); //就是128了.
PID_child=GetCurrentProcessId(); //我是孩子:p
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0); //建立快照
if (Process32First(hSnapshot, &pe32))
{
while (Process32Next(hSnapshot, &pe32)) //Process32First and Process32Next 可恶的M$非要我们这样写>_<
{
GetFileNameFromPath(pe32.szExeFile);
CharUpperBuff(pe32.szExeFile,strlen(pe32.szExeFile));
if(strcmp(pe32.szExeFile,"EXPLORER.EXE")==0)
{
PID_explorer=pe32.th32ProcessID;
}
if(pe32.th32ProcessID==PID_child) // th32ProcessID用的很巧妙
{
PID_parent=pe32.th32ParentProcessID;
}
}
}
if(PID_parent!=PID_explorer)
{
hh_parnet= OpenProcess(PROCESS_ALL_ACCESS,
TRUE, PID_parent);
TerminateProcess(hh_parnet, 0); //You are Fucking me?? :p
}
else
{
MODULEENTRY32 me32 = {0};
me32.dwSize = sizeof(MODULEENTRY32);
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, PID_explorer);
if (Module32First(hSnapshot, &me32)) //暴雪也这样:)
{
do
{
if(PID_explorer==me32.th32ProcessID)
{
GetWindowsDirectory(lpszSystemInfo, MAX_PATH+1);
strcat(lpszSystemInfo,"\\");
strcat(lpszSystemInfo,"EXPLORER.EXE");
CharUpperBuff(me32.szExePath, strlen(me32.szExePath));
if(strncmp(me32.szExePath, lpszSystemInfo, strlen(lpszSystemInfo)))
{
GetFileNameFromPath(me32.szExePath);
if(strcmp(me32.szExePath, "EXPLORER.EXE")==0)
{
hh_parnet = OpenProcess(PROCESS_ALL_ACCESS, TRUE, PID_explorer);
TerminateProcess(hh_parnet, 0);
}
}
}
}while (Module32Next(hSnapshot, &me32)); //真纳闷了..同一个人.怎么两种写法不一样??我喜欢上面的:)
}
}
} 最后.检测就做个小结.但是.光这样是远远不够的..看看一些游戏.你用OD或者CE是看不到它的进程的.(CE因为都封装了API所以也许可以看到:()无论如何.HideProcess是必要的.这种例子网上很多.google一下就好了.
另外.只有这样也远远不够.想要真正做到反外挂.还需要在ring0保护游戏进程.使任何东西都难注入我们的进程.上面说了.CE可以看到我们隐藏的进程.但是如果我们对进程加了保护.它还是打不开的.
象NP之类的这些反外挂系统.非常暴力.占着自己的地盘不说.还要在别人的地盘插上一腿.你的程序被它注入了.你会觉得相当的不爽的.尤其是外挂..个人觉得一些反外挂都快赶上病毒了.而且计算机有一山不容二虎的意思.你打开一个Xtrap的保护游戏.再开个其他保护的..你可以试下会是什么结果..
其实保护程序的方法还有很多.论坛牛很多.这篇文章纯属给新手的总结性文章.:)
by Jessica(手链魔咒)
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)