MCPcopy
hub / github.com/sigalor/whatsapp-web-reveng

github.com/sigalor/whatsapp-web-reveng @main sqlite

repository ↗ · DeepWiki ↗
686 symbols 2,162 edges 24 files 1 documented · 0%
README

WhatsApp Web reverse engineered

Introduction

This project intends to provide a complete description and re-implementation of the WhatsApp Web API, which will eventually lead to a custom client. WhatsApp Web internally works using WebSockets; this project does as well.

Trying it out

With Nix

There's no need to install or manage python and node versions, the file shell.nix defines an environment with all the dependencies included to run this project.

There's an .envrc file in root folder that is called automatically when cding (changing directory) to project, if program direnv is installed along with nix you should get an output like this:

>cd ~/dev/whatsapp
Installing node modules
npm WARN prepare removing existing node_modules/ before installation

> fsevents@1.2.11 install /home/rainy/dev/whatsapp/node_modules/fsevents
> node-gyp rebuild

make: Entering directory '/home/rainy/dev/whatsapp/node_modules/fsevents/build'
  SOLINK_MODULE(target) Release/obj.target/.node
  COPY Release/.node
make: Leaving directory '/home/rainy/dev/whatsapp/node_modules/fsevents/build'

> nodemon@1.19.4 postinstall /home/rainy/dev/whatsapp/node_modules/nodemon
> node bin/postinstall || exit 0

added 310 packages in 3.763s
Done.

$$\      $$\ $$\                  $$\
$$ | $\  $$ |$$ |                 $$ |
$$ |$$$\ $$ |$$$$$$$\   $$$$$$\ $$$$$$\    $$$$$$$\  $$$$$$\   $$$$$$\   $$$$$$\
$$ $$ $$\$$ |$$  __$$\  \____$$\\_$$  _|  $$  _____| \____$$\ $$  __$$\ $$  __$$\
$$$$  _$$$$ |$$ |  $$ | $$$$$$$ | $$ |    \$$$$$$\   $$$$$$$ |$$ /  $$ |$$ /  $$ |
$$$  / \$$$ |$$ |  $$ |$$  __$$ | $$ |$$\  \____$$\ $$  __$$ |$$ |  $$ |$$ |  $$ |
$$  /   \$$ |$$ |  $$ |\$$$$$$$ | \$$$$  |$$$$$$$  |\$$$$$$$ |$$$$$$$  |$$$$$$$  |
\__/     \__|\__|  \__| \_______|  \____/ \_______/  \_______|$$  ____/ $$  ____/
                                                              $$ |      $$ |
                                                              $$ |      $$ |
                                                              \__|      \__|
Node v13.13.0
Python 2.7.17

Try running server with: npm start

[nix-shell:~/dev/whatsapp]$ 

If you don't use direnv or just want to manually get into the build environment do:

nix-shell

in the project root

Bare metal

Before you can run the application, make sure that you have the following software installed:

  • Node.js (at least version 8, as the async await syntax is used)
  • Python 2.7 with the following pip packages installed:
  • websocket-client and git+https://github.com/dpallot/simple-websocket-server.git for acting as WebSocket server and client.
  • curve25519-donna and pycrypto for the encryption stuff.
  • pyqrcode for QR code generation.
  • protobuf for reading and writing the binary conversation format.
  • Note: On Windows curve25519-donna requires Microsoft Visual C++ 9.0 and you need to copy stdint.h into C:\Users\YOUR USERNAME\AppData\Local\Programs\Common\Microsoft\Visual C++ for Python\9.0\VC\include.

Before starting the application for the first time, run npm install -f to install all Node and pip install -r requirements.txt for all Python dependencies.

Lastly, to finally launch it, just run npm start on Linux based OS's and npm run win on Windows. Using fancy concurrently and nodemon magic, all three local components will be started after each other and when you edit a file, the changed module will automatically restart to apply the changes.

Reimplementations

JavaScript

