标 题:
【原创】TX游戏多开分析
作 者: 毁灭
时 间: 2013-04-08,15:06:23
链 接: http://bbs.pediy.com/showthread.php?t=167848
作者:Tiany
QQ:304400230
花了10多天 研究过XX的多开 研究分析出一些东西 可是也被卡住了 遇到瓶颈 搞的郁闷了 把我分析到的东西和大家分享一下吧
大家都知道一般多开检测 都是一些互斥体 信号量 这些内核对象 当然TX也不例外 他也会对这些对象有检测 当然还有其他的检测 但是关键的代码都被VM了 所以想逆的难度就相当的大(对我而言) 所以我们必须要想些其他的方法
一般而言这些内核对象为了要个多个进程访问 所以都有Name 因为我们不好断定程序使用了1个或几个内核对象 来对比判断 所以这里我用的方法是 Inline Hook ZwCreateMutex ZwCreateEvent
ZwCreateSection ZwCreateSemaphore .... 这些函数 检测一下 如果Name 不为空的话 我们都让他的名字改变一下 达到每个进程创建的内核对象都变形
这里为什么要HOOK Zw开头的函数 我解释一下 因为CreateMutexA 调用 CreateMutexW 然后调用 ntdll.dll-ZwCreateMutex 函数 继续就到内核 nt!ZwCreateMutexA
(ntdll.dll-ZwCreateMutex 和 ntdll.dll-NtCreateMutex) 的导出地址是一样的
回调函数:
其他的函数都一样处理
我们看看效果
搞完这里只是基本工作 我们运行发现还是运行不要了 说明了还有其他地方检测 经过我细心的观察 发现一个内核对象很可疑
这个Event 并未被处理 他的Name 也不固定 反正重启过也都会变 可是他为什么没有进到我们的回调函数里呢? 这就是TX猥琐之处 他重新加载Ntdll.dll 然后执行创建对象
这个时候我们只能到更深层的 ntdll.KiFastSystemCall 地方去看看他是在那里调用的
(Zw函数都会 调用 ntdll.KiFastSystemCall 来跳转到内核 )
定位到这里 反复跟踪后 可以了解 他首先 打开 Ntdll.dll 然后读取数据到内存 然后开辟内存空间 来执行 创建内核对象的代码
这里我用的方法就是 Inline Hook CreateFile 函数 判断 如果打开的是 Ntdll.dll 就让他打开我实现处理好HOOK 的 Ntdll.dllTen 文件 让他继续跳到我们的回调函数去 创建内核对象
上代码
CreateFile 回调函数
经过以上的处理后 我们可以看到 内核对象很多都已经让我们 更名换姓了
当然如果不放心 也可以在ZwOpen***** 函数也进行HOOK 让他如果用打开的时候也访问到是我们创建的内核对象 处理的方法一样 可惜的是程序肯定还有其他的检测 我也尝试处理了 CreateToolhelp32Snapshot Process32First Process32Next 进程遍历的函数 也没能绕过 想不出解决的办法 故把分析的东西都公布 如果大家想一起研究或发现什么新的东西 大家可以论坛PM我 一起研究*转载请注明来自看雪论坛@PEdiy.com
作 者: 毁灭
时 间: 2013-04-08,15:06:23
链 接: http://bbs.pediy.com/showthread.php?t=167848
作者:Tiany
QQ:304400230
花了10多天 研究过XX的多开 研究分析出一些东西 可是也被卡住了 遇到瓶颈 搞的郁闷了 把我分析到的东西和大家分享一下吧
大家都知道一般多开检测 都是一些互斥体 信号量 这些内核对象 当然TX也不例外 他也会对这些对象有检测 当然还有其他的检测 但是关键的代码都被VM了 所以想逆的难度就相当的大(对我而言) 所以我们必须要想些其他的方法
一般而言这些内核对象为了要个多个进程访问 所以都有Name 因为我们不好断定程序使用了1个或几个内核对象 来对比判断 所以这里我用的方法是 Inline Hook ZwCreateMutex ZwCreateEvent
ZwCreateSection ZwCreateSemaphore .... 这些函数 检测一下 如果Name 不为空的话 我们都让他的名字改变一下 达到每个进程创建的内核对象都变形
这里为什么要HOOK Zw开头的函数 我解释一下 因为CreateMutexA 调用 CreateMutexW 然后调用 ntdll.dll-ZwCreateMutex 函数 继续就到内核 nt!ZwCreateMutexA
(ntdll.dll-ZwCreateMutex 和 ntdll.dll-NtCreateMutex) 的导出地址是一样的
Code:
HMODULE hNTDLL = LoadLibrary("ntdll.dll"); if (!hNTDLL) { OutputDebugStringA("[TEN] [DLL] 加载 ntdll.dll 失败\n"); return TRUE; } //NtCreateEvent InlineHook((__pfnNtCreateEvent)(LPVOID)GetProcAddress(hNTDLL,"NtCreateEvent"), OnNtCreateEvent, (void **)&pfnNtCreateEvent);
Code:
NTSTATUS NTAPI OnNtCreateEvent( OUT PHANDLE EventHandle, IN ACCESS_MASK DesiredAccess, IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL, IN EVENT_TYPE EventType, IN BOOLEAN InitialState) { NTSTATUS nRet; if (ObjectAttributes && ObjectAttributes->ObjectName && ObjectAttributes->ObjectName->Buffer) { WCHAR lpTemp[MAX_PATH]; ZeroMemory(lpTemp,MAX_PATH); wsprintfW(lpTemp,L"[Ten] [DLL] %lS%08X",ObjectAttributes->ObjectName->Buffer,GetCurrentProcessId()); OutputDebugStringW(ObjectAttributes->ObjectName->Buffer); pfnRtlInitUnicodeString(ObjectAttributes->ObjectName,lpTemp); } nRet = pfnNtCreateEvent( EventHandle, DesiredAccess, ObjectAttributes, EventType, InitialState ); return nRet; }
其他的函数都一样处理
我们看看效果
搞完这里只是基本工作 我们运行发现还是运行不要了 说明了还有其他地方检测 经过我细心的观察 发现一个内核对象很可疑
这个Event 并未被处理 他的Name 也不固定 反正重启过也都会变 可是他为什么没有进到我们的回调函数里呢? 这就是TX猥琐之处 他重新加载Ntdll.dll 然后执行创建对象
这个时候我们只能到更深层的 ntdll.KiFastSystemCall 地方去看看他是在那里调用的
(Zw函数都会 调用 ntdll.KiFastSystemCall 来跳转到内核 )
定位到这里 反复跟踪后 可以了解 他首先 打开 Ntdll.dll 然后读取数据到内存 然后开辟内存空间 来执行 创建内核对象的代码
这里我用的方法就是 Inline Hook CreateFile 函数 判断 如果打开的是 Ntdll.dll 就让他打开我实现处理好HOOK 的 Ntdll.dllTen 文件 让他继续跳到我们的回调函数去 创建内核对象
上代码
Code:
//新建Ntdll.dll BOOL CreateNtdll(LPBYTE NewCode,DWORD len) { PBYTE szBuffer = NULL; BYTE szSearCode[] = {0xB8,0x23,0x00,0x00,0x00,0xBA,0x00,0x03,0xFE,0x7F,0xFF,0x12,0xC2,0x14,0x00}; DWORD dwSize,lpSize; HANDLE hFile = CreateFileA("C:\\WINDOWS\\system32\\ntdll.dll", GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,NULL); if (hFile == INVALID_HANDLE_VALUE) { return FALSE; } dwSize = GetFileSize(hFile,NULL); szBuffer = new BYTE[dwSize]; ReadFile(hFile,szBuffer,dwSize,&lpSize,NULL); CloseHandle(hFile); //特征码搜索 LPBYTE pCode = SearchFeature(szBuffer,dwSize,szSearCode,sizeof(szSearCode)); if (pCode == NULL) { delete szBuffer; return FALSE; } //替换操作 memcpy(pCode,NewCode,len); hFile = CreateFileA("C:\\WINDOWS\\system32\\ntdll.dllTen",GENERIC_WRITE,FILE_SHARE_WRITE, NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL); if (hFile == INVALID_HANDLE_VALUE) { delete szBuffer; return FALSE; } WriteFile(hFile,szBuffer,dwSize,&lpSize,NULL); CloseHandle(hFile); delete szBuffer; return TRUE; }
CreateFile 回调函数
Code:
NTSTATUS NTAPI OnNtCreateFile( OUT PHANDLE FileHandle, IN ACCESS_MASK DesiredAccess, IN POBJECT_ATTRIBUTES ObjectAttributes, OUT PIO_STATUS_BLOCK IoStatusBlock, IN PLARGE_INTEGER AllocationSize OPTIONAL, IN ULONG FileAttributes, IN ULONG ShareAccess, IN ULONG CreateDisposition, IN ULONG CreateOptions, IN PVOID EaBuffer OPTIONAL, IN ULONG EaLength) { NTSTATUS nRet; if (ObjectAttributes && ObjectAttributes->ObjectName && ObjectAttributes->ObjectName->Buffer) { if (wcsstr(ObjectAttributes->ObjectName->Buffer,L"ntdll.dll")) { WCHAR lpTemp[MAX_PATH]; ZeroMemory(lpTemp,MAX_PATH); wsprintfW(lpTemp,L"%lSTen",ObjectAttributes->ObjectName->Buffer); pfnRtlInitUnicodeString(ObjectAttributes->ObjectName,lpTemp); OutputDebugStringW(ObjectAttributes->ObjectName->Buffer); } } nRet = pfnNtCreateFile( FileHandle, DesiredAccess, ObjectAttributes, IoStatusBlock, AllocationSize, FileAttributes, ShareAccess, CreateDisposition, CreateOptions, EaBuffer, EaLength ); return nRet; }
经过以上的处理后 我们可以看到 内核对象很多都已经让我们 更名换姓了
当然如果不放心 也可以在ZwOpen***** 函数也进行HOOK 让他如果用打开的时候也访问到是我们创建的内核对象 处理的方法一样 可惜的是程序肯定还有其他的检测 我也尝试处理了 CreateToolhelp32Snapshot Process32First Process32Next 进程遍历的函数 也没能绕过 想不出解决的办法 故把分析的东西都公布 如果大家想一起研究或发现什么新的东西 大家可以论坛PM我 一起研究*转载请注明来自看雪论坛@PEdiy.com