JavaScript Deobfuscation — Reading Obfuscated Code and Decoding Payloads (HTB CWES)
Built from the JavaScript Deobfuscation module of the HTB Academy Web Penetration Tester path (HTB CWES). Revision notes: the workflow and every command kept, condensed. Labs and lessons are on HTB Academy.
Obfuscated JavaScript shows up everywhere — malware droppers, client-side "security", hidden functionality. The job: find it, make it readable, understand it, and replicate what it does. It's a small module but a pure skills one.
1. Finding the source
All front-end code ships to the client, so you can always read it. HTML is structure, CSS is style, JavaScript is behaviour — each can be internal (inline) or external (linked):
<style> … </style> <!-- internal CSS -->
<link rel="stylesheet" href="style.css"> <!-- external CSS -->
<script> … </script> <!-- internal JS -->
<script src="secret.js"></script> <!-- external JS -->
View the page source with Ctrl+U; always read HTML comments (devs leave credentials and notes there). Click through to linked .js files. When a script looks like line-noise, that's obfuscation.
2. Recognising obfuscation
Obfuscation keeps code working but makes it unreadable (often slower). It's used to protect IP, to (wrongly) hide client-side "auth/encryption", and — most commonly — to evade IDS/AV in malware. Because JavaScript runs client-side in cleartext, it's the prime target. Forms you'll meet:
| Technique | Signature |
|---|---|
| Minification | Whole script on one line (.min.js). Readable-ish, just compressed. |
| Packing | eval(function(p,a,c,k,e,d){…}) — the six-arg function is the tell. Strings often still visible. |
| obfuscator.io | var _0x1ec6=[…] arrays, hex names, optional Base64 string encoding — strings hidden. |
| JSFuck | Only []()!+ characters. Runs, but very slow. |
| JJEncode / AAEncode | Symbol soup; slow — used to bypass filters. |
3. Deobfuscating
- Beautify (undo minification): browser debugger — Firefox
Ctrl+Shift+Z, open the script, click{ }Pretty Print — or Prettier / Beautifier. - Deobfuscate (undo packing/encoding): beautifying isn't enough for packed code. Use a tool like UnPacker (matthewfl). For packed code, you can also grab the
returnvalue andconsole.logit instead ofeval-ing it. - Reverse engineer by hand when custom obfuscation defeats the tools.
A packed snippet unpacks to readable logic like:
function generateSerial() {
var xhr = new XMLHttpRequest;
var url = "/serial.php";
xhr.open("POST", url, true);
xhr.send(null);
}
4. Analysing and replicating
Read it line by line; Google unfamiliar APIs (XMLHttpRequest = JS web requests, xhr.open/xhr.send = build/send an HTTP request). The function above just POSTs to /serial.php with no data. Unused/hidden functionality like this is worth poking — unreleased features tend to be buggy. Replicate it with cURL:
curl http://TARGET/ # read the page
curl -s http://TARGET/ -X POST # empty POST (-s = quiet)
curl -s http://TARGET/serial.php -X POST -d "param1=sample" # POST with data
5. Decoding encoded strings
Obfuscated code hides data in encoded blobs decoded at runtime. The three you must recognise and reverse:
Base64 — alphanumeric + + /, length a multiple of 4, = padding is the giveaway:
echo "text" | base64 # encode
echo "dGV4dAo=" | base64 -d # decode
Hex — only 0-9 a-f; each char → its ASCII hex (a=61…). (man ascii for the table.)
echo "text" | xxd -p # encode
echo "74657874" | xxd -p -r # decode
ROT13 / Caesar — letters shifted (13 by default); http://www → uggc://jjj, still patterned:
echo "text" | tr 'A-Za-z' 'N-ZA-Mn-za-m' # encode AND decode (symmetric)
Unsure which encoding? Feed it to a Cipher Identifier (boxentriq). Note: encryption (encoding with a key) can't be reversed without the key — if the key isn't in the script, the tools stop here.
6. What to carry into the CWES exam
Ctrl+Ufirst, read comments, follow<script src>. The lead is often sitting in the source.- Name the obfuscation by its signature —
(p,a,c,k,e,d)= packer,_0x…arrays = obfuscator.io,[]()!+= JSFuck — then pick beautify → deobfuscate → UnPacker. - Translate JS into HTTP. Most interesting obfuscated code ends in an
XMLHttpRequest/fetch; replicate it withcurl -X … -d …to hit the endpoint directly. - Spot and reverse base64/hex/rot13 on sight — padding
=,0-9a-f, shifted-but-patterned text.
Cheatsheet — JavaScript Deobfuscation
Find & beautify
Ctrl+U view page source (read HTML comments!)
<script src="x.js"> locate external JS
Firefox Ctrl+Shift+Z debugger → click script → { } Pretty Print
Prettier / Beautifier online beautify
UnPacker (matthewfl) deobfuscate packed code
Obfuscation signatures
.min.js minified (one line)
eval(function(p,a,c,k,e,d){…}) packer
var _0x1ec6=[…] obfuscator.io
only []()!+ JSFuck | symbol soup = JJEncode/AAEncode
Replicate with cURL
curl http://TARGET/
curl -s http://TARGET/ -X POST
curl -s http://TARGET/endpoint.php -X POST -d "param1=sample"
Encode / decode
echo "text" | base64 ; echo "…=" | base64 -d # base64
echo "text" | xxd -p ; echo "…" | xxd -p -r # hex (man ascii)
echo "text" | tr 'A-Za-z' 'N-ZA-Mn-za-m' # rot13 (symmetric)
# identify unknown encodings: boxentriq Cipher Identifier
Built from HTB Academy's JavaScript Deobfuscation module — labs, lessons and the CWES exam are on HTB Academy.