A recent addition is a version of the decryption routine translated to in-browser JavaScript. Run node index_jsdemo.js (just needed because browsers don't allow changing HTTP headers for WebSockets), then open client/login-via-js-demo.html as a normal file in any browser. The console output should show decrypted binary messages after scanning the QR code.

adiwajshing created Baileys, a Node library that implements the WhatsApp Web API.

ndunks made a TypeScript reimplementation at WaJs.

Python

p4kl0nc4t created kyros, a Python package that implements the WhatsApp Web API.

Rust

With whatsappweb-rs, wiomoc created a WhatsApp Web client in Rust.

Go

Rhymen created go-whatsapp, a Go package that implements the WhatsApp Web API.

Clojure

vzaramel created whatsappweb-clj, a Clojure library the implements the WhatsApp Web API.

Application architecture

The project is organized in the following way. Note the used ports and make sure that they are not in use elsewhere before starting the application. whatsapp-web-reveng Application architecture

Login and encryption details

WhatsApp Web encrypts the data using several different algorithms. These include AES 256 CBC, Curve25519 as Diffie-Hellman key agreement scheme, HKDF for generating the extended shared secret and HMAC with SHA256.

Starting the WhatsApp Web session happens by just connecting to one of its websocket servers at wss://w[1-8].web.whatsapp.com/ws (wss:// means that the websocket connection is secure; w[1-8] means that any number between 1 and 8 can follow the w). Also make sure that, when establishing the connection, the HTTP header Origin: https://web.whatsapp.com is set, otherwise the connection will be rejected.

Messages

When you send messages to a WhatsApp Web websocket, they need to be in a specific format. It is quite simple and looks like messageTag,JSON, e.g. 1515590796,["data",123]. Note that apparently the message tag can be anything. This application mostly uses the current timestamp as tag, just to be a bit unique. WhatsApp itself often uses message tags like s1, 1234.--0 or something like that. Obviously the message tag may not contain a comma. Additionally, JSON objects are possible as well as payload.

Logging in

To log in at an open websocket, follow these steps:

  1. Generate your own clientId, which needs to be 16 base64-encoded bytes (i.e. 25 characters). This application just uses 16 random bytes, i.e. base64.b64encode(os.urandom(16)) in Python.
  2. Decide for a tag for your message, which is more or less arbitrary (see above). This application uses the current timestamp (in seconds) for that. Remember this tag for later.
  3. The message you send to the websocket looks like this: messageTag,["admin","init",[0,3,2390],["Long browser description","ShortBrowserDesc"],"clientId",true].
    • Obviously, you need to replace messageTag and clientId by the values you chose before
    • The [0,3,2390] part specifies the current WhatsApp Web version. The last value changes frequently. It should be quite backwards-compatible though.
    • "Long browser description" is an arbitrary string that will be shown in the WhatsApp app in the list of registered WhatsApp Web clients after you scan the QR code.
    • "ShortBrowserDesc" has not been observed anywhere yet but is arbitrary as well.
  4. After a few moments, your websocket will receive a message in the specified format with the message tag you chose in step 2. The JSON object of this message has the following attributes:
    • status: should be 200
    • ref: in the application, this is treated as the server ID; important for the QR generation, see below
    • ttl: is 20000, maybe the time after the QR code becomes invalid
    • update: a boolean flag
    • curr: the current WhatsApp Web version, e.g. 0.2.7314
    • time: the timestamp the server responded at, as floating-point milliseconds, e.g. 1515592039037.0

QR code generation

  1. Generate your own private key with Curve25519, e.g. curve25519.Private().
  2. Get the public key from your private key, e.g. privateKey.get_public().
  3. Obtain the string later encoded by the QR code by concatenating the following values with a comma:
    • the server ID, i.e. the ref attribute from step 4
    • the base64-encoded version of your public key, i.e. base64.b64encode(publicKey.serialize())
    • your client ID
  4. Turn this string into an image (e.g. using pyqrcode) and scan it using the WhatsApp app.

Requesting new ref for QR code generation (not implemented)

  1. You can request up to 5 new server refs when previous one expires (ttl).
  2. Do it by sending messageTag,["admin","Conn","reref"].
  3. The server responds with JSON with the following attributes:
    • status: should be 200 (other ones: 304 - reuse previous ref, 429 - new ref denied)
    • ref: new ref
    • ttl: expiration time
  4. Update your QR code with the new ref.

After scanning the QR code

  1. Immediately after you scan the QR code, the websocket receives several important JSON messages that build up the encryption details. These use the specified message format and have a JSON array as payload. Their message tag has no special meaning. The first entry of the JSON array has one of the following values:
    • Conn: array contains JSON object as second element with connection information containing the following attributes and many more:
      • battery: the current battery percentage of your phone
      • browserToken: used to logout without active WebSocket connection (not implemented yet)
      • clientToken: used to resuming closed sessions aka "Remember me" (not implemented yet)
      • phone: an object with detailed information about your phone, e.g. device_manufacturer, device_model, os_build_number, os_version
      • platform: your phone OS, e.g. android
      • pushname: the name of yours you provided WhatsApp
      • secret (remember this!)
      • serverToken: used to resuming closed sessions aka "Remember me" (not implemented yet)
      • wid: your phone number in the chat identification format (see below)
    • Stream: array has four elements in total, so the entire payload is like ["Stream","update",false,"0.2.7314"]
    • Props: array contains JSON object as second element with several properties like imageMaxKBytes (1024), maxParticipants (257), videoMaxEdge (960) and others

Key generation

  1. You are now ready for generating the final encryption keys. Start by decoding the secret from Conn as base64 and storing it as secret. This decoded secret will be 144 bytes long.
  2. Take the first 32 bytes of the decoded secret and use it as a public key. Together with your private key, generate a shared key out of it and call it sharedSecret. The application does it using privateKey.get_shared_key(curve25519.Public(secret[:32]), lambda a:a).
  3. Extend sharedSecret to 80 bytes using HKDF. Call this value sharedSecretExpanded.
  4. This step is optional, it validates the data provided by the server. The method is called HMAC validation. Do it by first calculating HmacSha256(sharedSecretExpanded[32:64], secret[:32] + secret[64:]). Compare this value to secret[32:64]. If they are not equal, abort the login.
  5. You now have the encrypted keys: store sharedSecretExpanded[64:] + secret[64:] as keysEncrypted.
  6. The encrypted keys now need to be decrypted using AES with sharedSecretExpanded[:32] as key, i.e. store AESDecrypt(sharedSecretExpanded[:32], keysEncrypted) as keysDecrypted.
  7. The keysDecrypted variable is 64 bytes long and contains two keys, each 32 bytes long. The encKey is used for decrypting binary messages sent to you by the WhatsApp Web server or encrypting binary messages you send to the server. The macKey is needed to validate the messages sent to you:
    • encKey: keysDecrypted[:32]
    • macKey: keysDecrypted[32:64]

Restoring closed sessions (not implemented)

  1. After sending init command, check whether you have serverToken and clientToken.
  2. If so, send messageTag,["admin","login","clientToken","serverToken","clientId","takeover"]
  3. The server should respond with {"status": 200}. Other statuses:
    • 401: Unpaired from the phone
    • 403: Access denied, check tos field in the JSON: if it equals or greater than 2, you have violated TOS
    • 405: Already logged in
    • 409: Logged in from another location

Resolving challenge (not implemented)

  1. When using old or expired serverToken and clientToken, you will be challenged to confirm that you still have valid encryption keys.
  2. The challenge looks like this messageTag,["Cmd",{"type":"challenge","challenge":"BASE_64_ENCODED_STRING=="}]
  3. Decode challenge string from Base64, sign it with your macKey, encode it back with Base64 and send messageTag,["admin","challenge","BASE_64_ENCODED_STRING==","serverToken","clientId"]
  4. The server should respond with {"status": 200}, but it means nothing.
  5. After solving challenge your connection should be restored.

Logging out

  1. When you have an active WebSocket connection, just send goodbye,,["admin","Conn","disconnect"].
  2. When you don't have such connection (for example your session has been taken over from another location), sign your encKey with your macKey and encode it with Base64. Let's say it is your logoutToken.
  3. Send a POST request to https://dyn.web.whatsapp.com/logout?t=browserToken&m=logoutToken
  4. Remember to always clear your sessions, so sessions list in your phone will not grow big.

Validating and decrypting messages

Now that you have the two keys,

Core symbols most depended-on inside this repo

call
called by 203
client/js/WebSocketClient.js
a
called by 126
client/lib/sjcl/js/sjcl-1.0.7.js
decode
called by 87
backend/whatsapp_defines.py
gf
called by 69
client/lib/curve25519-js/js/axlsign.js
get
called by 55
backend/whatsapp_defines.py
M
called by 35
client/lib/curve25519-js/js/axlsign.js
d
called by 35
client/lib/sjcl/js/sjcl-1.0.7.js
then
called by 27
client/js/UpdaterPromise.js

Shape

Function 581
Method 90
Class 15

Languages

TypeScript85%
Python15%

Modules by API surface

client/lib/lodash/js/lodash.min.js257 symbols
client/lib/moment/js/moment-2.20.1.min.js105 symbols
client/lib/jquery/js/jquery-3.2.1.min.js86 symbols
client/lib/curve25519-js/js/axlsign.js36 symbols
backend/whatsapp_binary_writer.py26 symbols
backend/whatsapp_binary_reader.py26 symbols
backend/whatsapp.py24 symbols
client/lib/qrcode/js/qrcode.min.js13 symbols
client/js/WebSocketClient.js13 symbols
client/lib/jsonTree/jsonTree.js12 symbols
client/lib/jquery-colResizable/js/jquery-colResizable-1.6.min.js11 symbols
backend/whatsapp_defines.py11 symbols

Dependencies from manifests, versioned

acorn6.4.1 · 1×
acorn-walk6.1.1 · 1×
concurrently3.6.1 · 1×
express4.17.1 · 1×
fs0.0.1-security · 1×
lodash4.17.21 · 1×
nodemon1.19.4 · 1×
path0.12.7 · 1×
request2.88.0 · 1×
request-promise-core1.1.2 · 1×
request-promise-native1.0.7 · 1×
sass1.25.0 · 1×

For agents

$ claude mcp add whatsapp-web-reveng \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact