PC微信逆向:实现自动添加好友分享名片

二进制安全 2019-11-09

本文作者:鬼手56(信安之路病毒分析小组成员 & 信安之路 2019 年度优秀作者) 成员招募:信安之路病毒分析小组寻找志同道合的朋友

在我的成品 WeChatRobot 里面实现了四个自动系列的功能,自动添加好友分享名片,自动收款,自动同意好友请求,还有自动聊天。今天分享一下如何实现自动添加好友分享名片。其他的三个功能思路大致相同。

想要实现自动添加好友分享的名片的功能,首先需要找到加好友的 call,分析加好友需要用到哪些参数,然后在接收到好友名片推送的时候,取出这些参数,调用加好友的 call。这样就能实现自动添加好友分享的名片了

定位加好友 call 相关思路

首先思考一下一个加好友函数背后的编程逻辑,肯定是需要传入两个参数,第一个参数是需要添加好友的微信 ID,第二个参数是加好友时发送给对方的消息。当我们点击发送朋友验证时,就会调用这个加人的 call。

img

那么我们就可以从需要添加的好友的微信 ID 入手,先在当前窗口找到添加好友时用到的那个微信 ID,然后通过对微信 ID 下内存访问断点,点击发送朋友验证,触发断点,再通过栈回溯,从而找到加人的 call。

定位微信加好友 call

定位加好友 call 的微信 ID

img

首先用 CE 搜索已删除好友的微信 ID,将所有地址添加到下方地址栏

img

接着点击添加发送朋友验证,你会发现这是 CE 的结果有变化。这是因为当我们点击的时候,客户端会拿到我们当前点击的微信 ID,然后放到一个变量里面,将这个变量作为参数传入到函数中。所以我们点击之后搜索的结果一定会有这个变量,接下来我们要做的就是找到这个变量。

那么怎么找到这个变量呢?通过下方地址栏的点击之前的数据和现在搜索结果中的点击之后的数据进行对比,就能找到作为参数传入到加人 call 里面的微信 ID。

img

通过这个在线的文本对比的网站http://www.jq22.com/textDifference,放入点击前和点击之后的数据(A是点击前,B 是点击后),就能筛选出两组数据中不同的地方。我们需要找到点击后的数据中有的地址,点击前的数据中没有的地址。

img

img

我这里找到了两处点击后不同的地址,接着我们直接在 CE 中搜索这两个地址,看看有哪些地址保存有这两个地址

img

从搜索结果中可以看到第一个地址已经变成了其他字符串,而第二个地址已经被一个指针保存,所以可以确定这个地址就是我们要的传入加人 call 的微信 ID 的地址。哈哈,这一步需要有点耐心,我也是找了好几次才找到。

定位微信加好友 call

使用 OD 附加微信,在找到的微信 ID 地址下内存访问断点,接着点击确定,此时断点断下,删除内存访问断点

img

这里我们看到在堆栈中的第二个返回地址中传入了要添加的好友的微信 ID,那么这里就非常有可能是我们要找的加人的 call

img

验证加好友 call

我们在这里下一个断点,然后 F9 运行,让程序再次断在这个位置

img

此时我们修改这个微信 ID,如果此时被添加的微信没有收到加好友消息的话,那么就说明这个 call 就是我们需要的 call

img

F9 运行,此时显示由于对方隐私设置,你无法将其添加至通讯录,说明这个 call 就是我们要找的加好友的 call 了

微信加好友 call 的分析

接下来分析一下加好友的 call 的参数

ebx 指向的是微信 ID 的结构体,这个结构体是有五个成员的

中间的这一句汇编指令mov dword ptr ss:[ebp-0xE0],esp经过测试可以不需要写。

然后这一句将 0xFFFFFFFF 压入了堆栈,也就是 -1。但是我们在这个 call 所在的寄存器并没有找到加好友时发送的消息。这个结构体是必须要找到的,所以我们继续分析它上面一个 call

上面一个 call 传入的是消息内容,并没有拿到消息的结构体,所以我们继续再分析这个 call 上面的一个 call

img

单步步过这个函数,这里 push 了一个 6,这个参数是代表添加的渠道,群外加好友是 6,群内加好友的 E,名片推送是 0x11

img

另外,这个 call 写代码调用的时候会比较麻烦,需要同时调用 6 个 call,6 个 call 里面少了任何一个微信都会奔溃

添加名片 call 参数

微信添加好友名片的 call 和加好友用的是同一个 call,区别只有两点。我们点击添加一个好友分享的名片,让程序断下。

区别一

img

这个地方传入的是 0x11 而不再是 6

区别二

img

这个地方传入的是 V1 结构体 而不再是微信 ID 的结构体。而 V1 的结构体我们可以在收到名片推送消息的时候,从消息结构体中获取。

分析接收名片消息

加好友的 call 我们已经找到了,而且也已经知道需要传入哪些数据,那么下一步就是去接收名片消息的地方,去拿到这些数据。

直接来到接收消息的地方,至于怎么找到接收消息的 call,请看我上一篇文章

https://blog.csdn.net/qq_38474570/article/details/93339861

