铜墙铁壁是怎样毁于全局变量的
转VMP For 拆解起凡游戏全图1848铜墙铁壁是怎样毁于全局变量的
前言:
1,起凡游戏平台是上海起凡数字技术有限公司自主研发的国内首款原创电竞游戏平台,自2007年7月正式推出以来,迅速填补了中国国内电竞游戏原创稀缺的空 白。起凡游戏平台旗下主推“三国争霸”系列游戏,将时尚电竞元素与古典三国战场相融合,独创三国电竞风潮。在数个月的测试期间,起凡游戏平台已经拥有上百万注册用户,此处使用的游戏主程序是1848版本。
2,7FGameShowMap是一个起凡游戏平台的外挂,需要授权后才能使用。 //本地验证方式
目标:破解外挂的授权限制。
第一步,让我们用OD来分析。
PEID观察下加壳情况,如图:
加了VMP,用强壮的OD带上HIDEOD附加上去可以跑,也可以下断点,但是一但断点命中,程序就挂了。我们马上可以想到程序用了SetInformationThread将线程的异常信息屏蔽了。
NtSetInformationThread,Undocumented Functions
NATIVE NTAPI
NtSetInformationThread(
IN HANDLE ThreadHandle,
IN THREAD_INFORMATION_CLASS ThreadInformationClass,
IN PVOID ThreadInformation,
IN ULONG ThreadInformationLength
);
我们再来,这次不要附加上去调了,因为如果是附加的话,程序已经调用了SetInformationThread,我们没办法了,所以我们直接用OD载入7FGameShowMap.exe F9跑起来,但是又来了另外一个错误,VMProtect在启动时调用了强大的调试器检测技术,发现了我们的OD:
看样子还是用附加好了,跟VMP硬来可不好办。那有什么办法可以去掉程序对SetInformationThread的调用呢?有以下两种方法:
1. 附加上去后不要下任何断点,因为一但有了异常,程序就会挂,而OD得不到任何处理机会。我们用WINDBG修改线程的HideFromDebugger标志,不过这样操作难度很高,而且如果程序一直在调用SetInformationThread设置些标志的话,就没任何办法了。
2. HOOK 程序对SetInformationThread的调用,根据前面的试验我们可以发现,程序对内存的校验不强,我们下断点他都没有发现。所以只是HOOK的话应该也没有问题。祭出我们强大的LPK,挂钩NtSetInformationThread函数。
3. 代码
复制代码
[*]BYTE g_ret2TrueSetInfo = {0xB8, 0xE5, 0x00, 0x00, 0x00, 0xE9, 0x00, 0x00, 0x00, 0x00};
[*]
[*]DWORD
[*]WINAPI
[*]MyNtSetInformationThread(
[*] IN HANDLE ThreadHandle,
[*] IN DWORD ThreadInformationClass,
[*] IN PVOID ThreadInformation,
[*] IN ULONG ThreadInformationLength
[*] )
[*]{
[*] DWORD dwRet;
[*]
[*] if (ThreadInformationClass == 17)
[*] {
[*] //HideFromDebugger
[*] return 1;
[*] }
[*] __asm
[*] {
[*] PUSH ThreadInformationLength
[*] PUSH ThreadInformation
[*] PUSH ThreadInformationClass
[*] PUSH ThreadHandle
[*] LEA EAX,g_ret2TrueSetInfo
[*] CALL EAX
[*] MOV dwRet, EAX
[*] }
[*] return dwRet;
[*]}
[*]
如果是调用NtSetInformationThread来设置HideFromDebugger的话,我们直接拦掉。放到7FGameShowMap.exe下面,再来调试,OK,可以下断点了。
PS,这期间还有一个小问题,就是我发现用OD附加上7FGameShowMap.exe之后,他会不停地创建游戏进程,我弄半天没弄明白是怎么回事,后来发现是HideOD挂钩了他的Process32NextW函数,造成7FGameShowMap不能发现游戏进程,以为游戏退出了,才不停地创建游戏进程,调出HideOD配置窗口,去掉对Process32Next的挂钩,一切正常了。
第二步,分析授权认证:
这里面的特征码也就是序列号,不同的称呼而已,一般的特征码是通过硬盘序列号,磁盘卷区号或者网卡序列号变换生成的,先看看是不是卷区号。OD中bp GetVolumeInformationA,当我点击主窗口的授权认证按钮时,OD断下来了:
只断了下次,验证了我们的猜想。该程序使用C盘卷区号来生成序列号。我们不管他的特征码是怎么生成的,直接来弄他的特征码和授权码的比对。我们按以下方式找到VB的消息处理函数。
1. 暂停下程序
2. 查看窗口的句柄
“点我进行认证”的句柄是0xd0924
3.在CallWindowProcA入口下条件断点:== 0xd0924 && ==0x202;
意思是:当消息是WM_LBUTTONUP而且目标窗口是“点我进行认证”这个按钮时,就断下来。
4. F9跑起来,点击按钮,断下来了:
5.Alt + M切换出MemoryMap,找到7FGameShowMap模块,在.text区下访问断点,一般默认代码是话.text段里面的。下好断点后再F9跑起来。
6. 程序断在这里:
dword ptr 的值即是消息处理函数的地址:0x402B50
OK,我们找到了消息处理函数,接下来分析下他是怎么处理的:
(关于VC VB等程序怎样找消息处理函数,请参照:http://bbs.pediy.com/showthread.php?s=&threadid=20078)
第三步,分析特征码和授权码的比对算法
来到第一个CALL
不懂是啥意思,调用的是VB的库,PASS
来到第二个CALL
还是VB库里的函数,不懂,PASS
都是一堆乱七八糟的跳转和CALL,弄不清楚在做啥。此路不通,再想办法。
这个外挂必须要授权过后才能使用全图功能,否则点了没反应,我们从这里想办法。
按照第二步的方法找到“打开全图”按钮的处理函数:0x40338C
第4步,分析全图按钮处理函数的逻辑,重点注意值的比较和跳转
第一个比较和跳转
我们NOP掉他,试试看,弹出一个东东:
破解一个程序就要从程序员的角度去想问题,比如,我们在写这个程序的时候,一般先检测是不是注册成功了,然后再看游戏平台是不是启动了,然后再去PATCH游戏的内存。 // 要学会编程!
刚才我NOP掉的应该是第一个判断。我们恢复NOP掉的代码再来一遍看看。结果啥都没反应。应该是这个跳转起了作用。很明显这个比较
cmp word ptr , 0FFFF
是对一个全局变量的比较,有可能是判断是否注册的一个标志我们将他修改成0xFFFF。
再点击授权认证按钮可以看到
OK,证实了我们的猜想,一个强大的VB+VMP保护的程序就因为一个未加保护的全局变量挂掉了。。。
但是!新的问题又来了,我们成功地开启了游戏外挂,可是过了一段时间游戏平台主动退出了。这个我想应该不是外挂作者故意所为,应该是他无法解决的一个技术BUG。
外挂采用了内存补丁的方式修改了游戏内存的一些代码,修改了游戏本来的逻辑,导致我们可以使用本来不可以使用的功能,这是现在外挂的主要方法。
但是游戏平台也有应对措施,他能够检测到恶意程序所做的修改,然后进行保护性关闭。
破解代码 For MFC:
复制代码
[*]unsigned long GetProcessIdFromName(char *pn)
[*]{
[*] BOOL bProcess;
[*] HANDLE hnd;
[*] PROCESSENTRY32 pe;
[*]
[*] hnd=CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);
[*]
[*] pe.dwSize=sizeof(pe);
[*] bProcess=Process32First(hnd,&pe);
[*] while(bProcess)
[*] {
[*] if(strcmp(pn,pe.szExeFile)==0)
[*] return pe.th32ProcessID;
[*] bProcess=Process32Next(hnd,&pe);
[*] }
[*] CloseHandle(hnd);
[*] return 0;
[*]}
[*]
[*]void CMy7FGamePathDlg::OnButton1()
[*]{
[*] // TODO: Add your control notification handler code here
[*] DWORD dwPId=GetProcessIdFromName("7FGameShowMap.exe");
[*] if (dwPId == 0)
[*] {
[*] MessageBox("先启动原版外挂!");
[*] return;
[*] }
[*] //0040F955cmp word ptr , 0FFFF
[*] BYTE JumpBytes = {0xE9, 0xC4, 0x95, 0x00, 0x00, 0x90, 0x90, 0x90};
[*] BYTE WriteBytes = {0x66, 0xBB, 0xFF, 0xFF, 0x66, 0x89, 0x1D, 0x4C, 0x90, 0x41,
[*] 0x00, 0x33, 0xDB, 0xE9, 0x2D, 0x6A, 0xFF, 0xFF};
[*] DWORD dwBytes, tmp;
[*] HANDLE hProcess=::OpenProcess(PROCESS_ALL_ACCESS,FALSE,dwPId);
[*] if (!WriteProcessMemory(hProcess, (PVOID)0x0040F955, JumpBytes, 8, &dwBytes))
[*] {
[*] OutputDebugString("patch failed");
[*] return;
[*] }
[*] if (!WriteProcessMemory(hProcess, (PVOID)0x00418F1E, WriteBytes, 18, &dwBytes))
[*] {
[*] MessageBox("Patch failed!");
[*] }
[*] else
[*] {
[*] MessageBox("补丁成功!");
[*] }
[*]}
[*]
[*]
全局写起来方便,破解起来也方便 介绍的很详细,感谢分享
页:
[1]