
This article is an excellent piece from the Kanxue Forum.
Author ID on Kanxue Forum: xwtwho
Recently, I looked into WhatsApp (both Android and PC versions) and implemented the protocol for sending private messages (template messages can also be sent). Here, I record my learning process.
1. Hardware and Software Environment
WhatsApp v2.22IDA 7.5Frida 14.2.2Gda3.86JEBjadx-guiunidbgLineageOS 17.1 (Android 10)Xiaomi Mi 8
2. Recording the Learning Process
I started by understanding WhatsApp, which claims to have end-to-end encryption for messages, meaning that only the two parties can decrypt the information, and the server does not know the content. Initially, I was quite confused and searched for information online:【Translation】Overview of WhatsApp Encryption (Technical White Paper)http://www.caotama.com/1993224.htmlWhatsApp Communication Protocol End-to-End Encryption AIhttps://blog.csdn.net/BMW33939/article/details/120322512Signal Protocol https://blog.csdn.net/yzpbright/article/details/117808556However, looking at these materials was somewhat awkward; when I first started, I couldn’t understand them due to various key concepts and the encryption process, which was quite confusing. Once I began analyzing the app and could understand it, I found that I had already completed the analysis. Looking back, I realized that what was written (especially in the white paper) was indeed correct, but it was hard to grasp initially since the articles did not provide practical processes.First, I looked at the data flow to determine the network transmission method. Using Wireshark, I confirmed through hooking and setting breakpoints that it was TCP:
I checked the IP: 157.240.199.61, which is Facebook in Hong Kong.After identifying the sending point, I combined it with JNI functions (the important modules can be identified by their names: libwhatsapp.so, libcurve25519.so) to gradually determine the calling lines.Upon seeing the names of the above SO files, I researched the knowledge points:Curve25519 is currently the highest level of Diffie-Hellman function, suitable for a wide range of scenarios, designed by Professor Daniel J. Bernstein.In cryptography, Curve25519 is an elliptic curve providing 128-bit security,designed for the Elliptic Curve Diffie-Hellman (ECDH) key agreement scheme. It is one of the fastest ECC curves and is not covered by any known patents.libcurve25519.so boolean org.whispersystems.curve25519.NativeCurve25519Provider.smokeCheck(int) 0x9d782548 func: 0x78b8406288 0x0 iOffset: 4288libcurve25519.so byte[] org.whispersystems.curve25519.NativeCurve25519Provider.generatePrivateKey(byte[]) 0x9d782638 func: 0x78b8405a2c 0x0 iOffset: 3a2clibcurve25519.so byte[] org.whispersystems.curve25519.NativeCurve25519Provider.calculateAgreement(byte[], byte[]) 0x9d7825c0 func: 0x78b8405b68 0x0 iOffset: 3b68Generating keys:retval: [object Object]java.lang.Exception at org.whispersystems.curve25519.NativeCurve25519Provider.generatePublicKey(Native Method) at org.whispersystems.curve25519.OpportunisticCurve25519Provider.generatePublicKey(:750206)I found the plaintext of the sent message (“11”):[MI 10::com.whatsapp]-> byteArray,byte src : [10,2,49,49] protobuf format byteArray,md5str:11java.lang.Exception at X.1FH.A02(Native Method) at com.whatsapp.jobqueue.job.SendE2EMessageJob.writeObject(:271863) Following the process, I found many calls related to encryption classes:java.security.MessageDigestjavax.crypto.Macjavax.crypto.CipherI could directly hook to see the changes in the data flow, at this point, I had a certain understanding of the encryption mode.The client and server use an encryption method AES-256-GCM, with a different IV for each packet. If the data packet sent is private message content,then the private message content is encrypted at a second layer (aes-256-cbc), this layer of data uses the public key of the other party to generate the key through DH,so only the receiving party can decrypt it, and the key for encrypting each private message content is also different.Each time the app is opened, a new TCP connection is initiated (even after logging in with a verification code),at this point, a pair of keys must be initialized, and the public key will be included in the first sending packet after the connection is established, sent to the server.Here, several other keys will also be used (one’s identity_key, a session token key similar to Douyin’s session token, and the server’s public key), these keys are combined through calculateAgreementand HKDF expansion to obtain intermediate data and keys, including those used to encrypt the subsequent data.The first sending packet contains mobile environment information:
After this data verification passes, the connection is established normally, and private messages can be sent.As mentioned earlier, the encryption of private message content is actually another encryption mode; this data is used for interaction between the sender and receiver, and the server cannot decrypt it; it only forwards the encrypted data.The encryption of private message content is aes-256-cbc, which will use a message key (Message Key), an 80-byte value used to encrypt the message content.32 bytes are used for the AES-256 key, 32 bytes for the HMAC-SHA256 key, and 16 bytes for the IV.The HMAC-SHA256 key is used to calculate the message authentication code for the result of the aes encryption of the private message content, taking only the first 8 bytes of the result.The calculation of this message key (Message Key) involves a ratchet transformation; after encrypting a message, the next message’s encryption uses a new Message Key calculated through a combination algorithm of sha256.Sending private messages involves a session concept; both parties need to establish a session for end-to-end communication, which means constructing agreed keys. After establishing the session, both parties can save the relevant environment parameters and directly calculate the Message Key to encrypt and decrypt messages based on the current message sequence.Even if the app is reopened later, the encryption and decryption of private message content can continue based on the previous session without needing to re-establish the session.Therefore, when I first started analyzing, to simplify, I analyzed based on the app session establishment, allowing me to hook the current message’s Message Key and calculate the Message Key for a specified message sequence to send encrypted information directly.Then I began to implement session establishment; this process can refer to the white paper:The session initiator requests the recipient’s public identity key (public Identity Key), signed pre-shared public key (public Signed Pre Key), and a one-time pre-shared key (One-Time Pre Key).The server returns the requested public keys. The one-time pre-shared key (One-Time Pre Key) is used only once, so it will be deleted from the server after the request is completed. If the one-time pre-shared key (One-Time Pre Key) is used up and not replenished, it will return empty.The initiator stores the recipient’s identity key (Identity Key) as Irecipient, the signed pre-shared key (Signed Pre Key) as Srecipient, and the one-time pre-shared key (One-Time Pre Key) as Orecipient.The initiator generates a temporary Curve25519 key pair EinitiatorThe initiator loads their identity key (Identity Key) as IinitiatorThe initiator calculates the master key master_secret = ECDH (Iinitiator, Srecipient) || ECDH (Einitiator, Irecipient) || ECDH (Einitiator, Srecipient) || ECDH (Einitiator, Orecipient). If there is no one-time pre-shared key (One-Time Pre Key), the final ECDH will be ignored.The initiator uses the HKDF algorithm to create a root key (Root Key) and chain keys (Chain Keys) from the master_secret.This process needs to be executed practically to understand better; the algorithms involved are not complex, but the key transformation process is crucial.For session establishment, two pairs of keys (OurBaseKey and ourRatchetKey) also need to be created:
After implementing the process and testing, the recipient could receive the message, but the content was not visible:

