DIY WeChat Bot from Scratch

  • Motivation

  • Basic Principles

  • Key Technologies

    • Injection

    • Interception and Spoofing

    • RPC

    • Mixed Programming

  • Specific Implementation

    • Project Structure

    • SDK Logic

  • Applications

    • Weather Reporting Bot

    • Simple Response Bot

    • To Be Continued…

The title might be a bit misleading—this article primarily focuses on introducing a communication channel for WeChat bots, with less emphasis on the bot itself. However, even if it can only respond mechanically, it can still be considered a bot. This article intends to elaborate on five aspects: motivation, basic principles, key technologies, specific implementation, and application examples.

The code is open-source: WeChatFerry[1], and hands-on enthusiasts can directly download the SDK[2] to get started (supports Python!).

The WeChat installation package repository is also open-source, and you can download the corresponding version of the WeChat installation package: WeChatSetup[3].

ℹ️ WeChatFerry is a tool based on PC WeChat. If you don’t have a Windows computer, you can still play around with it using a virtual machine. Reply with WeChatFerry to join the group chat for discussions!

Motivation

Initially, a senior developer had a requirement to send notifications via WeChat group messages. I found ichat, a framework based on the WeChat web version. However, after a few days, my WeChat web version was banned.

This might be because the WeChat web protocol was almost cracked, and later, the WeChat web version became inaccessible. I had no choice but to look for alternatives.

Thanks to the great internet, I finally found another framework based on PC WeChat. However, this framework supported an older version, and when I debugged the project and published it on the server, it failed—my server was a new system, and the old version of WeChat couldn’t be used.

At this point, I had two options: one was to create an image of the development environment and install it on the server to continue using the old version of WeChat; the other was to create a new solution and adapt it to the latest version of WeChat.

Out of love for technology, I chose to create a new solution. The functionalities that have been implemented so far include:

  • Getting login status
  • Sending text messages (can @ in group chats)
  • Sending image messages
  • Receiving all types of messages
  • Getting contacts (based on memory)
  • Friend verification
  • Querying the database to get libraries and tables
  • Executing SQL
  • Supporting Python3

Basic Principles

Essentially, I wrote a tool that “hijacks” WeChat:

  • When WeChat receives a message, the tool processes it before WeChat does (before displaying it on the page); after processing, it hands it back to the original processing module;
  • When sending a message, it simulates WeChat sending the message, assembles the message body, and calls the WeChat sending message module;
  • To get contacts, it traverses a specific memory space;
  • For friend verification, it assembles the verification information and calls the WeChat verification module;
  • Database-related functions are executed by obtaining the database handle and referencing the sqlite3 interface.
DIY WeChat Bot from Scratch

To illustrate, we deploy a spy (Spy.DLL) into WeChat, exchanging messages through telegrams (RPC) and intelligence stations (SDK.DLL) and external agents (C++ applications, Python applications):

  • When WeChat receives a message, Spy.DLL passes the message to SDK.DLL via RPC, which then distributes it to C++ applications or Python applications;
  • When C++ applications or Python applications need to send a message, SDK.DLL passes it to Spy.DLL to “falsely convey the order” to send it out.

So far, there is still a question: how did the spy get in? This requires the use of injection technology. Below, I will introduce several key technical points involved in this project.

Key Technologies

Based on the previous introduction:

  • Spy.DLL is responsible for interception and spoofing, which requires interception technology (Hook);
  • RPC is responsible for message transmission, involving inter-process communication, and this project uses Remote Procedure Call;
  • SDK.DLL (C++) and Python applications (Python) can communicate, involving mixed programming;
  • Finally, to inject Spy.DLL (the spy) into WeChat, injection technology is involved.

This is also why I enjoy this project, as it may be small, but it involves many interesting technical points.

Injection

First, let’s introduce injection technology.

Injection technology is usually associated with malicious software, generally used to execute custom code in the target process. There are many types of injection technology, and this project selects the most classic one: writing the path of Spy.DLL into the virtual address space of the WeChat process, and then creating a remote thread in the WeChat process to load Spy.DLL: DIY WeChat Bot from Scratch

The reference implementation is as follows:

    // 1. Get the target process and allocate space in the target process's memory
    HANDLE hProcess       = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
    LPVOID pRemoteAddress = VirtualAllocEx(hProcess, NULL, 1, MEM_COMMIT, PAGE_READWRITE);

    // 2. Write the dll path into the target process's memory space
    if (pRemoteAddress) {
        WriteProcessMemory(hProcess, pRemoteAddress, dllPath, wcslen(dllPath) * 2 + 2, &dwWriteSize);
    } else {
        MessageBox(NULL, L"DLL path write failed", L"InjectDll", 0);
        return -1;
    }

    // 3. Create a remote thread to call LoadLibrary in the target process
    hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)LoadLibrary, pRemoteAddress, NULL, NULL);
    if (hThread) {
        WaitForSingleObject(hThread, -1);
    } else {
        MessageBox(NULL, L"LoadLibrary call failed", L"InjectDll", 0);
        return -2;
    }
    CloseHandle(hThread);
    VirtualFreeEx(hProcess, pRemoteAddress, 0, MEM_RELEASE);
    CloseHandle(hProcess);