img

这里直接给出偏移 2.6.8.52 版本的接收消息的偏移是 0x315E98

img

直接来到接收消息的地方,在这里下断点,然后用小号分享一个名片,让程序断下

程序断下后,我们查看 [[esp]] 里面的内容,这里面保存了接收到的消息参数,往下拉

这里有消息发送者的微信 ID 和 xml 格式的消息内容

数据窗口跟随进去,里面有我们需要的 V1 数据,至于其他的数据我们并不关心。OK,分析到这里,自动添加好友分享的名片基本也就完成了。接下来贴代码

代码实现自动添加好友分享名片

第一步 取出 V1 数据 我这里用的是字符串查找和匹配的方法,你们可以 xml 解析库来解析数据

void AutoAddCardUser(wstring msg)
{
  //拿到V1
  int v1strat = msg.find(L"v1_");
  int v1end = msg.find(L"@stranger");
  wstring v1;
  v1 = msg.substr(v1strat, v1end - v1strat + 9);

  //调用添加名片好友函数
  AddCardUser((wchar_t*)v1.c_str(), (wchar_t*)L"快通过~快通过~ 吼吼!");
}

第二步 调用 call 添加好友

void AddCardUser(wchar_t* v1, wchar_t* msg)
{
  DWORD dwWeChatWinAddr = (DWORD)GetModuleHandle(L"WeChatWin.dll");
  DWORD dwParam1 = dwWeChatWinAddr + WxAddWxUserParam1;
  DWORD dwCall1 = dwWeChatWinAddr + WxAddWxUserCall1;
  DWORD dwCall2 = dwWeChatWinAddr + WxAddWxUserCall2;
  DWORD dwCall3 = dwWeChatWinAddr + WxAddWxUserCall3;
  DWORD dwCall4 = dwWeChatWinAddr + WxAddWxUserCall4;
  DWORD dwCall5 = dwWeChatWinAddr + WxAddWxUserCall5;

  struct TextStruct
  {
    wchar_t* pStr;
    int strLen;
    int strMaxLen;

  };

  TextStruct pV1 = { 0 };
  pV1.pStr = v1;
  pV1.strLen = wcslen(v1) + 1;
  pV1.strMaxLen = (wcslen(v1) + 1) * 2;


  char* asmV1 = (char*)&pV1.pStr;
  char buff3[0x100] = { 0 };
  char* buff = buff3;
  __asm
  {
    sub esp, 0x18;
    mov ecx, esp;
    mov dword ptr ss : [ebp - 0xDC], esp;
    push  dwParam1;
    call dwCall1;
    sub esp, 0x18;
    mov eax, buff;
    mov dword ptr ss : [ebp - 0xE4], esp;
    mov ecx, esp;
    push eax;
    call dwCall2;
    push 0x11;
    sub esp, 0x14;
    mov ecx, esp;
    mov dword ptr ss : [ebp - 0xE8], esp;
    push - 0x1;
    mov edi, msg;
    push edi;
    call dwCall3;
    push 0x2;
    sub esp, 0x14;
    mov ecx, esp;
    mov dword ptr ss : [ebp - 0xE0], esp;
    mov ebx, asmV1;
    push ebx;
    call dwCall4;
    mov ecx, eax;
    call dwCall5;
  }
}

拓展

实现自动聊天思路

首先在好友消息的时候保存一下好友的微信 ID,然后调用获取到好友的消息内容,并且将消息内容转发给图灵机器人的公众号

img

接着拿到图灵机器人回复的内容,将内容转发给好友

img

这样就实现了自动聊天

实现自动同意好友请求思路

我们在接收消息的地方下个断点,让好友发送请求时断下,并且查看 [[esp]] 里的消息内容

img

这里保存有两个重要的数据,一个是 V1,一个是 V2,我们只要在这个地方取出 V1 和 V2,然后调用同意好友请求的 call,就能实现自动同意好友请求

同意好友请求的 call,我这里直接给出偏移 微信 2.6.8.52 版本

#define WxAgreeUserRequestCall1 0x1865B0;   //同意好友请求
#define WxAgreeUserRequestCall2 0x4F4F0;    //同意好友请求
#define WxAgreeUserRequestCall3 0xCE4F0;    //同意好友请求
#define WxAgreeUserRequestCall4 0x16BD40;   //同意好友请求
#define WxAgreeUserRequestParam 0x126E050;    //同意好友请求

这里需要同时调用 4 个 call,接着附上代码

