wenum)A self-scope proof of concept: WhatsApp's media deduplication, combined with its end-to-end media encryption, leaks media lineage to any ordinary, user-level client.
To save bandwidth, a WhatsApp client keeps a content-addressed media dedup cache
and reuses the same mediaKey (the end-to-end key the server never sees) and
directPath whenever it re-sends or forwards media it already holds. Because that
key rides the whole forward chain, two copies sharing a mediaKey descend from one
upload. So a user-level client can read, from its own local storage:
fileSha256 match);mediaKey);mediaKeyTimestamp, the blob's mint time).The same secret that hides content from the server exposes its provenance to the client. Neither the encryption nor the deduplication is faulty on its own; the leak is emergent from their combination.
Full details in the white paper: English · Español.
Requirements: Node.js 20+ (Python 3 optional, only for the tools/ image generators).
git clone https://github.com/alejandrocora/WhatsAppMediaEnumeration.git
cd WhatsAppMediaEnumeration
./install.sh
./wenum login # opens a QR — scan it from WhatsApp to link the session
./wenum detect photo.jpg # was this media ever sent through the account?
./wenum forward --repeats # media reused across several chats (same mediaKey)
./wenum forward --timestamps # every forward: message time vs blob mint time
./wenum forward --repeats --svg --output map.svg # render an image (add --masked to anonymise)
Run ./wenum help for the full list, or ./wenum <command> --help for one
command's own options.
| Command | What it does |
|---|---|
login | Link a WhatsApp account by QR (run once). |
detect FILE… | Was this exact image/document ever sent through my account? (matches fileSha256). |
forward | Forward provenance for reused-mediaKey media (see the sub-modes below). |
dump-history | Export the synced history to a file for offline runs (--history FILE). |
inspect | Dump the full stored record(s) for a media (by file / --filehash / --date). |
check-directpath | Is a media's server blob still reachable? (retention / expiry). |
download | Download + decrypt a stored blob to .data/downloads/. |
scan-hash | Search every local store for a fileSha256. |
window-depth | How far back the synced history reaches (with a per-month histogram). |
dump-media | List every synced media descriptor (filehash / type / size / dimensions). |
forward sub-modes:
forward … | What it shows |
|---|---|
--repeats | Blobs with an identical mediaKey surfacing across ≥N chats (--min-chats N). |
--timestamps | Every forward's message time vs blob mint time; --many keeps only "forwarded many times". |
--stickers | Sticker media grouped by blob (the easiest tracer). |
--map / --svg / --art | Render a blob's chronological chain (JSON / SVG image / console). |
--file PATH | Trace one specific media instead of sweeping everything. |
Selectors that narrow any scan or pin one blob: --filehash, --mediakey,
--from NUMBER, --name SUBSTR, --recent N, --all, --masked, --json.
Self-scope only: drive your own linked session against your own synced history. This is a defensive/forensic proof of concept and a research artifact; do not use it to deanonymize or track other people.
MIT — free to use and modify; please keep attribution to the original author (Alejandro Zapico Lara), including for the white paper.
2 commits
TypeScript
88.5%
Python
10.3%
Shell
1.2%
wenum)A self-scope proof of concept: WhatsApp's media deduplication, combined with its end-to-end media encryption, leaks media lineage to any ordinary, user-level client.
To save bandwidth, a WhatsApp client keeps a content-addressed media dedup cache
and reuses the same mediaKey (the end-to-end key the server never sees) and
directPath whenever it re-sends or forwards media it already holds. Because that
key rides the whole forward chain, two copies sharing a mediaKey descend from one
upload. So a user-level client can read, from its own local storage:
fileSha256 match);mediaKey);mediaKeyTimestamp, the blob's mint time).The same secret that hides content from the server exposes its provenance to the client. Neither the encryption nor the deduplication is faulty on its own; the leak is emergent from their combination.
Full details in the white paper: English · Español.
Requirements: Node.js 20+ (Python 3 optional, only for the tools/ image generators).
git clone https://github.com/alejandrocora/WhatsAppMediaEnumeration.git
cd WhatsAppMediaEnumeration
./install.sh
./wenum login # opens a QR — scan it from WhatsApp to link the session
./wenum detect photo.jpg # was this media ever sent through the account?
./wenum forward --repeats # media reused across several chats (same mediaKey)
./wenum forward --timestamps # every forward: message time vs blob mint time
./wenum forward --repeats --svg --output map.svg # render an image (add --masked to anonymise)
Run ./wenum help for the full list, or ./wenum <command> --help for one
command's own options.
| Command | What it does |
|---|---|
login | Link a WhatsApp account by QR (run once). |
detect FILE… | Was this exact image/document ever sent through my account? (matches fileSha256). |
forward | Forward provenance for reused-mediaKey media (see the sub-modes below). |
dump-history | Export the synced history to a file for offline runs (--history FILE). |
inspect | Dump the full stored record(s) for a media (by file / --filehash / --date). |
check-directpath | Is a media's server blob still reachable? (retention / expiry). |
download | Download + decrypt a stored blob to .data/downloads/. |
scan-hash | Search every local store for a fileSha256. |
window-depth | How far back the synced history reaches (with a per-month histogram). |
dump-media | List every synced media descriptor (filehash / type / size / dimensions). |
forward sub-modes:
forward … | What it shows |
|---|---|
--repeats | Blobs with an identical mediaKey surfacing across ≥N chats (--min-chats N). |
--timestamps | Every forward's message time vs blob mint time; --many keeps only "forwarded many times". |
--stickers | Sticker media grouped by blob (the easiest tracer). |
--map / --svg / --art | Render a blob's chronological chain (JSON / SVG image / console). |
--file PATH | Trace one specific media instead of sweeping everything. |
Selectors that narrow any scan or pin one blob: --filehash, --mediakey,
--from NUMBER, --name SUBSTR, --recent N, --all, --masked, --json.
Self-scope only: drive your own linked session against your own synced history. This is a defensive/forensic proof of concept and a research artifact; do not use it to deanonymize or track other people.
MIT — free to use and modify; please keep attribution to the original author (Alejandro Zapico Lara), including for the white paper.
2 commits
TypeScript
88.5%
Python
10.3%
Shell
1.2%