Interception and Spoofing

By using injection technology, we successfully injected Spy.DLL (the spy) into WeChat. The next step is to enable Spy.DLL (the spy) to “hijack” WeChat messages and “falsely convey orders”, which requires interception and spoofing technology.

Interception

Interception technology is commonly referred to as Hook.

To introduce interception technology, we first need to discuss the process from coding to execution. Taking C/C++ as an example, the program generally goes through the following stages:

  1. Coding
  2. Pre-compilation, compilation, assembly, linking
  3. Creating the program process, loading program code and data, creating and mapping virtual address space
  4. Creating the main thread and running the program

During the compilation stage, the compiler places the instructions in the code into the code segment. When the program is loaded into the virtual address space, the code segment is mapped over. Thus, the functions in our program can be replaced with an address (does this remind you of pointers?).

When WeChat receives a new message that needs to be displayed to the user, it is reasonable to assume that it will call a certain function to display the message. If we replace this function with our own, we can intercept WeChat’s messages. As mentioned earlier, during program execution, a function is merely an address pointer, so we just need to point this address to our own function to achieve interception.

Here’s an example:

# Research shows that when WeChat receives a message, it calls the following function
# Address       Machine Code            Disassembly
0F7F0F4C    E8 FF535400     call WeChatWi.0FD36350

We just need to replace call WeChatWi.0FD36350 in 0F7F0F4C with call our own function to intercept the message. At the same time, to avoid affecting the original functionality, we also need to call WeChatWi.0FD36350 at the end of our own function.

We refer to 0F7F0F4C as the Hook address and WeChatWi.0FD36350 as the Call address. Here, both 0F7F0F4C and 0FD36350 are “relative” addresses—relative to the address of WeChatWin.dll; the address of WeChatWin.dll is called the Base address.

In this example, the base address of WeChatWin.dll is: 0F2A0000, so:

Hook = 0x0F7F0F4C - 0x0F2A0000 = 0x550F4C
Call = 0x0FD36350 - 0x0F2A0000 = 0xA96350

Assuming we wrote a function RecieveMsgHook to handle intercepted messages, the following code can achieve message interception:

    // Calculate the addresses of Hook and Call
    DWORD hookAddress   = g_WeChatWinDllAddr + g_WxCalls.recvMsg.hook;
    recvMsgCallAddr     = g_WeChatWinDllAddr + g_WxCalls.recvMsg.call;
    recvMsgJumpBackAddr = hookAddress + 5;

    // Assemble machine code
    BYTE jmpCode[5] = { 0 };
    jmpCode[0]      = 0xE9;  // Original function call, machine code E8, now changed to jump E9

    // Replace the original WeChatWi.0FD36350 with the address of RecieveMsgHook
    *(DWORD *)&jmpCode[1] = (DWORD)RecieveMsgHook - hookAddress - 5;

    // 0F7F0F4C    E8 FF535400     call WeChatWi.0FD36350
    WriteProcessMemory(GetCurrentProcess(), (LPVOID)hookAddress, jmpCode, 5, 0);

Spoofing

When we need to send a new message on WeChat, it is reasonable to assume that WeChat will call a certain function to send the message. If we find this function, assemble the sending content, and call it, we can send WeChat messages.

Here’s an example:

0F44FBF3    8D46 38         lea eax,dword ptr ds:[esi+0x38]
0F44FBF6    6A 01           push 0x1
0F44FBF8    50              push eax                                 ; At members
0F44FBF9    57              push edi                                 ; Message
0F44FBFA    8D55 90         lea edx,dword ptr ss:[ebp-0x70]          ; Receiver wxid
0F44FBFD    8D8D 50FCFFFF   lea ecx,dword ptr ss:[ebp-0x3B0]         ; Buffer
# Research shows that when WeChat sends a message, it uses the following function
0F44FC03    E8 28213700     call WeChatWi.0F7C1D30                   ; Send Msg
0F44FC08    83C4 0C         add esp,0xC
0F44FC0B    C645 FC 05      mov byte ptr ss:[ebp-0x4],0x5
0F44FC0F    8B85 70FCFFFF   mov eax,dword ptr ss:[ebp-0x390]
0F44FC15    0B85 74FCFFFF   or eax,dword ptr ss:[ebp-0x38C]
0F44FC1B    75 10           jnz short WeChatWi.0F44FC2D

Thus, when we need to send a message, we just need to call 0x521D30 (0x0F7C1D30 – 0x0F2A0000).