I checked online and found that it was said that if the sender changed devices, it could lead to this situation, but that was not the case here.I couldn’t resolve it for a long time and initially thought it was an issue with the decryption algorithm. I repeatedly verified the encryption results against the actual data, and they matched perfectly.Rechecking this data was quite frustrating, but later I calmed down and thought about how to implement this end-to-end encryption. I finally discovered an uncertain point: the oneTimePreKey,the other party could not know which one was used.When requesting, the server directly returned a one-time pre-shared key from the other party, which the receiver had provided in batches stored on the server; using one would delete one. If the session was not successfully established in the end, the server would not synchronize it to the receiver, and I did not see any feedback of this in the data sent to establish the session. For the receiver to decrypt correctly, they must know which one-time pre-shared key is being used for the current session.Later, I thought there should be an ID to identify it. At that time, I indeed found several uncertain field values in the data sent to establish the session, but I did not find a common value between the returned receiver key data and the sent private message data(later I found that it was actually due to different data formats).I couldn’t solve it, so I opened a new line of inquiry and analyzed the PC version, hoping to find this difference and resolve the issue.On the PC, WhatsApp has seven processes, and I first looked for the data transmission points, eventually determining that the following process handles network data, as can be seen from the command line parameters:
Tracking revealed that the PC sends data using websocket, and I directly downloaded the SSL source code, marking the functions:
I found the data transmission points:
During tracking, I found that the received data had no further logical processing; sometimes it requested to clear memory, leading me to guess it might have been sent to another process, and I saw something related to MojoMessages.I checked:Mojom is the latest cross-platform inter-process communication framework from Chromium.The focus was not on this, so I didn’t look into it in detail.Finally, I discovered that this process only handles the network layer, while the specific private message logic is in another process:
After that, I followed the call to trace the encryption process. Once I understood it, I could directly use Frida to hook PC data (injecting a DLL also works):

Finally, after verifying the data, I confirmed that there was an ID to mark the shared key:

After perfecting this, I tested sending again, and the recipient could see the content:
Template messages can also be sent:

By the way, the first time logging in with a code goes through HTTP mode, and the key generation will also involve encryption.Learning Summary:
-
I learned about the implementation of end-to-end communication and gained a certain understanding of this encryption mode.
-
I became familiar with the application of Curve25519.

Kanxue ID: xwtwho
https://bbs.pediy.com/user-home-44250.htm
*This article is original by xwtwho from Kanxue Forum, please indicate the source when reprinting from the Kanxue community.

# Previous Recommendations
1. Analysis of DexClassLoader loading process in Android 4.4 and 8.0 to find unpacking points
2. Reverse engineering: Solving the issue of TikTok switching between front and rear virtual cameras
3. Analysis of the new glibc IO exploitation technique in House of Cat & Detailed explanation of the 6th Strong Network Cup House of Cat
4. Attempt to exploit vulnerabilities in a portable Wi-Fi device
5. [Security Operations] Simulating the construction of a small enterprise intranet
6. Practical exploration of CoAP protocol vulnerabilities in a certain device


Share it

Like it

Watch it

Click “Read the original text” to learn more!