unobpx

package module
v0.0.0-...-81e07cf Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 23, 2026 License: MIT Imports: 8 Imported by: 0

README

unobpx

See through PerimeterX. Every layer, decoded.

PerimeterX (now HUMAN Security) wraps its client-server protocol in two layers of obfuscation — XOR and shuffle-interleave. Some PX-protected sites also bundle Snare (by iovation/TransUnion), a separate fraud detection product served through PX's first-party /si/ path. This tool strips all of them.

Open your browser devtools, capture PX traffic, and feed it to unobpx. Out comes the raw protocol: session commands, fingerprint JSON, encrypted telemetry — all in plaintext.

What It Decodes

Layer What You See What's Inside
OB responses Base64 blob in the ob JSON field Session IDs, cookies, PoW challenges, config, timestamps
Sensor payloads Garbled payload POST parameter Full browser fingerprint JSON (every field PX collects)
Snare telemetry KAUHEVKF... encrypted blob WebGL, fonts, screen, timing, behavioral data (via snr.js, not PX)

Install

go install github.com/sardanioss/unobpx/cmd/unobpx@latest

Or as a library:

go get github.com/sardanioss/unobpx

Quick Start

# Derive the XOR key from any PX tag
unobpx xorkey "IUMUAGcoCHQlTA=="
# Tag:     IUMUAGcoCHQlTA==
# XOR Key: 62 (0x3e)

# Decode an OB response — auto-identifies all command roles
unobpx ob "<base64_ob_string>" --tag "IUMUAGcoCHQlTA=="

# Decode a sensor payload (uuid and sts from the same POST request)
unobpx sensor "<encoded_payload>" "<uuid>" "<sts>"

# Decrypt Snare (snr.js) encrypted telemetry
unobpx obs "KAUHEVKF<base64_data>"

CLI

unobpx ob — Decode Collector Responses

The PX collector replies with an ob field containing Base64 + XOR encoded commands. This decodes it and auto-maps every command to its role — session IDs, visitor IDs, cookies, PoW challenges, timestamps — regardless of which PX tag version generated them.

# Using a known XOR key
unobpx ob "SGVsbG8gV29ybGQ=" 66

# Or let it derive the key from the tag
unobpx ob "SGVsbG8gV29ybGQ=" --tag "IUMUAGcoCHQlTA=="

PX rotates command labels with every tag version. unobpx doesn't care — it identifies commands by their value patterns, not their labels.

unobpx sensor — Decode Sensor Payloads

The payload POST parameter in every sensor request carries the browser fingerprint. It's obfuscated with XOR, Base64, and a shuffle-interleave keyed by the UUID and server timestamp.

unobpx sensor "<encoded_payload>" "<uuid>" "<sts>"

All three values are visible as separate POST parameters in the same request. Output is the raw fingerprint JSON.

unobpx obs — Decrypt Snare Telemetry

Some PX-protected sites bundle Snare (snr.js), a separate fraud detection product by iovation/TransUnion. Snare payloads are AES-256-GCM encrypted with a key embedded in snr.js. This decrypts them.

unobpx obs "KAUHEVKF<base64_data>"
unobpx xorkey — Tag to XOR Key

Every PX tag string deterministically maps to an XOR key. The hash function (from init.js):

e = 0
for each char in tag:
    e = (31 * e + charCode) % 2147483647
key = ((e % 900) + 100) % 128
unobpx xorkey "Zj4TeyhNFxB5"
# Tag:     Zj4TeyhNFxB5
# XOR Key: 66 (0x42)

Library Usage

import "github.com/sardanioss/unobpx"

// Compute XOR key from a PX tag
xorKey := unobpx.ComputeOBXORKey("IUMUAGcoCHQlTA==")

// Decode an OB response
decoded := unobpx.DecodeOB(obBase64, xorKey)

// Parse into structured commands
cmds := unobpx.ParseCommands(decoded)

// Auto-identify command roles across any tag version
roles := unobpx.AutoMapCommands(decoded)
fmt.Println("SID label:", roles[unobpx.RoleSID])
fmt.Println("VID label:", roles[unobpx.RoleVID])

// Extract cookies
cookies := unobpx.ExtractCookies(decoded)