//取出V1和V2
void AutoAgreeUserRequest(wstring msg)
{
  int v1strat = msg.find(L"v1_");
  int v1end = msg.find(L"@stranger");
  wstring v1;
  v1 = msg.substr(v1strat, v1end - v1strat + 9);
  //找到v2
  int v2strat = msg.find(L"v2_");
  int v2end = msg.rfind(L"@stranger");
  wstring v2;
  v2 = msg.substr(v2strat, v2end - v2strat + 9);
  //调用同意好友请求的call
  AgreeUserRequest((wchar_t*)v1.c_str(), (wchar_t*)v2.c_str());
}
//调用同意好友请求call
void AgreeUserRequest(wchar_t* v1, wchar_t* v2)
{
  struct v1Info
  {
    int fill = 0;
    wchar_t* v1 = 0;
    int v1Len;
    int maxV1Len;
    char fill2[0x41C] = { 0 };
    DWORD v2 = { 0 };
  };

  struct v2Info
  {
    char fill[0x24C] = { 0 };
    DWORD fill3 = 0x25;
    char fill4[0x40] = { 0 };
    wchar_t* v2;
    int v2Len;
    int maxV2Len;
    char fill2[0x8] = { 0 };
  };

  DWORD base = (DWORD)LoadLibrary(L"WeChatWin.dll");
  DWORD callAdd1 = base + WxAgreeUserRequestCall1;
  DWORD callAdd2 = base + WxAgreeUserRequestCall2;
  DWORD callAdd3 = base + WxAgreeUserRequestCall3;
  DWORD callAdd4 = base + WxAgreeUserRequestCall4;
  DWORD params = base + 0x126E050;

  DWORD* asmP = (DWORD*)params;

  v1Info userInfoV1 = { 0 };
  v2Info userInfoV2 = { 0 };
  userInfoV1.v2 = (DWORD)&userInfoV2.fill;
  userInfoV1.v1 = v1;
  userInfoV1.v1Len = wcslen(v1);
  userInfoV1.maxV1Len = wcslen(v1) * 2;
  userInfoV2.v2 = v2;
  userInfoV2.v2Len = wcslen(v2);
  userInfoV2.maxV2Len = wcslen(v2) * 2;

  char* asmUser = (char*)&userInfoV1.fill;
  char buff[0x14] = { 0 };
  char buff2[0x48] = { 0 };
  char* asmBuff = buff2;

  __asm
  {
    mov ecx, asmUser;
    push 0x6;
    sub esp, 0x14;
    push esp;
    call callAdd1;
    mov ecx, asmUser;
    lea eax, buff;
    push eax;
    call callAdd2;
    mov esi, eax;
    sub esp, 0x8;
    mov ecx, asmP;
    call callAdd3;
    mov ecx, asmBuff;
    mov edx, ecx;
    push edx;
    push eax;
    push esi;
    call callAdd4;
  }

}

实现自动收款思路

同样在接收消息的地方下个断点,接收转账消息,并且查看 [[esp]] 里的消息内容

img

这里重要的数据只有一个,就是这个 transferid,拿到这个转账 ID 和微信 ID,然后调用收款的 call,就能实现自动收款了

这里提供收款 call 的偏移,微信 2.6.8.52 版本

#define WxCllectMoneyCall1 0x676B10   //收款
#define WxCllectMoneyCall2 0x676B90   //收款

接着附上代码

//取出转账ID
void AutoCllectMoney(wstring msg,wchar_t* wxid)
{
  // 找到<transferid>字符串的位置
  int pos1 = msg.find(L"<transferid>");
  //找到]]></transferid>字符串的位置
  int pos2 = msg.find(L"]]></transferid>");
  //取出多余的字符串长度
  wstring noneed = L"<transferid><![CDATA[";
  int noneedLen = noneed.length();
  //取出转账ID
  wstring transferid;
  transferid = msg.substr(pos1 + noneedLen, (pos2 - pos1) - noneedLen);

  //调用收款call 实现自动收款
  CllectMoney((wchar_t*)transferid.c_str(), wxid);
}
//调用收款call
void CllectMoney(wchar_t* transferid, wchar_t* wxid)
{
  struct CllectMoneyStruct
  {
    wchar_t* ptransferid;
    int transferidLen;
    int transferidMaxLen;
    char full[0x8] = { 0 };
    wchar_t* pwxid;
    int wxidLen;
    int wxidMaxLen;
    char full2[0x8] = { 0 };
  };

  CllectMoneyStruct cllect;
  cllect.ptransferid = transferid;
  cllect.transferidLen = wcslen(transferid) + 1;
  cllect.transferidMaxLen = (wcslen(transferid) + 1) * 2;
  cllect.pwxid = wxid;
  cllect.wxidLen = wcslen(wxid) + 1;
  cllect.wxidMaxLen = (wcslen(wxid) + 1) * 2;

  char* asmBuff = (char*)&cllect.ptransferid;

  DWORD dwWeChatWinAddr = (DWORD)GetModuleHandle(L"WeChatWin.dll");
  DWORD dwCall1 = dwWeChatWinAddr + WxCllectMoneyCall1;
  DWORD dwCall2 = dwWeChatWinAddr + WxCllectMoneyCall2;


  __asm
  {
    sub esp, 0x30;
    mov ecx, esp;
    mov eax, asmBuff;
    push eax;
    call dwCall1;
    call dwCall2;
    add esp, 0x30;
  }
}

最后附上 Github 地址,还请亲们帮忙点个 star,点击阅读原文直达项目地址:

https://github.com/TonyChen56/WeChatRobot


本文由 信安之路 创作,采用 知识共享署名 3.0,可自由转载、引用,但需署名作者且注明文章出处。

楼主残忍的关闭了评论