Skip to content
郭立 (leeguoo)

Sending and Receiving Messages Without Launching WeChat: Notes on Reconstructing a Standalone Communication Core

From QR code authorization and transport and application-layer encryption to synchronization cursors, message transactions, and bidirectional acceptance testing after uninstalling WeChat, this article reviews how we reconstructed a standalone core for WeChat text communication step by step.

ON THIS PAGE
1.1. Goal: What Exactly Are We Trying to Extract from the App?2.2. First, Distinguish Three Things: Transport, Business Logic, and Storage3.III. Process: Establish the Channel Before Dealing with the Account4.1. The Key to an Encrypted Connection Is Proving Who the Other Party Is5.2. A QR Code Is the Entry Point to a Login Flow6.3. Login Contains Another Layer of Key Agreement7.4. Why Can’t Normal Message Retrieval Begin Immediately After Login?8.The Synchronization Cursor Is Not the “Last Message ID”9.Why Must Messages and the Cursor Be Committed Together?10.5. How Does a Text Message Become a Network Request?11.The Order of Compression and Encryption Cannot Be Reversed12.The Hardest Part to Reimplement Is the Protocol’s Own Computation Logic13.6. Receiving Is Reverse Unpacking, but Not Sending Code Run Backward14.Does Receiving Messages Require Constant Polling?15.7. How Do We Prove It Truly Operates Independently of Desktop WeChat?16.8. Why Are Operation Records Also Part of the Communication Core?17.9. Conclusion: What Has Been Accomplished, and What Has Not?18.During Review, Walk Through These Seven Questions

When we moved WeChat chat history into our tool, a natural question arose: if the tool can read messages and decrypt images, why does sending still depend on WeChat running on the computer? Could it log in, make requests, and receive responses on its own, just like a network client?

That question was the starting point for this research. We narrowed the objective to a verifiable scope: without launching the desktop WeChat client, independently complete QR code login, initialization, text message sending and receiving, and persist the resulting state. Finally, remove the dedicated WeChat installation used by the tool and perform another bidirectional messaging test.

This workflow has now been successfully tested with our own account and File Transfer Assistant. The phone displayed the complete text sent by the standalone program, and the standalone program received the reply from the phone, with the message body matching byte for byte. The following sections explain the principles and reconstruction process in the order we actually learned them.

This is a research record focused on a specific version and protocol path. The code is developed separately in a private repository and is not released with WeChat Use. This article contains no real account credentials, login QR codes, or original chat content.

1. Goal: What Exactly Are We Trying to Extract from the App?

Imagine sending “Hello” in WeChat. The window lets you choose a contact, enter text, and click Send. Behind that window, however, something must also handle account identity, serialization, encryption, network connections, server responses, and local state.

What we want to implement independently is that latter part.

Here, “core” refers to the communication protocol and state machine, not the operating system kernel. Nor does it mean rewriting every WeChat feature. A minimal standalone text client can initially accept an explicit recipient identifier, without first implementing the entire contact list, group membership, Moments, and media systems.

This gives us concrete acceptance criteria:

  1. A new session must be established through confirmation by scanning a QR code on the phone; it cannot reuse the login state of the currently running desktop WeChat process.
  2. Outgoing application requests must be encoded, encrypted, and transmitted by our own program.
  3. Receiving messages must advance our own synchronization state and write them to our own message database.
  4. After the program exits, a new process must be able to restore the state, rather than relying on the illusion maintained by a process that never terminates.
  5. Two-way messaging must still work when desktop WeChat is not running and the tool-specific copy has been removed.

The previous article, How WeChat Images Are Encrypted and How We Reconstructed the Process Step by Step, examined the recovery of image objects and caches. This time, we move one layer upstream: before a message becomes a local chat record, how does it enter the client from the network?

2. First, Distinguish Three Things: Transport, Business Logic, and Storage

Saying “WeChat uses AES” is nowhere near enough. The same content may be wrapped differently at different points, with different keys and states.

We divide the system into three layers:

LayerProblem It SolvesWhere It Appears in This Research
Transport protectionHow a trusted encrypted channel is established for a connection and whether records have been modifiedMMTLS handshake, record authentication, short-connection resumption
Business-layer encapsulationHow an account proves its identity and how individual send or sync requests are encodedLogin protocol, business-layer encryption, request headers, message bodies
Local persistenceHow received messages and the next sync position are stored reliablyOur own inbox database, transactions, cursors, operation logs

Decrypting an image DAT file does not mean decrypting a communication session; completing the transport handshake does not mean the account has logged in successfully; and even after a successful login, the synchronization state must still be initialized before incremental messages can be received correctly.

The complete flow can be summarized as follows:

mermaid
flowchart TD
    A[Our Own Client] --> B[Establish an Authenticated Transport Channel]
    B --> C[Obtain a Login QR Code]
    C --> D[Scan and Confirm on the Phone]
    D --> E[Complete Account Authentication]
    E --> F[Perform Initial Setup and Synchronization]
    F --> G[Save Our Own Account State and Message Database]
    G --> H[Send a Text Request]
    G --> I[Incrementally Synchronize Messages]
    H --> J[Verify the Server Response Against the Phone]
    I --> K[Commit Messages and the New Cursor in the Same Transaction]

This diagram also explains why “just send it with curl” is not as simple as it sounds. An HTTP request is only one possible outer shell. What truly needs to be implemented independently is: what state must be prepared before the request, how the bytes in the packet are generated, and what is allowed to change after the response is received.

III. Process: Establish the Channel Before Dealing with the Account

1. The Key to an Encrypted Connection Is Proving Who the Other Party Is

Our transport implementation starts with a new ephemeral elliptic-curve key, verifies the server signature, and checks the verification value at the handshake completion stage. Only then does it establish separate keys and sequence numbers for sending and receiving.

Two concepts are worth separating here.

Key exchange allows both parties to derive a shared secret; authentication allows the client to confirm that the party exchanging that secret is indeed the intended server. If only the former is performed, being able to decrypt the data does not automatically mean the connection is trustworthy.

Record sequence numbers are equally important. They participate in managing a connection’s encryption state. The sending and receiving directions must not share them, and a failed context must not be reused to assemble the next packet. Our implementation rejects sequence-number overflow outright and stops reusing contexts after a failure.

This path uses cryptographic components such as P-256, but MMTLS has its own message and state conventions. It cannot simply be called “standard TLS 1.3” with the assumption that a general-purpose TLS library has already implemented the entire protocol for us.

Short-lived connections also involve authenticated session tickets and resumed-key derivation. Their purpose is to reduce the cost of reestablishing each connection; they do not mean persisting the previous ephemeral private key and the entire encryption context, then reusing them unchanged.

2. A QR Code Is the Entry Point to a Login Flow

Once the channel is established, the client can request a QR code. But a valid PNG only proves that an image was obtained; it does not prove that the account has been logged in.

The login flow we reconstructed can be summarized as follows:

text
Get QR → Display QR code → Scan and confirm on the phone
       → Check the status of the same QR code
       → Final account authentication → Save authenticated account material

In the version-specific path examined here, retrieval and checking correspond to Get 502 and Check 503, respectively, while final authentication corresponds to Manual 252. These numbers are operation identifiers used in our analysis of this protocol path, not a stable public API.

The easiest detail to overlook is “the same QR code.” The check response must be bound to the identity of the original request and the source of the QR code. A check result from an old QR code cannot be used to complete a new authentication attempt, nor can server-side status confirmation be skipped merely because the user says they scanned it.

Confirmation after scanning on the phone is an authorization action. The program can complete the protocol flow, but it cannot confirm the account login on the user’s behalf.

3. Login Contains Another Layer of Key Agreement

Final authentication is not simply a matter of “sending the scan result back.” In this version-specific path, we also reconstructed a nested secp224r1 agreement: the uncompressed public key is 57 bytes, the resulting shared X coordinate is 28 bytes, and a key used to unpack the data is then derived using the observed algorithm.

This does not serve the same role as the earlier P-256 exchange. P-256 appears in the transport handshake path, while secp224r1 appears in this login business-logic path. Abbreviating both as “ECDH keys” makes it easy to connect the wrong inputs during implementation.