// Decode a sensor payload
json, _ := unobpx.DecodeSensor(encoded, uuid, sts)

// Decrypt Snare (snr.js) telemetry
plain, _ := unobpx.DecryptOBS(wireData)

Protocol Reference

OB Response Wire Format
wire = base64(XOR(plaintext, key))

Decoded text uses ~~~~ as command separator and | as field separator. Each command starts with a binary-like label (e.g., oo1o11) followed by parameters.

OB Command Roles
Role Pattern What It Is
sid 1 UUID field Session ID
vid UUID + 31536000 + false Visitor ID (1-year TTL)
cts UUID + false/true CTS token
cts_num 18-22 digit number CTS numeric token
cs 64-char hex Session hash
timestamp 12-14 digit number Server timestamp (epoch ms)
cls 1-6 digit number CLS value
token 15-25 char alphanumeric Session token
pow 5 fields, field[4] is 64-char hex Proof-of-work challenge
nonces 5 fields, long hex in [1],[2] Validation nonces
callback 4+ fields, UUID in [1] Callback data
cookie 3+ fields, starts with _px Set-Cookie instruction
config Contains bsco: Runtime configuration
cs_mode cu or cr Collector mode
solve_result 0 or -1 Challenge result
Sensor Payload Encoding
  1. XOR raw JSON with key 50
  2. Base64 encode
  3. Derive shuffle key: base64(STS) XOR'd with 10
  4. Compute deterministic insertion indices from shuffle key + payload length + UUID
  5. Interleave shuffle key characters at computed positions

The UUID and STS are sent as separate POST parameters alongside the payload.

Snare Encryption

Snare (snr.js) is a separate fraud detection product by iovation/TransUnion, not part of PX's core bot detection. Some PX-protected sites bundle it through PX's first-party /si/ path. The encrypted payloads are sent to /si/<token>/obs.

  • Algorithm: AES-256-GCM
  • Key: abC3UuT0Yte5FBGN2F6cQu0pegMgCMpr (32 bytes, hardcoded in snr.js)
  • Wire format: "KAUHEVKF" + base64(nonce[12] + ciphertext + GCM_tag[16])

Full PX Research & Services

This is the decode/analysis side of a much deeper PerimeterX research project.

If you're interested in the complete research, full protocol implementation, or commercial API access — reach out:

License

MIT

Documentation

Overview

Package unobpx decodes PerimeterX (HUMAN Security) protocol data.

PerimeterX uses multiple layers of obfuscation in its client-server communication. This package provides tools to decode and inspect that traffic for security research and analysis.

It handles three protocol layers:

  • OB responses: base64 + XOR encoded server responses from the PX collector
  • Sensor payloads: XOR + base64 + shuffle-interleaved sensor POST data
  • Snare payloads: AES-256-GCM encrypted telemetry from snr.js (iovation/TransUnion, not PX)

This package is decode/decrypt only — for traffic analysis and research.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func AutoMapCommands

func AutoMapCommands(decoded string) map[string]string

AutoMapCommands identifies OB command roles by analyzing value patterns.

PX rotates command-type labels with each tag version, but the value formats remain stable. This function uses heuristic pattern matching to identify what each command does, regardless of its label.

Returns a map of CommandRole -> command label string.

Recognized patterns:

  • UUID (8-4-4-4-12 hex): SID (1 field), VID (3 fields with TTL), CTS (2 fields)
  • 64-char hex: session hash
  • 18-22 digits: CTS numeric token
  • 12-14 digits: server timestamp
  • 1-6 digits: CLS value
  • 15-25 char lowercase alphanumeric: session token
  • Starts with "_px": Set-Cookie instruction
  • "cu"/"cr": collector mode
  • "0"/"-1": challenge result

func ComputeOBXORKey

func ComputeOBXORKey(tag string) byte

ComputeOBXORKey derives the OB XOR decryption key from a PX tag string.

This replicates init.js function Gf():

e = (31 * e + charCode) % 2147483647   for each character
key = ((e % 900) + 100) % 128

The tag string is visible in the init.js source and is also sent as the "tag" POST parameter in every sensor request.

func DecodeOB

func DecodeOB(ob string, xorKey byte) string

DecodeOB decodes a PX collector "ob" response field.

