- A short excursion into WeChat;
- About the platform, the version of the application, the utilities used and the decryption of the executable file;
- • About two protocols (old one and new one);
- About serialization of objects;
- Used cryptography and key exchange;
- About headers and hash-functions;
- About the exposures found.
This research was conducted 5 years ago, but published now for ethical reasons.
WeChat messenger from the Chinese company Tencent
WeChat is the second most popular messenger in the world. The official data on the number of users is very difficult to find, but we can make an approximate estimate.
We are talking about 800 million users around the world, 90% of which are from China.
Almost every smartphone owner uses WeChat in China (Chinese self-name -Weixin), since this is not only a messenger in its traditional meaning, but a whole system including a mobile wallet, built-in browser, online store, etc. All state institutions of China are represented there. You can pay a receipt or make an appointment with a doctor with this messenger.
The urgent task was the integration of CRM systems of customers, that are working in China actively, with WeChat. This was promoted by the wide spread of WeChat in China, as well as the lack of an official API. SMS-informing in China is expensive and, most importantly, unstable, besides, there is no «read» status here. The customer, using the API, will be able to notify WeChat users (signed to receive the information from the customer's number, I’ll tell more about it below) through his CRM system about the delivery of goods, new orders and other service information.
The investigation of the protocol
It was decided to study the messenger «from within», to understand the code of the 32-bit version of the messenger for iOS. We have an old, beat-up iPhone 4S with iOS version 7.2.1.
Use Burp Suite Free Editionas MITM.
Download the application and with the use of the wonderful utility dumpdecrypted decrypt the executable file.
At the time of the reverse-engineering WeChat had 6.3.13 version.
Now it's time to copy the file from the device, disassemble it in IDA, and we can start.
We’ll examine the key exchange algorithm with the example of registration.
Launch the application and see the offer to enter the phone number for registration.
Enter the phone number and see the HTTP request in MITM to the address hkshort.weixin.qq.com/bindopmobileforreg.
The body of the request consists of:
- Heading (yellow color);
- Encrypted data (blue color).
After a very long static analysis of the code it was possible to find out that the client communicates with the server through serialized objects. Objects are serialized using the Protocol Buffers library.
The following data is transmitted with the first message:
- Phone number ;
- Language of the phone system;
- Device ID;
- Version of the client;
- КThe key for decrypting the answer( random 16 bytes);
- Other data (that aren’t very interesting for us).
The message is serialized and encrypted with the public key of the server using the RSA algorithm. The server response is decrypted with the AES key transmitted in the request. The answer says that either everything is OK or an error is indicated.
Receive SMS and enter the code. The same request is generated, but with the code from the SMS, and we get the so-called ticket in the response. Now when we have a ticket, we can send a registration request. Enter the name and click OK.
Key exchange happens according to the Diffie-Hellman algorithm using an elliptic curve over the final field «secp224r1». A private and public key is generated, and a request is sent to hkshort.weixin.qq.com/newreg. The server generates its own keys, and also gives us so-called CryptUin and ServerID, we'll talk about both later. The server sends us its public key and session key in response.
Now we have the server public key, and we calculate the shared key, with the help of it we decrypt the session key. From this point the client-server communication is performed using a symmetric AES algorithm with a key length of 128 bits.
In general, there is nothing supernatural about the connection and key exchange algorithm. To encrypt data correctly and to exchange keys — this is half the task, it is also necessary to make a correct heading for each message. Even if you serialize the data and encrypt it correctly, the server will send the response with an error because of the incorrect header. The title looks like this:
bfa65f16050520252cb6b770021001754cc8fd5e57e085457800fb0242420001aeb890f40b010a0080
Now let's talk about each field:
1. The protocol identifier. Each packet starts with this byte.
2. Flags. They store information about the length of SrvID, the length of the header itself and the compression of the original message.
The compression flag is set to 0b10 if the message is not compressed, otherwise it is set to 0b01.
3. The version of the application. No comments.
4. CryptUin. After passing the registration, each account is assigned with a unique identifier of four bytes.
5. SrvID. It’s the ID of the current session. It changes with each new connection.
6. uiCgi. It’s the command code. Each command has its own uiCgi and url. For example, uiCgi is 0x91 for the bindopmobileforreg command, and for newreg — 0x7e. Most numbers are packed using the following algorithm:
private static void Write7BitEncodedInt(BinaryWriter store, int value)
{
Debug.Assert(store != null);
// Write out an int 7 bits at a time. The high bit of the byte,
// when on, tells reader to continue reading more bytes.
uint v = (uint)value; // support negative numbers
while (v >= 0x80)
{
store.Write((byte)(v | 0x80));
v >>= 7;
}
store.Write((byte)v);
}
In this example, uiCgi equals 0x17b, and fb02 — in its packed form.
7. The length of the original message. The length of the serialized data. The number is also packed, but it remains unchanged since it is less than 0x80.
8. The length of the compressed message. The compression was not performed, so it does not differ from the previous one.
9. The flag.
10. The hash.
It is calculated as following:
hash1 = md5(cryptUin.shareKey);
hash2 = md5( strlen(data).shareKey.hash1.data)
resultHash = adler32(hash2)
sharedKey is the shared key obtained from the handshake.
The hash is also packed.
11.Flags. The meaning of these flags remained a mystery, but they are static, so there was no point in studying them separately.
A different protocol is used now, which we will write about in next publications. In fact, it is a wrapper for the things above. The old protocol is still supported — you need to reset the MmtlsCtrlFlag flag in the debugger for it.
The protection against spam.
The user can enable the «friendship confirmation» option to protect against spam. In this case, you can write a message to him only after he confirms that you are friends. A friend confirmation request may contain a welcoming message.
You can’t send many welcoming messages. After sending fifteen requests, all other messages stop sending and queue. The sixteenth request will go only when someone from the previous fifteen will add you as a friend. But the user doesn’t know this, and the application interface doesn’t indicate it either. Figuring this thing out was possible with the help of traffic analysis and experiments.
An interesting exposure was also discovered during the work. There is an opportunity to find the user by phone number in the application. The server either responds that there is no such user, or sends back the information about him (name, gender, city, photo, etc.). But if you send these requests too often, the server responds with the message «Too many attempts. Try again later». This can be used, since if the user doesn’t exist, the server will always report this, and if the answer is «Too many attempts. Try again later» – this means that the user exists. Using this, you can collect the user base. By the way, the very «interesting» users were detected, who use WeChat, but they can’t be found in the usual way, and it's even impossible to send them an invitation, most likely they are «special» people of China. Even if you request registration for an «interesting» number, the server will inform you that this user is already registered and proposes to restore the account, but not by SMS.
Using 20,000 streams, it is possible to collect the whole WeChat database of China from one account in a day, there is no account blocking.
I would also like to inform you that there is no end-to-end encryption between users. Messages are encrypted only by a symmetric key, the server decrypts them and re-encrypts them with a symmetric key of the recipient and sends them to the recipient.
This article is introductory, if there is interest from the Habr community, the following publications on WeChat may appear, since any event in WeChat (for example, serialization of objects) is worthy of separate articles.