selph
selph
Published on 2024-07-15 / 439 Visits
3
1

微信逆向:防多开检测原理和绕过

#re

本篇主要从日志和反编译代码的角度,分析一下定位到防多开功能,以及原理和绕过

绕过代码不是本文重点,选自网上的一种有趣的方法进行介绍(参考资料[0])

日志分析定位

打开一个wechat,然后用调试器打开另一个,在载入wechatwin.dll的时候注入模块打印日志,然后运行起来得到如下内容:

2	6.090196	63260	WeChat.exe	(2024-7-9:10:15:32:389 42164)-i/AppletDesktopLaunchMgr:reg path = D:\Software\others\WeChat
3	6.090407	63260	WeChat.exe	(2024-7-9:10:15:32:389 42164)-i/AppletDesktopLaunchMgr:same regPath
4	6.090681	63260	WeChat.exe	(2024-7-9:10:15:32:390 42164)-i/FinderliveIpcMgr:process fake url: <NULL>
5	6.090837	63260	WeChat.exe	(2024-7-9:10:15:32:390 42164)-w/FinderliveIpcMgr:unmatch fake url
6	6.090950	63260	WeChat.exe	(2024-7-9:10:15:32:390 42164)-i/WeChat:GetLastError 183
7	6.091207	63260	WeChat.exe	(2024-7-9:10:15:32:390 42164)-i/WeChat:mainwnd is null? 0
8	6.106538	63260	WeChat.exe	(2024-7-9:10:15:32:405 42164)-e/MicroMsgWin:Already running
9	6.139109	63260	WeChat.exe	(2024-7-9:10:15:32:437 42164)-i/GlobalConfigStorage:C:\Users\Admin\Documents default path is useful
10	6.143698	63260	WeChat.exe	(2024-7-9:10:15:32:442 42164)-i/FileUtils:defautSaveDir is user document dir: C:\Users\Admin\Documents
11	6.144656	63260	WeChat.exe	(2024-7-9:10:15:32:443 42164)-i/ConfigInfoStorage:ConfigFilePath : C:\Users\Admin\Documents\WeChat Files\All Users\config\config.data, filesize: 530
12	6.145636	63260	WeChat.exe	(2024-7-9:10:15:32:444 42164)-i/ConfigInfoStorage:read configfile size: 530
13	6.145935	63260	WeChat.exe	(2024-7-9:10:15:32:444 42164)-i/WebViewMgr:WebViewMgr()
14	6.146118	63260	WeChat.exe	(2024-7-9:10:15:32:445 42164)-i/WebViewMgr:unInit()
15	6.341011	63260	WeChat.exe	<process started at 10:14:25.263 has terminated with exit code -1 (0xffffffff)>

有一条有趣的日志:

8	6.106538	63260	WeChat.exe	(2024-7-9:10:15:32:405 42164)-e/MicroMsgWin:Already running

代码原理分析

搜索字符串:

.rdata:00000001849622D0	0000001E	C	pingCheck is already running.
.rdata:0000000184962338	0000001C	C	dnsCheck is already running
.rdata:00000001849623A0	0000001D	C	tcpCheck is already running.
.rdata:0000000184962408	0000001E	C	httpCheck is already running.
.rdata:0000000184962478	00000024	C	tracerouteCheck is already running.
.rdata:00000001849624F0	00000020	C	reqbufCheck is already running.
.rdata:0000000184966DF8	0000002E	C	DnsCheck is already running, skip this action
.rdata:0000000184966F60	0000002F	C	HttpCheck is already running, skip this action
.rdata:00000001849670A0	0000002F	C	PingCheck is already running, skip this action
.rdata:0000000184967280	00000034	C	ReqBufferCheck is already running, skip this action
.rdata:0000000184967360	0000002E	C	TcpCheck is already running, skip this action
.rdata:0000000184967458	00000035	C	TracerouteCheck is already running, skip this action
.rdata:0000000184F05D30	00000010	C	already running
.rdata:00000001850A7AD0	00000010	C	Already running

最后一条就是我们要定位的地方,交叉引用到:

    v64 = v113[0];
    if ( (unsigned __int8)sub_1830F0E30() )
    {
      if ( !LOBYTE(v113[0]) )
      {
LABEL_103:
        pExceptionObject = *(_OWORD *)xmmword_184DED0B0;
        v117 = *(_OWORD *)xmmword_184DED0B0;
        v116 = *(_OWORD *)xmmword_184DED0B0;
        v115 = *(_OWORD *)xmmword_184DED0B0;
        v114 = *(_OWORD *)xmmword_184DED0B0;
        v118 = *(_OWORD *)xmmword_184DED0B0;
        log_message(
          4,
          (__int64)"D:\\Tools\\agent\\workspace\\MicroMsgWindowsV3911\\MicroMsgWin\\MicroMsgWin.cpp",
          695,
          (__int64)"StartWechat",
          "MicroMsgWin",
          "Already running",
          &v118,
          &v114,
          &v115,
          &v116,
          &v117,
          &pExceptionObject);
LABEL_162:
        sub_1830F9150(v113);
        ShutdownMMMojo();
        unknown_libname_1695(lpLibFileName);
        goto LABEL_165;
      }
LABEL_108:
      memset(ClassName, 0, sizeof(ClassName));
      if ( GetClassNameW(hWnd, ClassName, 256) )
      {
        v65 = 0i64;
        while ( ClassName[v65] == aWechatloginwnd[v65] && ClassName[v65 + 1] == aWechatloginwnd[v65 + 1] )
        {
          v65 += 2i64;
          if ( v65 == 20 )
            goto LABEL_113;
        }
      }
      goto LABEL_103;
    }

这应该就是StartWechat函数,这里有2个if条件