In this login branch, we observed that the MD5 digest of the shared X coordinate participates in CBC wrapping, with the key and IV using the same bytes. We reproduced this behavior for compatibility with the existing protocol; it is not a cryptographic design recommendation for new systems.

Likewise, the MD5 operation here must not be treated as server authentication. Hashes, business-level check values, and handshake signatures serve different purposes. If one is missing, another cannot substitute for it merely because it also “looks like a verification.”

4. Why Can’t Normal Message Retrieval Begin Immediately After Login?

Imagine receiving a new access card. It proves you may enter, but it does not tell you which message you last read.

After its first login, an independent client also needs to obtain its initial synchronization state. In this flow, it first executes NewInit 139, uses the bound initialization fields from the response to establish its own synchronization starting point, and then performs the first NewSync.

Here we specifically confirmed one detail: the f2 field from the Init response is used. Even if f2 and f3 happen to contain identical values in a particular sample, that does not erase their field identities. A coincidental equality in one instance does not establish a protocol contract that makes them interchangeable.

Only after the first synchronization completes does the client commit its own message store and generate its runtime configuration. This way, “initialization complete” means that a set of state has been persisted and can be reloaded by a new process—not merely that some function returned 0.

The Synchronization Cursor Is Not the “Last Message ID”

Incremental state in chat applications is usually more complex than a timestamp. The SyncKey we parsed is a set of identified state values that must be merged according to protocol rules; it cannot simply be replaced with the ID of the last message.

A response also contains more than a single text message. It contains a list of commands, with different commands representing different changes. For message commands, we preserve nested strings, raw fields, and full-width integers. We also retain the raw data for unrecognized commands, avoiding silent data loss followed by a false claim that synchronization is complete.

Message IDs may also exceed JavaScript’s safe integer range for Number. Storage and interfaces must preserve the complete 64-bit value. Where values must pass through JSON, use exact decimal strings rather than converting them to floating-point numbers and then back again.

Why Must Messages and the Cursor Be Committed Together?

Suppose a batch of messages M arrives together with a new cursor K.

If K is saved first and the process then crashes before M is saved, the server may continue from after K next time, causing messages to be missed. If M is saved but K is not, the same batch may be received again.

Our approach preserves the raw response and a source digest, then commits the message versions, batch deduplication information, and new cursor together in a database transaction:

text
Validate the entire batch response
    ↓
Save the traceable raw response
    ↓
BEGIN
    Write message versions
    Write batch deduplication information
    Update the synchronization cursor
COMMIT

If any validation fails, the cursor is not advanced. This design does not require reading or modifying WeChat Desktop’s live database; it manages the independent client’s own data.

5. How Does a Text Message Become a Network Request?

At this point, we have the account state and synchronization starting point. Only now can we send “Hello.”

First, the recipient, message body, and related business fields are encoded into protobuf bytes. Protobuf handles structured encoding only; it does not provide encryption. The business layer then applies compression and encryption according to the selected mode, adds the business header and check value, and finally places everything into a transport-layer record for transmission.

mermaid
flowchart LR
    A[Recipient and text] --> B[Protobuf business payload]
    B --> C[Mode-specific compression and encryption]
    C --> D[Business header and check value]
    D --> E[Encrypted transport record]
    E --> F[Server]

The Order of Compression and Encryption Cannot Be Reversed

This static reconstruction includes two business-payload encoding branches: ordinary mode 5 and mode 13.

Mode 5 checks whether zlib compression is beneficial before applying AES-128-CBC. The original length and selected pre-encryption length in the packet header have distinct meanings; neither can simply be set to the ciphertext length.

Mode 13 uses mandatory compression and AES-GCM. The observed format contains ciphertext, a 12-byte nonce, and a 16-byte tag. In AEAD, the tag verifies the integrity of the ciphertext and associated inputs, while the nonce-generation constraints are also part of protocol correctness. RFC 5116 provides general guidance on authenticated-encryption interfaces and nonce requirements; it is not a specification of the WeChat protocol.

Actual sending and receiving with a real account in this work used the confirmed ordinary CBC5 path. Implementing offline tests for another encoding branch does not mean that every real message exchange uses GCM, nor does it mean that all unidentified modes are automatically supported.

The Hardest Part to Reimplement Is the Protocol’s Own Computation Logic

