欢迎来到 嗅灵易学

零基础也能上手的脚本技术课,一对一答疑带你入门

[原创]TX游戏多开分析

[原创]TX游戏多开分析



花了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) 的导出地址是一样的 

  

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);


回调函数:

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 文件 让他继续跳到我们的回调函数去 创建内核对象
上代码

//新建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 回调函数

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我 一起研究

上传的附件 1.jpg
2.jpg
3.jpg
4.jpg

注意:上传附件及图片大小不得大于30M。

⚠️ 版权声明:
本博客所有内容(含教程、源码、工具)仅供个人技术学习与研究交流使用,严禁商用、倒卖、二次分发及非法用途。
未经作者书面授权,任何组织或个人不得转载、复制或用于其他平台,违者将追究相关责任。

0 0 0 举报
复制成功