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 ¶
- func AutoMapCommands(decoded string) map[string]string
- func ComputeOBXORKey(tag string) byte
- func DecodeOB(ob string, xorKey byte) string
- func DecodeSensor(encoded, uuid, sts string) (string, error)
- func DecryptOBS(wireData string) (string, error)
- func ExtractCookies(decoded string) map[string]string
- func OBSAESKey() []byte
- type CommandRole
- type Commands
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func AutoMapCommands ¶
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 ¶
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 ¶
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 ¶
DecodeSensor decodes a PX sensor payload captured from network traffic.
PX sensor payloads (the "payload" POST parameter) are obfuscated with:
- XOR each byte of the raw JSON with key 50
- Base64 encode
- Derive a shuffle key from STS (server timestamp string)
- 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 ¶
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 ¶
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.
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 ¶
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 ¶
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