The OB wire format is: base64(XOR(plaintext, key)). The XOR key is derived from the PX tag string — use ComputeOBXORKey to compute it from any tag, or extract it from init.js.

func DecodeSensor

func DecodeSensor(encoded, uuid, sts string) (string, error)

DecodeSensor decodes a PX sensor payload captured from network traffic.

PX sensor payloads (the "payload" POST parameter) are obfuscated with:

  1. XOR each byte of the raw JSON with key 50
  2. Base64 encode
  3. Derive a shuffle key from STS (server timestamp string)
  4. Interleave shuffle key characters at computed positions

To decode, you need the encoded payload string plus the UUID and STS values, both visible as separate POST parameters in the same request.

Returns the decoded JSON string.

func DecryptOBS

func DecryptOBS(wireData string) (string, error)

DecryptOBS decrypts a Snare (snr.js) encrypted payload.

Snare payloads carry browser telemetry (fingerprints, timing, behavior data) and are sent to the /si/<token>/obs endpoint. The wire format is:

"KAUHEVKF" + base64(nonce[12] + ciphertext + GCM_tag[16])

The returned string is the decrypted JSON containing the telemetry data.

func ExtractCookies

func ExtractCookies(decoded string) map[string]string

ExtractCookies pulls _px* cookie name=value pairs from decoded OB text.

Cookie commands have the format: <label>|<name>|<maxAge>|<value>|<secure>|<maxAge2> Only cookies with names starting with "_px" are returned.

func OBSAESKey

func OBSAESKey() []byte

OBSAESKey returns the hard-coded AES-256-GCM key from Snare (snr.js). This is provided for reference and external tooling.

Types

type CommandRole

type CommandRole = string

CommandRole identifies the semantic role of an OB command, independent of the binary label which rotates across PX tag versions.

const (
	RoleSID         CommandRole = "sid"          // Session ID (single UUID)
	RoleVID         CommandRole = "vid"          // Visitor ID (UUID + TTL 31536000 + "false")
	RoleCTS         CommandRole = "cts"          // CTS token (UUID + "false"/"true")
	RoleCTSNum      CommandRole = "cts_num"      // CTS numeric token (18-22 digit number)
	RoleCS          CommandRole = "cs"           // Session hash (64-char hex)
	RoleTimestamp   CommandRole = "timestamp"    // Server timestamp (12-14 digit epoch ms)
	RoleCLS         CommandRole = "cls"          // Server CLS value (1-6 digit number)
	RoleToken       CommandRole = "token"        // Session token (15-25 char alphanumeric)
	RolePoW         CommandRole = "pow"          // Proof-of-work challenge (5 fields, field[4] is 64-char hex)
	RoleNonces      CommandRole = "nonces"       // Validation nonces (5 fields, long hex in [1] and [2])
	RoleCallback    CommandRole = "callback"     // Callback data (4+ fields, UUID in field[1])
	RoleCookie      CommandRole = "cookie"       // Set-Cookie instruction (_px* cookie data)
	RoleConfig      CommandRole = "config"       // Runtime config string (contains "bsco:")
	RoleCSMode      CommandRole = "cs_mode"      // Collector mode ("cu" = challenge, "cr" = clean)
	RoleSolveResult CommandRole = "solve_result" // Challenge result ("0" = accepted, "-1" = rejected)
)

Known OB command roles. These are stable across PX versions even though the command labels (binary strings) change with each tag rotation.

type Commands

type Commands map[string][]string

Commands maps command-type labels to their parameter values. Labels are binary-like strings (e.g., "oo1o11", "o1111o") that vary across PX tag versions. Use AutoMapCommands to identify roles by value patterns instead of relying on fixed labels.

func ParseCommands

func ParseCommands(decoded string) Commands

ParseCommands splits decoded OB text into a command map.

OB format uses "~~~~" as command separator and "|" as field separator. The first field of each command is the type label; remaining fields are parameters.

Example decoded OB:

oo1o11|_px3|172800|<cookie_value>|false|500~~~~o1111o|<uuid>~~~~ooo11o|cu

Directories

Path Synopsis
cmd
unobpx command
unobpx is a CLI tool for decoding PerimeterX (HUMAN Security) protocol data.
unobpx is a CLI tool for decoding PerimeterX (HUMAN Security) protocol data.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL