QQ华夏 - 反调试对抗

前言

      QQ华夏作为一款古董级的游戏,所使用的保护手段放到今天已经是非常脆弱了,朋友初学逆向,作为初级选手,这款游戏在合适不过,本着以学习为目的,就让我来带他打赢这第一仗。
 

对抗

      1.恢复 DbgUiRemoteBreakin HOOK
          OD 附加游戏闪退,使用 PCHunter 扫描进程钩子发现游戏对此函数做了 HOOK,右键恢复此函数钩子,约莫几秒游戏闪退,怀疑游戏对此函数有CRC校验。
QQ华夏_PassSafe

      2.干掉 DbgUiRemoteBreakin CRC

          使用 Windbg 对此函数下一字节访问断点,等待游戏校验此函数即可得到检测函数。
QQ华夏_PassSafe
          断下后单步运行至函数 ret 处,查看 eax 值,在此检测函数头部写入 eax 返回值,然后返回即可过掉此函数 CRC 校验。

/*
 *  创建的线程函数,等待游戏加载完检测模块,
 *  先干掉 CRC,然后等待游戏 HOOK 完 DbgUiRemoteBreakin,
 *  咋们在还原 HOOK
 */
DWORD WINAPI PassSafeThread(LPVOID lpThreadParameter)
{
	HMODULE Controller = NULL;	
	byte Code1[] = { 0xB8,0x00,0x00,0x00,0x00,0xC3 };
	byte Code2[] = { 0x6A,0x08,0x68,0xE8,0x07 };
	ULONG DbgUiRemoteBreakinAddr = 0;

	while (true)
	{
		Controller = GetModuleHandle("Controller.dll");
		if (Controller)
		{
			// Pass DbgUiRemoteBreakin CRC	
			if (!SetMemoryProtect((ULONG)Controller + 0x39300, 6, PAGE_EXECUTE_READWRITE))
			{
				MessageBox(NULL, "修改内存属性失败", "错误", MB_OK);
				return 0;
			}
			memcpy((void*)((ULONG)Controller + 0x39300), Code1, 6);

			// 恢复 DbgUiRemoteBreakin HOOK
			DbgUiRemoteBreakinAddr = GetModuleFunction("ntdll.dll", "DbgUiRemoteBreakin");
			if (DbgUiRemoteBreakinAddr)
			{
				if (*((byte*)DbgUiRemoteBreakinAddr) == 0xE9)
				{
					if (!SetMemoryProtect(DbgUiRemoteBreakinAddr, 5, PAGE_EXECUTE_READWRITE))
					{
						MessageBox(NULL, "修改内存属性失败", "错误", MB_OK);
						return 0;
					}
					memcpy((void*)DbgUiRemoteBreakinAddr, Code2, 5);

					break;
				}
			}
		}

		Sleep(100);
	}

	MessageBox(0, "PassSafe 成功", "提示", MB_OK);

	return 0;
}

 
      3.HOOK 查询调试状态的函数
          使用 Windbg 对 IsDebuggerPresent、CheckRemoteDebuggerPresent、NtQueryInformationProcess 函数下执行断点,发现游戏有调用这三个函数查询调试状态,所以需要处理。

__declspec(naked) BOOL PassSafe_IsDebuggerPresent()
{
	_asm
	{
		mov eax, 0
		ret
	}
}
BOOL WINAPI PassSafe_CheckRemoteDebuggerPresent(HANDLE hProcess, PBOOL pbDebuggerPresent)
{
	return FALSE;
}
__declspec(naked) PZwQueryInformationProcess PassSafe_NtQueryInformationProcess(
	_In_      HANDLE           ProcessHandle,
	_In_      PROCESSINFOCLASS ProcessInformationClass,
	_Out_     PVOID            ProcessInformation,
	_In_      ULONG            ProcessInformationLength,
	_Out_opt_ PULONG           ReturnLength)
{
	_asm
	{
		push ebp
		mov ebp, esp
		pushad
		pushfd
	}

	if (ProcessInformationClass == PROCESSINFOCLASS::ProcessDebugPort ||
		ProcessInformationClass == PROCESSINFOCLASS::ProcessDebugObjectHandle)
	{
		if (ProcessInformationLength)
		{
			ZeroMemory(ProcessInformation, ProcessInformationLength);
		}

		goto PASS_CHECK_DEBUG;
	}
	else if (ProcessInformationClass == PROCESSINFOCLASS::ProcessDebugFlags)
	{
		if (ProcessInformationLength)
		{
			memset(ProcessInformation, 0xFF, ProcessInformationLength);
		}

		goto PASS_CHECK_DEBUG;
	}

	_asm
	{
		popfd
		popad
		pop ebp
		mov eax, 0xEA
		mov edx, 0x7FFE0300
		call[edx]
		ret 0x14
	}

PASS_CHECK_DEBUG:

	if (ReturnLength)
	{
		*ReturnLength = 0;
	}

	_asm
	{
		popfd
		popad
		pop ebp
		mov eax,0
		ret 0x14;
	}
}

 
      4.解决游戏主线程触发软/硬断点崩溃
          到了这一步,OD 能附加上游戏了,但是下断点崩溃,使用 Windbg 查看 ETHREAD 结构 发现主线程 HideFromDebugger 值为1,该字段值可以通过 NtSetInformationThread 函数参数二传入 ThreadHideFromDebugger 设置为1,所以需要处理。
