WhatsApp Messages Eavesdropping

whatsapplogo

Logging into WhatsApp on a new device currently works as follows:

  • The phone posts its phone number to a HTTPS URL to request an authentication code,
  • the phone receives an authentication code in the text message,
  • the authentication code is used, again over HTTPS, to obtain a password.

These passwords are quite long and never visible to the user, making them hard to steal from a phone.

Authentication Overview

With the password, the client can log in to a XMPP-like server. For this it uses the custom SASL mechanism WAUTH-1. To log in with the phone number 1234567890, the following happens (this is only an XML representation of the protocol)

  • The client sends:
    <auth xmlns=”urn:ietf:params:xml:ns:xmpp-sasl” user=”1234567890” mechanism=”WAUTH-1″ />
  • The server responds with a some challenge (here ABCDEFGHIJKLMNOP):
    <challenge xmlns=”urn:ietf:params:xml:ns:xmpp-sasl”> ABCDEFGHIJKLMNOP</challenge>
  • To respond to the challenge, the client generates a key using PBKDF2 with the user’s password, the challenge data as the salt and SHA1 as the hash function. It only uses 16 iterations of PBKDF2, which is a little low these days, but we know the password is quite long and random so this does not concern me greatly. 20 bytes from the PBKDF2 result are used as a key for RC4, which is used to encrypt and MAC 1234567890 || ABCDEFGHIJKLMNOP || UNIX timestamp:
    <response xmlns=”urn:ietf:params:xml:ns:xmpp-sasl”>XXXXXXXXXXXX</response>
  • From now on, every message is encrypted and MACed (using HMAC-SHA1) using this key.

Mistake #1: The same encryption key in both directions

How RC4 is supposed to work? RC4 is a PRNG that generates a stream of bytes, which are XORed with the plaintext that is to be encrypted. By XORing the ciphertext with the same stream, the plaintext is recovered.

So,  (A ^ X) ^ (B ^ X) = A ^ B

In other words: if we have two messages encrypted with the same RC4 key, we can cancel the key stream out!

As WhatsApp uses the same key for the incoming and the outgoing RC4 stream, we know that ciphertext byte i on the incoming stream XORed with ciphertext byte i on the outgoing stream will be equal to XORing plaintext byte i on the incoming stream with plaintext byte i of the outgoing stream. By XORing this with either of the plaintext bytes, we can uncover the other byte.

This does not directly reveal all bytes, but in many cases it will work: the first couple of messages exchanged are easy to predict from the information that is sent in plain. All messages still have a common structure, despite the binary encoding: for example, every stanza starts with 0xF8. Even if a byte is not known fully, sometimes it can be known that it must be alphanumeric or an integer in a specific range, which can give some information about the other byte.

Mistake #2: The same HMAC key in both directions

The purpose of a MAC is to authenticate messages. But a MAC by itself is not enough to detect all forms of tampering: an attacker could drop specific messages, swap them or even transmit them back to the sender. TLS counters this by including a sequence number in the plaintext of every message and by using a different key for the HMAC for messages from the server to the client and for messages from the client to the server. WhatsApp does not use such a sequence counter and it reuses the key used for RC4 for the HMAC.

When an attacker retransmits, swaps or drops messages the receiver can not notice that, except for the fact that the decryption of the message is unlikely to be a valid binary-XMPP message. However, by transmitting a message back to the sender at exactly the same place in the RC4 stream as it was originally transmitted will make it decrypt properly.

Conclusion

You should assume that anyone who is able to eavesdrop on your WhatsApp connection is capable of decrypting your messages, given enough effort. You should consider all your previous WhatsApp conversations compromised. There is nothing a WhatsApp user can do about this but except to stop using it until the developers can update it.

There are many pitfalls when developing a streaming encryption protocol. Considering they don’t know how to use a XOR correctly, maybe the WhatsApp developers should stop trying to do this themselves and accept the solution that has been reviewed, updated and fixed for more than 15 years, like TLS.

Android PoC

Decompiling this turned into a pile of garbarge. All strings are obfuscated so it’s very hard to determine which class is doing what. It appears to use some crypto API as references to certain elliptic curves and AES. This is likely Bouncy Castle, which doesn’t mean they actually use ECC or AES.

So on to the Android Emulator, where it turns out it is not following the authentication procedure as described in my previous post. The mechanism is still called WSAUTH-1, but the initial <auth> contains 45 bytes of data now:

<auth user=”1234567890″ xmlns=”urn:ietf:params:xml:ns:xmpp-sasl” mechanism=”WAUTH-1″> XXXXXXXXXXXX</auth>

After this message the server replies with an encrypted response (ABCDEFGHIJKLMNOP)

The first hint that this is still RC4 is that encrypted messages are of different lengths: a block cipher would require padding and would result in messages of exactly the block size.

45 bytes is also exactly the length of the data in the SASL   <response>. So lets try and repeat the same trick:

  • The plaintext of the XXXXXXXXXXXX block is still 1234567890 || nonce || UNIX time (1234567890 was the phone number, wich is still included in plain). We do not know what the nonce is in this case, but we do know 1234567890. So we could try to make a guess for the timestamp, but lets keep it simple.
  • Calculate (X[i] ^ 1[i]) ^ A[i] (ignoring the HMAC bytes) and the result is (in hex):
    F8 0E BE C3 FC 0A 31 33 38 31 32
  • As mentioned earlier, all stanzas in WhatsApp’s binary encoding start with 0xF8. The last 5 bytes are 13812in ASCII, which looks suspiciously much like the 't' attribute of the <success> message (a UNIX timestamp). The 0xBE refers to the string success in WhatsApp’s encoding.

This is very unlikely to be a coincidence, which proves that the latest version of the Android client is vulnerable.

But where did that RC4 key come from?

Apparently the server is able to generate the RC4 key using no nonce or key exchange with the client. The key is also different on each login, as otherwise the first couple of ciphertext bytes in the <auth> would always be the same.

We can suspect that the key only depends on the password, but new password is obtained during every login. The client makes an HTTPS request to WhatsApp before the fake-XMPP connection is made and yowsup-cli has a flag that will send your password to the server, which will give you a new one if the password was correct.

Conclusion

You should assume that anyone who is able to eavesdrop on your WhatsApp connection is capable of decrypting your messages, given enough effort. You should consider all your previous WhatsApp conversations compromised. There is nothing a WhatsApp user can do about this but except to stop using it until the developers can update it.

There are many pitfalls when developing a streaming encryption protocol. Considering they don’t know how to use a XOR correctly, maybe the WhatsApp developers should stop trying to do this themselves and accept the solution that has been reviewed, updated and fixed for more than 15 years, like TLS.

Thanks to : xnyhps’ blog

Leave a comment