暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

微信BOT机器人复苏之旅

DBA巫师 2025-01-20
813

     

      

    加入我们的微信群,开启您的数据库学习之旅!

    你将获得:

    √免费最新GPT-4o模型智能微信机器人

    Oracle MOS免费查询

    职业发展规划咨询

    数据库专家交流

    运维经验分享

    为什么选择我们? “选择”比“努力”更重要。这里有众多志同道合的小伙伴,欢迎一起探讨、学习、进步!

    如何加入:

    扫描下方二维码,添加作者微信。

    回复“DBA理想”即可加入群聊。

    我们致力于让每一位DBA轻松享受最先进的人工智能技术。科技为每个人服务,而不仅限于少数专家。老群已满,新群现正虚位以待,快来加入吧!

        微信作为一个主要的社交平台,已经在企业内部通知、群管理、以及自动化任务中广泛使用。而在一些应用场景中,使用微信机器人的需求愈发增加,例如:微信群发通知、自动化处理群消息等。由于微信网页版接口的关闭,许多依赖微信机器人的工具和服务因此无法使用。为了解决这个问题,本文将介绍如何通过 WeChatFerry
     项目,基于 PC 版微信,实现微信机器人的消息传输通道。

    早期,基于微信网页版的自动化工具能够通过简单的脚本来实现消息发送与接收,但由于微信网页版接口的封闭,原有的方案被迫停止。因此,我们迫切需要一种新的方式来让机器人重新工作,尤其是群发通知、自动化处理群内消息等功能。WeChatFerry
     通过注入技术,绕过了这些限制,使得基于 PC 版微信的机器人得以重生。

    引用如下:

    微信机器人 DIY 从 0 到 1

    查克,公众号:碲矿微信机器人 DIY 从 0 到 1


    基本原理

    本质上,就是工具,“劫持”了微信:

    注入技术是指将外部代码“注入”到目标进程(此处是微信)内,并让该进程执行自定义的代码。在本项目中,注入技术主要用于将 Spy.DLL
     注入到微信的进程中,进而拦截微信的消息并对其进行处理。通过这种方式,WeChatFerry
     可以在微信内部“劫持”消息流,实现自动化操作。

    RPC 是一种在分布式计算中常用的技术,它允许程序调用位于不同进程或机器上的函数。在本项目中,RPC
     用于实现 WeChatFerry
     与微信进程之间的通信。通过 RPC,Spy.DLL
     和外部应用(如 Python 或 C++)能够高效地交换消息和指令。

    通过拦截技术,我们可以捕获微信收到的新消息,然后根据需求进行处理或转发。拦截通常依赖于“Hook”技术。我们通过修改微信内部函数的地址指向,实现在消息到达前对其进行预处理,从而达到自动化响应的目的。


      更形象一点,我们派一个间谍(Spy.DLL
      )打入微信内部,通过电报(RPC
      )和情报站(SDK.DLL
      )及外部特工(C++应用
       、 Python应用
      )进行消息交换:

      • 当微信收到消息时,Spy.DLL
         把消息通过 RPC
         传给 SDK.DLL
        ,再由 SDK.DLL
         分发给 C++应用
         或者 Python应用
      • 当 C++应用
         或者 Python应用
         需要发送消息时,会由 SDK.DLL
         通过 RPC
         传递给 Spy.DLL
         “假传圣旨”发送出去。

      到现在为止,还有一个问题:间谍是怎么混进去的?这就需要借助注入技术。下面介绍一下本项目涉及到的几个关键技术点。

      关键点

      根据前面的介绍:

      • Spy.DLL
         负责拦截、伪装,这就需要拦截技术(Hook);
      • RPC
         负责传送消息,涉及到跨进程间通信,本项目使用的是远程过程调用(Remote Procedure Call);
      • SDK.DLL
        (C++)和 Python应用
        (Python)能对话,涉及到混合编程;
      • 最后,为了把 Spy.DLL
        (间谍)打入微信内部,涉及到注入技术。

      项目虽小,但涉及到的技术点还挺多,非常有趣。


      Happy New Year

      首先介绍一下注入(Inject)技术


          注入技术指的是将外部代码插入到目标进程(在本项目中是微信进程)中,并让该进程执行自定义的代码。具体来说,在 WeChatFerry 项目中,我们使用注入技术将 Spy.DLL 插入到微信的进程中,以便拦截并处理微信的消息流。通过这种方式,WeChatFerry 能够在微信进程内部“劫持”消息,从而实现自动化操作。

      虽然注入技术通常与恶意软件相关,因为它允许外部代码未经授权地在目标进程中执行,但在本项目中,我们采用了一种经典且安全的注入方式。具体做法是:首先将 Spy.DLL 的路径写入微信进程的虚拟地址空间,然后通过创建一个远程线程,加载并执行 Spy.DLL。这一过程保证了注入的代码能够在微信进程中成功运行,从而实现消息的拦截与自动处理。

      参考实现如下:

          // 1. 获取目标进程,并在目标进程的内存里开辟空间
          HANDLE hProcess       = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
          LPVOID pRemoteAddress = VirtualAllocEx(hProcess, NULL1, MEM_COMMIT, PAGE_READWRITE);

          // 2. 把 dll 的路径写入到目标进程的内存空间中
          if (pRemoteAddress) {
              WriteProcessMemory(hProcess, pRemoteAddress, dllPath, wcslen(dllPath) * 2 + 2, &dwWriteSize);
          } else {
              MessageBox(NULLL"DLL 路径写入失败"L"InjectDll"0);
              return-1;
          }

          // 3. 创建一个远程线程,让目标进程调用 LoadLibrary
          hThread = CreateRemoteThread(hProcess, NULL0, (LPTHREAD_START_ROUTINE)LoadLibrary, pRemoteAddress, NULLNULL);
          if (hThread) {
              WaitForSingleObject(hThread, -1);
          } else {
              MessageBox(NULLL"LoadLibrary 调用失败"L"InjectDll"0);
              return-2;
          }
          CloseHandle(hThread);
          VirtualFreeEx(hProcess, pRemoteAddress, 0, MEM_RELEASE);
          CloseHandle(hProcess);


      Happy New Year

      通过注入技术实现微信消息拦截与伪装


      通过注入技术,我们成功地将 Spy.DLL(间谍)注入到微信进程中。下一步,我们的目标是使 Spy.DLL 能够拦截微信消息并伪装发送消息。这需要用到拦截和伪装技术。


      YEAR-END SUMMARY

      拦截技术

          拦截技术通常称为 Hook,其原理是通过替换目标程序中某个函数的地址,使其指向我们自定义的函数,从而实现对该函数的拦截。在此过程中,我们需要理解程序从编码到运行的基本流程。

      以 C/C++ 编程语言为例,程序在从源代码到执行的过程中,主要经历以下步骤:

      1. 编码
      2. 预编译、编译、汇编、链接
      3. 创建程序进程,加载程序代码和数据,创建和映射虚拟地址空间
      4. 创建主线程并运行程序

      在编译阶段,编译器将代码里的函数指令放入代码段。当程序加载到虚拟地址空间时,代码段被映射到内存。程序中的函数指针通常会指向代码段中的一个具体地址。

      假设微信在接收到一条新消息时,会调用某个函数来处理和展示该消息。如果我们能够修改该函数的地址并将其指向我们自己的处理函数,就可以实现消息的拦截。

      以下是拦截过程的一个例子:

      经分析,微信接收消息时,调用了如下函数:

        地址          机器码              反汇编
        0xF7F0F4C     E8 FF535400        call WeChatWi.0FD36350

        通过修改 0xF7F0F4C
         地址处的 call WeChatWi.0FD36350
        ,将其替换为指向我们自己函数的调用,就能拦截微信消息。为了不影响原有功能,我们还需要在自定义函数的最后,调用 WeChatWi.0FD36350
        ,恢复原有的功能。

        在代码中,我们会计算出 Hook
         和 Call
         的地址,具体的过程如下:

        1. 计算 Hook 地址
          Hook = 0x0F7F0F4C - 0x0F2A0000 = 0x550F4C

        2. 计算 Call 地址
          Call = 0x0FD36350 - 0x0F2A0000 = 0xA96350

        接着,我们编写 RecieveMsgHook
         函数来处理拦截的消息,代码示例如下:

          // 计算 Hook 和 Call 地址
          DWORD hookAddress = g_WeChatWinDllAddr + g_WxCalls.recvMsg.hook;
          recvMsgCallAddr = g_WeChatWinDllAddr + g_WxCalls.recvMsg.call;
          recvMsgJumpBackAddr = hookAddress + 5;


          // 组装机器码
          BYTE jmpCode[5] = {0};
          jmpCode[0] = 0xE9;  // 跳转指令
          *(DWORD *)&jmpCode[1] = (DWORD)RecieveMsgHook - hookAddress - 5;


          // 替换原地址的机器码
          WriteProcessMemory(GetCurrentProcess(), (LPVOID)hookAddress, jmpCode, 5, 0);


          通过这种方式,我们就能够实现对微信消息的拦截。



          YEAR-END SUMMARY

          伪装技术

          伪装技术是指当我们需要通过微信发送消息时,可以通过调用微信内部的消息发送函数来伪装发送操作。类似于拦截,我们通过找到微信发送消息时所调用的函数,模拟调用该函数来发送消息。

          在微信发送消息的过程中,研究发现,发送消息会调用如下函数:

            地址          机器码              反汇编
            0xF44FC03     E8 28213700         call WeChatWi.0F7C1D30


            当我们需要发送消息时,只需要调用 0xF7C1D30
            (即 0x0F7C1D30 - 0x0F2A0000
            )即可。通过调用该地址并传入相应的参数,我们可以伪装发送消息。示例代码如下:

              // 计算发送消息的地址
              DWORD sendMsgAddr = g_WeChatWinDllAddr + g_WxCalls.sendMsg.call;


              // 构造发送消息的参数
              SendMessageParams msgParams = {...}; / 需要发送的消息内容、接收者信息等


              // 调用发送消息函数
              WriteProcessMemory(GetCurrentProcess(), (LPVOID)sendMsgAddr, &msgParams, sizeof(msgParams), 0);


              通过这种伪装技术,我们能够模拟微信的消息发送功能,从而实现自动化消息发送。


              Happy New Year

              通过 RPC 实现微信消息的传输


              在前面,我们成功地将 Spy.DLL
               注入到微信进程,并实现了消息拦截和伪装功能。那么,接下来的问题是如何将消息在我们的应用和微信进程之间传递?

              由于微信和我们的应用程序是不同的进程,我们需要通过 进程间通信(Inter Process Communication, IPC)来实现它们之间的数据交换。Windows 提供了多种 IPC 方式,常见的包括:

                • 剪贴板
                • COM(组件对象模型)
                • 数据复制
                • DDE(动态数据交换)
                • 文件映射
                • Mailslots(邮件插槽)
                • 管道
                • Windows 套接字

                    在本项目中,大佬选择使用 RPC(远程过程调用)来进行进程间通信。

                RPC 是一种通信机制,它允许程序调用位于远程系统(甚至是同一台计算机上不同进程中的)函数。简而言之,RPC 使得我们可以像调用本地函数一样,调用其他进程中的函数,尽管这些函数可能位于不同的内存空间中。

                RPC 的优势在于它简化了跨进程、跨机器的通信过程,并且它的高效性使得分布式应用程序的开发变得更加便捷。在 Windows 环境中,RPC 工具可以让用户的客户端程序直接调用远程服务器程序中的方法,仿佛这些方法就在本地执行一样。

                RPC 工作时,客户端和服务器进程各自拥有独立的地址空间,意味着它们的数据和内存资源是分开管理的。客户端通过 RPC 请求访问服务器端的函数,RPC 框架负责将请求数据传输到远程服务器,服务器处理完请求后再将结果返回给客户端。RPC 体系结构大致如下图所示:

                通过这种方式,客户端和服务器之间不需要直接共享内存,而是通过 RPC 框架间接通信,这使得我们能够在不同进程之间轻松传递数据。

                Python 调用 C++ SDK:从 ctypes 到 PyBind11

                    前面已经成功实现了通过 C++ 拦截微信消息和“假传圣旨”的功能。但是,如果想让 Python 也能够调用 C++ 编写的代码(比如拦截消息和伪装操作),就需要借助 混合编程,即让 Python 调用 C++ 的 SDK。

                在 Python 与 C++ 交互时,可以选择多种不同的技术。微软文档中提到了几种常见的方式来实现这种混合编程。

                  下面是微软文档[5]上介绍的实现方式:

                  Approach
                  Vintage
                  Representative users
                  C/C++ extension modules for CPython
                  1991
                  Standard Library
                  PyBind11 (recommended for C++)
                  2015

                  Cython (recommended for C)
                  2007
                  gevent, kivy
                  HPy
                  2019

                  mypyc
                  2017

                  ctypes
                  2003
                  oscrypto
                  cffi
                  2013
                  cryptography, pypy
                  SWIG
                  1996
                  crfsuite
                  Boost.Python
                  2002

                  cppyy
                  2017

                  为什么选择 ctypes

                  最开始时,大佬选择了 ctypes 来实现 Python 调用 C++ 的功能。ctypes
                   是 Python 的一个标准库,提供了与 C 语言共享库(DLL 或 so 文件)的交互方式。它的优势在于:

                  • 简单易用
                    ctypes
                     使用起来非常简单,能够直接加载 C/C++ 编写的共享库,进行函数调用。
                  • 跨平台
                    ctypes
                     支持 Windows 和 Linux 等多种操作系统,适合做一些基础的 C/C++ 调用。

                  由于项目初期功能相对简单,ctypes
                   完全能够满足需求,且上手难度低。

                  为什么转向 PyBind11

                  随着项目功能的复杂性不断增加,ctypes
                   开始显得力不从心。虽然 ctypes
                   可以实现 Python 和 C++ 的互操作,但它的局限性也越来越明显:

                  • 性能问题
                    ctypes
                     的性能开销较大,特别是在需要频繁进行 Python 和 C++ 之间数据传递时。
                  • 复杂的类型转换
                    :C++ 中的复杂数据结构(如类、模板等)在 ctypes
                     中很难优雅地映射,导致代码维护变得繁琐。

                  因此,大佬转向了 PyBind11,这是一个专门为 Python 和 C++ 之间的互操作而设计的库。它的优势包括:

                  • 性能优化
                    PyBind11
                     是为高效调用而设计的,能够减少跨语言调用时的性能开销。
                  • 简洁的接口
                    :它可以自动将 C++ 的类、函数、以及数据结构绑定到 Python 中,简化了开发工作。
                  • 更好的类型支持
                    :与 ctypes
                     相比,PyBind11
                     能够更好地处理 C++ 的复杂类型,特别是类、智能指针等。

                  因此,随着项目逐渐增大,大佬们决定将 C++ 代码的调用转移到 PyBind11
                   上,这不仅提高了性能,也大大简化了代码的实现和维护。

                  具体实现

                  前面已经把关键技术介绍完了,具体实现就比较好理解了。

                  工程结构

                  WeChatFerry
                  ├── App
                  ├── Rpc
                  ├── SDK
                  ├── SDKpy
                  └── Spy

                  App

                  具体的应用,可以是个对话机器人,可以是个定时消息发送器,还可以是个群发机器。这个模块应该是最有趣的部分。

                  目前该部分只提供了一个 C++ 版和 Python 版的接口使用样例,用于介绍接口使用方法:

                  App.cpp
                  App.py

                  Rpc

                  主要是 RPC 接口的定义,使用了微软的 IDL(Interface Definition Language):

                  rpc.idl
                  rpc_memory.cpp

                  SDK

                  SDK 层是为给 App 层提供接口,封装了很多细节。本质是个 RPC Client,通过 RPC 将 App 的接口调用传送给 Spy 模块执行:

                  dllmain.cpp
                  framework.h
                  injector.cpp
                  injector.h
                  rpc_client.cpp
                  rpc_client.h
                  sdk.cpp
                  sdk.h
                  util.cpp
                  util.h

                  SDKpy

                  这部分就是前面提到的混合编程部分,实现了 Python 客户端:

                  sdkpy.cpp

                  Spy

                  这部分就是前面举例的拦截消息和“假传圣旨”的实现部分,目前实现了:

                  • 接受好友申请(accept_new_friend
                  • 执行 SQL(exec_sql
                  • 获取联系人(get_contacts
                  • 接收消息(receive_msg
                  • 发送消息(send_msg

                  源码文件如下:

                  accept_new_friend.cpp
                  accept_new_friend.h
                  dllmain.cpp
                  exec_sql.cpp
                  exec_sql.h
                  framework.h
                  get_contacts.cpp
                  get_contacts.h
                  load_calls.cpp
                  load_calls.h
                  receive_msg.cpp
                  receive_msg.h
                  rpc_server.cpp
                  rpc_server.h
                  send_msg.cpp
                  send_msg.h
                  spy.cpp
                  spy.h
                  spy_types.h

                  SDK 逻辑

                  SDK 的逻辑主要隐藏在初始化阶段,其他的接口一般直接调用就可以了。

                  SDK 的初始化完成:

                  1. 打开微信(如果没打开的话)
                  2. 注入 Spy.dll
                  3. 连接 RPC
                  4. 检查登录状态

                  初始化完成之后,SDK 便可以正常使用了。退出 SDK 的时候,只会断开 RPC 连接,以方便下次再使用 SDK。实现代码如下:

                  int WxInitSDK()
                  {
                      int status           = 0;
                      unsignedlong ulCode = 0;

                      GetModuleFileName(GetModuleHandle(WECHATSDKDLL), SpyDllPath, MAX_PATH);
                      PathRemoveFileSpec(SpyDllPath);
                      PathAppend(SpyDllPath, WECHATINJECTDLL);

                      if (!PathFileExists(SpyDllPath)) {
                          return ERROR_FILE_NOT_FOUND;
                      }

                      status = OpenWeChat(&WeChatPID);
                      if (status != 0) {
                          return status;
                      }

                      Sleep(2000); // 等待微信打开
                      if (InjectDll(WeChatPID, SpyDllPath)) {
                          return-1;
                      }

                      Sleep(1000); // 等待SPY就绪
                      status = RpcConnectServer();
                      if (status != 0) {
                          printf("RpcConnectServer: %d\n", status);
                          return-1;
                      }

                      do {
                          status = RpcIsLogin();
                          if (status == -1) {
                              return status;
                          } elseif (status == 1) {
                              break;
                          }
                          Sleep(1000);
                      } while (1);

                      return ERROR_SUCCESS;
                  }

                  Bot重生

                  这是一个充满活力、严肃认真的DBA交流群——你可以在这里讨论数据库的深奥哲学,也可以轻松地分享一些DBA的小秘密。我们的机器人功能会持续增加功能,毕竟,谁不想要个更聪明的机器人来帮忙呢?



                  文章转载自DBA巫师,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

                  评论