Standard implementations are readily available for AES, elliptic-curve cryptography, and zlib. Protocol-specific check values, however, cannot be reproduced by simply choosing a common algorithm.

We encountered computation logic embedded in a fixed module. Continuing to call functions inside WeChat Desktop would leave the “independent client” dependent on the original application. Instead, we treated the module as data, decoded the instructions actually used, and wrote a restricted interpreter.

The interpreter accesses only its own mapped memory, never treats untrusted addresses as host pointers, forbids arbitrary external calls, and enforces an instruction-step limit. It currently supports the 55 instruction types actually used, and the module bytes are constrained by a fixed digest.

Along this path, the input is derived from the encrypted business payload and passed to the module for computation, which ultimately produces a result of the required length. We validate the return code, output length, and actual bytes written. If any check fails, packet assembly is rejected.

The key lesson is this: reconstructing a protocol is not just about finding keys; it also requires reconstructing the computations and state needed to produce valid requests. Successfully calculating one local check value proves only that the local function produced an output. It must still be validated through a complete request and a real server response.

6. Receiving Is Reverse Unpacking, but Not Sending Code Run Backward

After receiving a server response, first verify the transport record, then unwrap the corresponding business-layer envelope, parse the commands and messages, and finally enter the synchronization transaction.

This path runs in the opposite direction with similar responsibilities, but not every assumption from the sending side can be mechanically reused. For example, if an encoder can encrypt and then decrypt its own fabricated data, that only proves it is internally consistent. If both operations use the same incorrect interpretation of a field, the round-trip test may still pass.

We therefore validate these aspects separately: fields and boundaries in fixed samples, rejection behavior for modified data, real server responses, and correspondence with complete message bodies on the phone. Only when this evidence connects end to end can a receiving path be considered operational.

Does Receiving Messages Require Constant Polling?

Here, we must distinguish between the official networking components and our current implementation.

Tencent's public Mars project provides networking components, with STN covering mechanisms such as long and short connections and heartbeats. It helps explain the overall network architecture, but the public networking components do not constitute the complete WeChat account, login, and business protocols.

Our standalone tool currently supports explicit incremental fetching and synchronization triggered by heartbeat notifications. Full long-connection push support has not yet been completely wired up, so we cannot claim that the current tool fully reproduces the official push mechanism. Conversely, we also cannot conclude that official WeChat relies solely on fixed-interval polling.

The waiting intervals and timeouts configured in the tool are our own runtime policies. If the unit of a waiting value in the protocol has not yet been firmly established, its magnitude alone cannot justify interpreting it as a specific number of seconds.

7. How Do We Prove It Truly Operates Independently of Desktop WeChat?

Simply closing the window is not enough. The application may still be running in the background; our own program might also secretly read resources from the original application or reuse an old background service.

Therefore, the final acceptance test involved several steps beyond confirming that the command exited with code 0.

First, we used new account credentials established through an independent QR-code scan to complete initialization and set up our own message storage. We then launched a new process outside the source directory and, with network access disabled, read back the inbox and cursor. This proves that the state was actually persisted; it does not prove that the offline status command logged in to the server again.

Next, we properly quit the dedicated WeChat instance used by the tool on the current computer and moved that copy to the Trash. The chat data was not cleared, and the application can be restored. The primary WeChat installation remained installed but was not running.

To eliminate the possibility that the tool was secretly retrieving data from the applications even though they were not running, the protocol runtime was restricted using the system sandbox: read access was denied to both the primary WeChat installation and the dedicated copy in the Trash, while access to the original applications' related data directories and services was also restricted. Actual read probes returned permission-denied errors.

Finally, under these conditions, we performed one send and one receive operation:

Item checkedActual resultWhat it demonstrates
Text sendingThe server accepted the message, and the phone displayed the full test textThe independent sending path reached the phone
Text receivingThe new message body was byte-for-byte identical to the phone's reply, and the server message ID was nonzeroIndependent synchronization retrieved the corresponding message
Synchronization stateThe sequence advanced from 2 to 3, with 130 message versions stored in totalThe inbox transaction and state persistence actually occurred
Runtime environmentNo Desktop WeChat process was detected in samples taken during sending or receivingThese two operations did not depend on a running Desktop WeChat instance
Application dependencyThe dedicated copy no longer existed at its original path; sandbox rules denied reads from both the primary application and the copy in the TrashThe runtime could not complete the operations by reading either application
LifecycleOriginal child-process exit records and request-result associations were verifiedThe success did not come from another request cycle or a leftover process

