admin管理员组文章数量:1582038
前言
QQ华夏作为一款古董级的游戏,所使用的保护手段放到今天已经是非常脆弱了,朋友初学逆向,作为初级选手,这款游戏在合适不过,本着以学习为目的,就让我来带他打赢这第一仗。
对抗
1.恢复 DbgUiRemoteBreakin HOOK
OD 附加游戏闪退,使用 PCHunter 扫描进程钩子发现游戏对此函数做了 HOOK,右键恢复此函数钩子,约莫几秒游戏闪退,怀疑游戏对此函数有CRC校验。
2.干掉 DbgUiRemoteBreakin CRC
使用 Windbg 对此函数下一字节访问断点,等待游戏校验此函数即可得到检测函数。
断下后单步运行至函数 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,所以需要处理。
__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.对抗成功
总结
1.恢复 DbgUiRemoteBreakin 钩子,使游戏能被调试器正常暂停。
2.干掉 DbgUiRemoteBreakin CRC,阻止游戏判断此函数是否被还原导致的闪退。
3.HOOK IsDebuggerPresent、CheckRemoteDebuggerPresent、NtQueryInformationProcess 阻止游戏查询是否正在调试。
4.HOOK NtSetInformationThread 阻止游戏隐藏主线程导致调试器断点触发奔溃。
5.HOOK EnumWindows、EnumProcesses 阻止游戏扫描黑名单工具,导致游戏和工具一起闪退。
资源
源代码: https://download.csdn/download/Cussrro/12621675
版权声明:本文标题:QQ华夏 - 反调试对抗 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:https://www.elefans.com/dianzi/1727891758a1136396.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论