第一个是要满足sub_1830F0E30返回true,第二个是满足v113=0

首先看这个函数,第一部分:

  v1 = v0;
  Block[0] = v0;
  if ( !v0 )
  {
    sub_181B49DE0(&pExceptionObject, "out of memory");
    CxxThrowException(&pExceptionObject, (_ThrowInfo *)&_TI2_AVruntime_error_std__);
  }
  *v0 = 0;
  HIDWORD(Block[1]) = 40;
  wcsncpy(v0, L"_WeChat_App_Instance_Identity_Mutex_Name", 0x28ui64);
  v1[40] = 0;
  LODWORD(Block[1]) = 40;
  GetCurrentProcessId();
  v2 = &DirectoryName;
  if ( *v1 )
    v2 = v1;
  MutexW = CreateMutexW(0i64, 0, v2);
  v4 = 0;
  if ( MutexW )
  {
    LastError = GetLastError();
    BYTE8(v29) = 0;
    v30[0] = LastError;
    *(_OWORD *)&v30[1] = *(_OWORD *)xmmword_184DED0B0;
    v31 = *(_OWORD *)xmmword_184DED0B0;
    v32 = *(_OWORD *)xmmword_184DED0B0;
    v33 = *(_OWORD *)xmmword_184DED0B0;
    *(_OWORD *)lParam = *(_OWORD *)xmmword_184DED0B0;
    log_message(
      2,
      (__int64)"D:\\Tools\\agent\\workspace\\MicroMsgWindowsV3911\\MicroMsgWin\\10_platform\\WeChat.cpp",
      444,
      (__int64)"WeChat::preInstanceAlreadyRun",
      "WeChat",
      "GetLastError %d",
      (__int128 *)((char *)&v29 + 8),
      (__int128 *)lParam,
      &v33,
      &v32,
      &v31,
      (__int128 *)&v30[1]);
    if ( LastError == 183 )
    {
      v4 = 1;
      CloseHandle(MutexW);
    }
    else
    {
      hObject = MutexW;
    }
  }
  free(v1);

这里用了互斥体MutexW,通过CreateMutexW创建的,这个函数:用于创建或打开命名的或未命名的互斥体对象。

HANDLE CreateMutexW(
  [in, optional] LPSECURITY_ATTRIBUTES lpMutexAttributes,
  [in]           BOOL                  bInitialOwner,
  [in, optional] LPCWSTR               lpName
);

第三个参数标识这是个命名互斥体,返回值:

  • 如果函数成功,则返回值是新创建的互斥对象的句柄。
  • 如果函数失败,则返回值为 NULL。 要获得更多的错误信息,请调用 GetLastError。
  • 如果互斥体是命名互斥体,并且对象在调用此函数之前就存在,则返回值是现有对象的句柄, 而 GetLastError 函数返回 ERROR_ALREADY_EXISTS。

这里明显是满足了这里的第三条,GetLastError返回183

这个函数剩下的部分需要满足v4=1,满足这个条件需要GetLastError返回183

关于v113,这里能打印出日志,说明条件应该是默认就满足了的,不管也行

绕过防多开检测

知道原理后,这里处理互斥体检测方法就很多了,例如:

  • hook CreateMutexW函数,检查第三个参数,如果是硬编码的_WeChat_App_Instance_Identity_Mutex_Name,就修改一下这个命名

    • 需要在第一次创建互斥体之前劫持
  • 或者注入模块启动的时候做一件事,关闭这个命名互斥体

    • 获取到句柄,然后关闭句柄
  • 或者直接patch这里的跳转逻辑,让LastError == 183失效

  • 给这个互斥体设置权限,拒绝访问

最后这个思路很有趣,代码来自参考资料[0],以这个为例,这里下面介绍这个代码:

#include <iostream>
#include <aclapi.h>
#include <shlwapi.h>
#include <windows.h>
#pragma comment(lib,"Shlwapi.lib")


void enableMultiWeChat()
{
    HANDLE hMutex = CreateMutexW(NULL, FALSE, L"_WeChat_App_Instance_Identity_Mutex_Name");
    SID_IDENTIFIER_AUTHORITY SIDAuthWorld = SECURITY_WORLD_SID_AUTHORITY;
    PSID pEveryoneSID = NULL;
    char szBuffer[4096] = { 0 };
    PACL pAcl = (PACL)szBuffer;

    AllocateAndInitializeSid(&SIDAuthWorld, 1, SECURITY_WORLD_RID, 0, 0, 0, 0, 0, 0, 0, &pEveryoneSID);
    InitializeAcl(pAcl, sizeof(szBuffer), ACL_REVISION);
    AddAccessDeniedAce(pAcl, ACL_REVISION, MUTEX_ALL_ACCESS, pEveryoneSID);
    SetSecurityInfo(hMutex, SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION, NULL, NULL, pAcl, NULL);
}

int main()
{
    enableMultiWeChat();
    //WinExec("D:\\ProgramFiles\\Tencent\\WeChat\\WeChat.exe", SW_SHOW);
    return 0;
}

AllocateAndInitializeSid用于分配SID,SID是Windows系统中用于唯一标识用户、组或计算机的安全标识符。代码中创建了一个表示“Everyone”的SID,用于在ACL中指定拒绝访问的对象。

紧接着的InitializeAcl和AddAccessDeniedAce,初始化并创建了访问控制项,ACL用于定义哪些用户或组能不能访问某个资源

SetSecurityInfo用于设置该句柄的权限,让任何人不能访问

所以当wecaht执行到CreateMutexW的时候,会因为没有权限返回NULL,从而不执行后面的代码以及返回0,从而绕过了多开检测

效果展示

image

参考资料


Comment