The figure 130 refers to the number of stored message versions, not 130 distinct new chat messages. The final incremental receive operation added two message versions, one of which contained a new message body that fully matched the test reply sent from the phone.

A more precise conclusion is: With the tool's dedicated WeChat copy removed, the primary WeChat installation not running, and both inaccessible to the protocol process, we completed bidirectional text messaging through our own implementation. This does not mean that every WeChat file on the computer was deleted.

8. Why Are Operation Records Also Part of the Communication Core?

A send request can encounter a troublesome situation: the server has already processed it, but the client never received the response. Automatically sending it again may result in duplicate delivery.

We therefore persist the send intent before creating the specific subprocess that executes it. The request, parameter summary, process identity, result, and exit record are all tied to the same operation. If the outcome is unknown, we retain the record and stop automatic retries. A file failing to appear temporarily must not be treated as evidence that it was never sent.

Receiving has a similar issue, though local duplicates can be controlled through batch deduplication and atomic commits. When the sender's status is unknown, however, local deduplication alone cannot prove that the remote side did not execute the operation.

This is also why migrating between computers requires more than copying login materials. We must also copy a consistent inbox store and operation history, while preventing the old computer from continuing to use the same runtime state. Otherwise, two clients may advance the cursor simultaneously or reach contradictory conclusions about operations with unknown outcomes.

Saving a session file is easy. Saving client state that can explain “what I have done and what I can do next” is the full meaning of persistence.

9. Conclusion: What Has Been Accomplished, and What Has Not?

This work produced a standalone text communication core: QR-code authorization via phone, account authentication, first-time initialization, its own message database, incremental synchronization, text sending, and state restoration in a new process. After removing the dedicated desktop WeChat instance used by the tool, real-world testing still confirmed that bidirectional text messages corresponded correctly between the phone and local records.

It is not yet a complete WeChat client. A complete contact list and group-member mapping, support for arbitrary recipients, media sending and receiving, full persistent-connection push support, and long-term runtime stability cannot be inferred from these two text-message acceptance tests. The previous image-decryption work also does not automatically become a media capability of this new core; it still needs separate integration and validation.

The main research method I want to preserve from this work is this: break a large application into several boundaries with defined inputs, outputs, and states, then find corresponding evidence for each boundary.

When a QR code appears, continue by asking whether account authentication has completed. After decoding a response, continue by asking whether the messages and cursor were committed together. When the server reports success, verify it on the phone. When a new process starts successfully, check whether it has borrowed state from the original application.

By reconstructing the system layer by layer, “sending requests independently of the application” evolved from an idea into a standalone, reproducible communication program.

During Review, Walk Through These Seven Questions

  1. After the window closes, what is still communicating? Distinguish between the application UI, the protocol client, and the server.
  2. Why not look for just one AES key? Keys and state used for transport, business logic, and storage serve different purposes.
  3. Does displaying the QR code mean you are logged in? Phone confirmation, same-origin state verification, and final account authentication are still required.
  4. Where does the first message retrieval begin? Initialize first, then use synchronization state from an exact source.
  5. Why must the cursor not be saved early? Messages and the cursor must be committed in the same local transaction.
  6. How do you verify successful sending? Check the valid response tied to the request, then verify the complete message body on the receiving device; do not automatically retry an unknown outcome.
  7. How do you prove independence? Do not run the original application, restrict dependency reads, preserve evidence from the original process, and perform real bidirectional acceptance testing.

The WeChat-specific fields and acceptance results in this article come from this private implementation and its preserved raw records. External links are provided only as supplementary background on network architecture and cryptography, not as substitutes for these empirically verified conclusions.

More in this category · Reverse Engineering & Security

next →
How WeChat Images Are Encrypted—and How We Restore Them Step by Step

Comments

Replies are public immediately and may be moderated for policy violations.

Max 1000 characters.