QQ华夏_PassSafe

__declspec(naked) PZwSetInformationThread PassSafe_NtSetInformationThread(
	__in HANDLE ThreadHandle,
	__in THREADINFOCLASS ThreadInformationClass, 
	__in_bcount(ThreadInformationLength) PVOID ThreadInformation,
	__in ULONG ThreadInformationLength)
{
	_asm
	{
		push ebp
		mov ebp, esp
		pushad
		pushfd
	}

	if (ThreadInformationClass == THREADINFOCLASS::ThreadHideFromDebugger)
	{
		if (ThreadInformationLength)
		{
			ZeroMemory(ThreadInformation, ThreadInformationLength);
		}

		_asm
		{
			popfd
			popad
			pop ebp
			mov eax, 0
			ret 0x10
		}
	}

	_asm
	{
		popfd
		popad
		pop ebp
		mov eax, 0x14F
		mov edx, 0x7FFE0300
		call [edx]
		ret 0x10
	}
}

 
      5.解决游戏扫描黑名单软件 结束游戏和软件
          OD 附加游戏挂一段时间,莫名其妙连同软件一起退出进程,猜测是扫描窗口或者扫描进程,在枚举窗口及进程相关函数下断之后,发现调用了 EnumWindows、EnumProcesses 进行扫描黑名单工具,所以需要处理。

// 检测进程 游戏会枚举进程是否存在非法调试器
	ULONG EnumProcessesAddr = GetModuleFunction("PSAPI.DLL", "EnumProcesses");
	if (!EnumProcessesAddr || !SetMemoryProtect(EnumProcessesAddr, 8, PAGE_EXECUTE_READWRITE))
	{
		MessageBox(NULL, "Pass EnumProcesses 失败", "错误", MB_OK);
		return;
	}
	else
	{
		byte code[] = { 0xB8,0x00,0x00,0x00,0x00,0xC2,0x0C,0x00 };
		memcpy((void*)EnumProcessesAddr, code, 8);
	}

	// 检测窗口 游戏会枚举窗口文字是否有非法文字
	ULONG EnumWindowsAddr = GetModuleFunction("user32.dll", "EnumWindows");
	if (!EnumWindowsAddr || !SetMemoryProtect(EnumWindowsAddr, 8, PAGE_EXECUTE_READWRITE))
	{
		MessageBox(NULL, "Pass EnumWindows 失败", "错误", MB_OK);
		return;
	}
	else
	{
		byte code[] = { 0xB8,0x00,0x00,0x00,0x00,0xC2,0x08,0x00 };
		memcpy((void*)EnumWindowsAddr, code, 8);
	}

 
      6.对抗成功
QQ华夏_PassSafe
QQ华夏_PassSafe

总结

      1.恢复 DbgUiRemoteBreakin 钩子,使游戏能被调试器正常暂停。
      2.干掉 DbgUiRemoteBreakin CRC,阻止游戏判断此函数是否被还原导致的闪退。
      3.HOOK IsDebuggerPresent、CheckRemoteDebuggerPresent、NtQueryInformationProcess 阻止游戏查询是否正在调试。
      4.HOOK NtSetInformationThread 阻止游戏隐藏主线程导致调试器断点触发奔溃。
      5.HOOK EnumWindows、EnumProcesses 阻止游戏扫描黑名单工具,导致游戏和工具一起闪退。
 

资源

      源代码: https://download.csdn.net/download/Cussrro/12621675

  • 11
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值