RPC

Having successfully infiltrated WeChat and being able to intercept messages and “falsely convey orders”, how do we send messages out or bring them in?

WeChat and our application are in different processes. If our application needs to communicate with WeChat, it involves inter-process communication (IPC).

Windows supports several IPC methods, including:

  • Clipboard
  • COM
  • Data Copy
  • DDE
  • File Mapping
  • Mailslots
  • Pipes
  • RPC
  • Windows Sockets

RPC stands for Remote Procedure Call. Here, remote refers to not being in the same process; it can be different processes on the same computer or different computers. Using RPC, high-performance tightly coupled distributed applications can be created.

This project chose RPC, which caused some trouble. However, through RPC, inter-process communication becomes very simple. The RPC tool makes it appear as if the client is directly calling the procedure located in the remote server program. The client and server each have their own address space; that is, each resource has its own memory allocation for the data used by the procedure. The following diagram illustrates the RPC architecture: DIY WeChat Bot from Scratch

Yet Another Demo for Windows RPC[4] summarizes some uses of RPC on Windows.

Mixed Programming

Now that we can intercept messages and “falsely convey orders”—but only for our own (C++). How can we allow Python to also intercept messages and “falsely convey orders”? This involves mixed programming, specifically Python calling the C++ SDK.

The following is the implementation method described in Microsoft Documentation[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
Boost.Python 2002
cppyy 2017

This project initially chose ctypes for its simplicity. However, as the functionality became more complex, ctypes became difficult to manage, so I switched to PyBind11.

Specific Implementation

Having introduced the key technologies, the specific implementation is easier to understand.

Project Structure

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

App

The specific application can be a chat bot, a scheduled message sender, or a mass messaging bot. This module should be the most interesting part.

Currently, this part only provides a C++ version and a Python version of the interface usage example to introduce how to use the interface:

App.cpp
App.py

Rpc

This mainly defines the RPC interface, using Microsoft’s IDL (Interface Definition Language):

rpc.idl
rpc_memory.cpp

SDK

The SDK layer provides interfaces for the App layer and encapsulates many details. Essentially, it is an RPC Client that transmits the App’s interface calls to the Spy module for execution:

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

SDKpy

This part is the mixed programming part mentioned earlier, implementing the Python client:

sdkpy.cpp

Spy

This part implements the interception of messages and “falsely conveying orders” as previously illustrated, currently implementing:

  • Accepting friend requests (accept_new_friend)
  • Executing SQL (exec_sql)
  • Getting contacts (get_contacts)
  • Receiving messages (receive_msg)
  • Sending messages (send_msg)

The source code files are as follows:

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 Logic

The logic of the SDK is mainly hidden in the initialization phase, while other interfaces can generally be called directly.

The SDK initialization completes:

  1. Open WeChat (if not already open)
  2. Inject Spy.dll
  3. Connect RPC
  4. Check login status

After initialization, the SDK can be used normally. When exiting the SDK, it only disconnects the RPC connection for convenient use of the SDK next time. The implementation code is as follows:

int WxInitSDK()
{
    int status           = 0;
    unsigned long 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); // Wait for WeChat to open
    if (InjectDll(WeChatPID, SpyDllPath)) {
        return -1;
    }

    Sleep(1000); // Wait for SPY to be ready
    status = RpcConnectServer();
    if (status != 0) {
        printf("RpcConnectServer: %d\n", status);
        return -1;
    }

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

    return ERROR_SUCCESS;
}

Applications

Initially, the wheel was created out of necessity, but unexpectedly, the need disappeared after the wheel was created, so now I can only make some useless things.

Weather Reporting Bot

Every day at 7 AM, it publishes the weather forecast in the group:

To create the weather reporting bot, I wrote a weather crawler to fetch the weather data. The code is also open-source: WeatherScrapy[6].

Simple Response Bot

Can only answer weather inquiries:

DIY WeChat Bot from Scratch

This application uses RASA, and I created some training data for simple training.

To Be Continued…

  • Statistics on friend distribution
  • Calculating intimacy between friends (like ratio, number of common group chats, chat frequency…)
  • Wool group bot
  • Clearing group zombies
  • ……

Reply with WeChatFerry to join the group chat for discussions!

Related Links

[1]

WeChatFerry: https://gitee.com/lch0821/WeChatFerry

[2]

SDK: https://gitee.com/lch0821/WeChatFerry/releases

[3]

WeChatSetup: https://gitee.com/lch0821/WeChatSetup

[4]

Yet Another Demo for Windows RPC: https://gitee.com/lch0821/RpcDemo

[5]

Microsoft Documentation: https://docs.microsoft.com/en-us/visualstudio/python/working-with-c-cpp-python-in-visual-studio?view=vs-2022

[6]

WeatherScrapy: https://gitee.com/lch0821/weather-scrapy

Leave a Comment