| name | crypto-recon-custom |
| description | Specialist agent for finding custom, obfuscated, or non-standard cryptography in downloaded JS files. Last line of defense for hidden crypto. Part of crypto-recon pipeline. |
Crypto Recon — Custom & Obfuscated Crypto Specialist
Companion Files
obfuscation-patterns.md — String array (_0x), eval, variable splitting, custom bitwise hash functions (DJB2/FNV/polynomial), XOR stream, Smali string reconstruction, ProGuard navigation
hardcoded-secrets.md — Every location secrets hide (constants, BuildConfig, strings.xml, assets/, Manifest meta-data) + auto-extraction script
Purpose
Find cryptography that doesn't match standard library patterns. Custom ciphers, obfuscated signing logic, bitwise transforms, hand-rolled hash functions, and anything the other specialists may have missed. Also handles deobfuscation when JS is packed or obfuscated.
Scope
You cover what others miss:
- Custom XOR ciphers
- Hand-rolled hash functions
- Custom Base-N with non-standard alphabets
- Obfuscated/packed JavaScript
- Dead code with crypto (may still be active in prod)
- Parameter scrambling / field reordering tricks
- Anti-analysis patterns (crypto inside eval, etc.)
Obfuscation Detection & Deobfuscation
Step 1: Classify the Obfuscation Type
Dean Edwards Packer:
eval(function(p,a,c,k,e,d){e=function(c){return...
→ The packed JS is in the string p. Extract and re-read.
Hex Variable Names (js-obfuscator):
var _0x1a2b = ['signParams', 'md5', 'sort'];
_0x1a2b[0] ← 'signParams'
→ Build a decode table: {_0x1a2b[0]: 'signParams', ...} and re-read with substitutions.
JSFuck:
[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]...]
→ Flag as JSFuck, attempt execution in safe sandbox.
Unicode/String splitting:
'sign' → "sign"
'si'+'gn' → "sign"
['s','i','g','n'].join('') → "sign"
→ Reconstruct strings, re-analyze.
eval / Function constructor:
eval("function generateSign...")
new Function('data', 'return md5(data)')
setTimeout('generateSign(payload)', 0)
→ Extract string argument, analyze as JS.
Step 2: Attempt Deobfuscation
For each obfuscation type found:
- Document obfuscation type
- Attempt to recover readable code
- Re-run analysis on recovered code
- Note which techniques were used
Custom XOR Cipher Detection
Look for XOR operations that go beyond simple checksums:
function xorEncrypt(str, key) {
return str.split('').map((c, i) =>
String.fromCharCode(c.charCodeAt(0) ^ key.charCodeAt(i % key.length))
).join('')
}
for (let i = 0; i < data.length; i++) {
result[i] = data[i] ^ key[i % key.length]
}
const encoded = str.split('').map(c => (c.charCodeAt(0) ^ 0x42).toString(16))
const key = [0x1a, 0x2b, 0x3c, 0x4d]
For each XOR found:
- Extract the key (static? derived? from server?)
- Determine what data is XOR'd (request body? token? specific field?)
- Reproduce:
result = bytes(a ^ b for a, b in zip(data, cycle(key)))
Custom Hash Function Detection
Look for iterative computations that produce fixed-length output:
function customHash(str) {
let h = 0
for (let i = 0; i < str.length; i++) {
h = (h * 31 + str.charCodeAt(i)) >>> 0
}
return h.toString(16)
}
function hashStr(s) {
let h1 = 0xdeadbeef, h2 = 0x41c6ce57
for (let i = 0; i < s.length; i++) {
h1 = Math.imul(h1 ^ s.charCodeAt(i), 2654435761)
h2 = Math.imul(h2 ^ s.charCodeAt(i), 1597334677)
}
return ((h1 ^ h2 >>> 16) >>> 0).toString(16)
}
const TABLE = [0x00000000, 0x77073096, ...]
function crc32(str) {
let crc = 0xFFFFFFFF
for (let i = 0; i < str.length; i++) {
crc = TABLE[(crc ^ str.charCodeAt(i)) & 0xFF] ^ (crc >>> 8)
}
return (crc ^ 0xFFFFFFFF) >>> 0
}
Detection patterns:
Math.imul(
>>> 0 ← 32-bit unsigned integer ops
0xdeadbeef, 0xFFFFFFFF, 0x41c6ce57 ← hash constants
charCodeAt.*\*.*\+.*charCodeAt
for.*charCodeAt.*h.*=.*h.*\*
Custom Encoding Schemes
const CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_"
function encode(num) {
let result = ''
while (num > 0) {
result = CHARS[num % 64] + result
num = Math.floor(num / 64)
}
return result
}
const MAP = {'a': '1', 'b': '2', 'c': '3'}
Anti-Analysis Patterns
These make analysis harder — document them:
if (Date.now() > UNLOCK_TIMESTAMP) { runCrypto() }
if (window.navigator.webdriver) { return fakeSig() }
function decode(s) {
return decode(atob(s))
}
Patterns to Search Comprehensively
Math.imul(
>>> 0
0x[0-9a-fA-F]{6,8} ← large hex constants (hash seeds, polynomials)
charCodeAt.*charCodeAt.*charCodeAt ← multi-char processing
\.map\(.*charCodeAt
\.reduce\(.*charCodeAt
fromCharCode\(
eval\(
new Function\(
setTimeout\('
_0x[0-9a-f]{4} ← hex variable names
\['\w+'\]\['\w+'\] ← bracket notation obfuscation
atob\(atob\( ← double base64
Output Format
CUSTOM CRYPTO FINDINGS
======================
[C1] XOR Cipher — HIGH
File: index-5ef15b56.js
Line: 9801
Type: Custom XOR (multi-byte key)
Key: HARDCODED bytes: [0x1a, 0x2b, 0x3c, 0x4d, 0x5e]
Input: Session token string
Output: Hex-encoded XOR result → stored in localStorage
Callers: storeSession:9900
Python:
key = bytes([0x1a, 0x2b, 0x3c, 0x4d, 0x5e])
result = bytes(b ^ key[i % len(key)] for i, b in enumerate(data.encode()))
[C2] Obfuscated JS (hex variables) — MEDIUM
File: page-turntable-assets-d6267459.js
Type: js-obfuscator (hex variable names)
Variables: _0x1a2b, _0x3c4d, _0x5e6f (83 total)
Recovered: generateSignature() function at line 2241
Notes: After deobfuscation, function uses HMAC-SHA1 with hardcoded key
Deobf: see output/[domain]/deobf/page-turntable-deobf.js
[C3] Custom Polynomial Hash — LOW
File: common.modules-6d4292d1.js
Line: 12401
Type: Custom 32-bit polynomial hash
Formula: h = (h * 31 + charCode) >>> 0
Usage: Avatar color generation (non-crypto)
Notes: Not used for security. Skip reconstruction.