JScript "////" web droppers, extracting payloads via regex
How to identify and extract, safely and without execution, payloads marked with “////” in JScript/HTA served via the web with regex, neutralized examples, a Python extractor, and a playbook for hosting environments.
Security notice
100% defensive post. Examples are neutralized (they do not execute payloads). The extractor does not execute code, it only locates and decodes////marked blobs for offline analysis.
Context (why this matters for hosting)
In real incidents, web servers (including shared hosting environments) end up serving malicious HTML/HTA that abuses JScript (Internet Explorer/HTA) with ActiveX. A recurring pattern in droppers is to “hide” the payload in base64/hex blocks visually marked with ////, then reassemble and decode on the client with Replace/Join before doing write-to-disk and exec via ADODB.Stream and WScript.Shell (in HTA, for example).
From a blue team/forensics point of view at a hosting provider, you want to:
- quickly locate these marked blobs,
- extract the content without execution,
- generate IOCs (hashes/domains/filenames) and block further dissemination from the compromised host.
“////” dropper pattern in JScript (web/HTA)
Three common signals (with variations):
1) Visual markers: //// in long strings (base64/hex)
2) Split/Join/Replace for reassembly: e.g., Split(payload,"////") or Replace(payload,"////","")
3) ActiveX (in HTA/IE): new ActiveXObject("ADODB.Stream"), WScript.Shell, Scripting.FileSystemObject
Below is a neutralized example that mimics the pattern (without executing anything dangerous):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
<!-- sample-big-slashes.hta (neutralized) -->
<html>
<head>
<meta http-equiv="x-ua-compatible" content="IE=9" />
<title>Neutralized JScript //// demo</title>
<script language="JScript">
// neutralized: no atob, no ActiveX, no ADODB.Stream, no exec.
// only cleanup of '////' markers and byte counting.
function drpp() {
// Wall of “text” (mix of words and base64) with //// scattered.
// The idea is to look visually noisy, like real droppers.
var p =
"// preconize loeti right eidetik nonchafing jurical ////QUFBQkNEREU\n" +
"// superagiety counterdrain coscuwod doutous ternelate ////RkdISUpLTE0=\n" +
"// autonomous unperishably upsey millennial forfep ////TkpPUFFSU1RV\n" +
"// jersial hyalopiarte dorhawk retmept spearhead ////VldYWVo9PT0=\n" +
"// quinodiation endomesoderm semigelative ////Ly8vLy8vLy8vLy8v\n" +
"// toxichemia thril unsupermanaded decomponible ////QUJDREVGR0hJ\n" +
"// kagn stemy hypothaecary halidon altrose ////SktMTU5PUFFSU1RV\n" +
"// exihibitional counterblow naifly cushy ////VldYWVo7Ozs7Ozs7\n" +
"// osteoglossid syncretistic alviducous tean ////c2FmZS1kZW1vLXN0\n" +
"// snuffing paster instrumentalize coped ////cmluZw==////Ly8v\n" +
"// deading particularist quarten bluland ////U291bmRzLWxpa2UtY29kZQ==\n" +
"// incremental betoil unscrutable eo ////Ly8gYmVuaWduX2Jsb2I6Ci8v\n" +
"// gendarme klendusity patelline ////QUFB////QkJC////Q0ND////\n" +
"// gynephobia priscian butyne strind ////REVF////RkZG////R0dH\n" +
"// postpubic damsel jiffy garlick ////Ly8vLy8vLy8vLy8vLy8vLy8v\n" +
"// cranemortal edanitis naporited ////bG9yZW0tc2VicmEtdmlzdWFs\n" +
"// planula elops palt coquire ////YW5jaG9yLWZpbGxlci0vLy8vLy8=\n" +
"// varioform underread overconsciousness ////YXJjaGl2ZS1waWVjZQ==\n" +
"// gyrocaren hospcatcher plumbaginaceae ////Ly8gLy8gLy8gLy8gLy8g\n" +
"// dorsoscupiae rhumatismal intertwining ////QVBQRU5E////TUVUQURB\n" +
"// dimber chariorscurist clench greenback ////VEFHVy8vLy8vLy8v\n" +
"// dorsoscapular zoe sticksnaph ////UEhQLUpTY3JpcHQtbGlrZQ==\n" +
"// --- visual break --- ////Ly8tLS0tLS0tLS0tLS0tLS0tLS0t\n" +
"// assorted words to drown patterns ////Ly8gZGlzdHJpYnV0ZWQtbWFya2Vy\n" +
"// more filler more filler more ////Ly8gLy8gLy8gLy8gLy8gLy8g\n" +
"// even more filler with //// placed ////Ly8gLy8gLy8gLy8gLy8g\n" +
"// around long stretches that look like base64 ////QUJDREVGR0hJSktMTU5PUFFSU1RV\n" +
"// but everything stays neutralized ////VldYWVo=\n" +
"// ---------------------------------------- ////Ly8vL2VuZA==\n" +
"////QUFBQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaQUFBQ0RFRkdISQ==\n" +
"////SktMTU5PUFFSU1RVVldYWVo7Ozs7Ozs7Ly8vLy8vLy8vLy8vLy8=\n" +
"////c2FmZS1kZW1vLW9ubHktY291bnQtbGVuZ3RoLWhlcmU=\n" +
"////bW9yZS10ZXh0LW1vcmUtcmFuZG9tLXdvcmRzLW1vcmU=\n" +
"////Ly8tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0=\n" +
"// final chunk just to look big ////U3RpbGwtbm9uLWV4ZWM=\n";
// Cleanup: remove '////' and whitespace/newlines (no decoding).
var cleaned = p.replace(/\/\/\/\/+/g, "").replace(/\s+/g, "");
// Neutral report: show size and a small ASCII preview (printable).
var printable = "";
for (var i = 0; i < cleaned.length && printable.length < 96; i++) {
var ch = cleaned.charCodeAt(i);
if (ch >= 32 && ch < 127) printable += cleaned.charAt(i);
else printable += ".";
}
var out = []
out.push("lines: " + p.split(/\n/).length);
out.push("size after cleanup: " + cleaned.length + " bytes");
out.push("ascii preview (96): " + printable);
// Display in the page's <pre id="out">
document.getElementById("out").innerText = out.join("\n");
// Note: in real samples, you'd see:
// var b = atob(cleaned); // (do not do here)
// var s = new ActiveXObject('ADODB.Stream'); // (do not use)
// s.Type = 1; s.Open(); s.Write(b); s.SaveToFile('...'); // (no)
// new ActiveXObject('WScript.Shell').Run('...'); // (no)
}
</script>
</head>
<body onload="drpp()" style="background:#0b0f14;color:#cfe8ff;font-family:Consolas,Menlo,monospace;">
<h3 style="margin:16px 24px;">Neutralized JScript //// visual</h3>
<pre id="out" style="margin:0 24px 24px; padding:16px; border:1px solid #123; background:#0f141c;"></pre>
</body>
</html>
Note: HTML pages with
language="JScript"and HTA (.hta) are the most common variants on the legacy “web side” (IE/Windows). The Linux server may be only serving the malicious artifact — which is why detecting it in the host’s static content is useful.
Safe extraction via regex (no execution)
The idea: scan the file (.html, .hta, .js) for blocks with ////, clean the markers, try to decode as base64/hex, save the bytes to a file without executing anything.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#!/usr/bin/env python3
import re, base64, binascii, sys, pathlib, hashlib
# Usage: python3 safe_extract_slashes.py sample.hta
def find_slash_chunks(text: str):
"""
Capture sequences with //// that are likely fragmented base64/hex.
- Strategy 1: '////' followed by a long run of [A-Za-z0-9+/=]
- Strategy 2: multiple occurrences concatenated
"""
pattern = re.compile(r"(?:\/\/\/\/[A-Za-z0-9+/=\s]{20,}){2,}", re.S)
return [m.group(0) for m in pattern.finditer(text)]
def cleanup_markers(blob: str) -> str:
cleaned = re.sub(r'\/\/\/\/+', '', blob)
cleaned = re.sub(r'\s+', '', cleaned)
return cleaned
def try_decode(s: str) -> bytes | None:
t = s
rem = len(t) % 4
if rem:
t += "=" * (4 - rem)
try:
return base64.b64decode(t, validate=True)
except Exception:
pass
hs = re.sub(r'\s+', '', s)
try:
return bytes.fromhex(hs)
except Exception:
return None
def main():
if len(sys.argv) < 2:
print("Usage: python3 safe_extract_slashes.py <file>")
sys.exit(1)
p = pathlib.Path(sys.argv[1])
text = p.read_text(errors="ignore")
chunks = find_slash_chunks(text)
if not chunks:
print("[!] no ////-style blobs found")
sys.exit(0)
base = p.stem
outdir = p.parent / f"{base}_extracted"
outdir.mkdir(exist_ok=True)
n_hits = 0
for i, raw in enumerate(chunks, 1):
cleaned = cleanup_markers(raw)
data = try_decode(cleaned)
if data is None:
(outdir / f"blob_{i:02d}.txt").write_text(cleaned)
print(f"[-] undecodable blob_{i:02d}.txt (saved for manual review)")
continue
sha256 = hashlib.sha256(data).hexdigest()
out_path = outdir / f"payload_{i:02d}_{sha256[:12]}.bin"
out_path.write_bytes(data)
print(f"[+] extracted {len(data)} bytes -> {out_path} (sha256:{sha256})")
n_hits += 1
print(f"[=] done. extracted: {n_hits}")
if __name__ == "__main__":
main()
How to use
1
python3 safe_extract_slashes.py sample.hta
Useful regexes (for quick triage)
Find blocks with //// (continuous base64):
1
(?:\/\/\/\/[A-Za-z0-9+/=\s]{20,}){2,}
Find reconstructions (Join/Replace) using ////:
1
(?:Split|Join|Replace)\s*\([\s\S]*?\/\/\/\/[\s\S]*?\)
String concatenation with +:
1
["']\/\/\/\/[A-Za-z0-9+/=\s]{10,}["'](?:\s*\+\s*["'][A-Za-z0-9+/=\s]{10,}["']){1,}
ActiveX heuristic (HTA/IE JScript):
1
ActiveXObject\s*\(\s*["'](?:ADODB\.Stream|WScript\.Shell|Scripting\.FileSystemObject)["']\s*\)
“Marker + ActiveX” combo (strong):
1
\/\/\/\/[\s\S]{0,500}ActiveXObject
Hunting in hosting environments
Even when the final target is Windows/IE, the server hosting the artifact leaves traces. Ideas:
File hunting in the customer’s webroot:
- Suspicious names:
*.hta,*.htmloutside CMS,index_old.html,update_login.htm - Strings:
////,ActiveXObject,JScript.Encode,atob( - mtime differences outside the deploy window
- Suspicious names:
Access logs (Nginx/Apache):
- Spikes in
.hta/.jsdownloads - Strange referrers pointing to
login/orinvoice/
- Spikes in
WAF (mod_security)
- Simple rule (conceptual): block responses that contain repeated
////sequences ANDActiveXObject(low FP chance on modern sites)
- Simple rule (conceptual): block responses that contain repeated
Practical hardening (hosting)
Block .hta on the web server (almost nobody legitimately serves HTA):
Nginx:
1
2
3
4
location ~* \.hta$ {
default_type text/plain;
return 403;
}
Apache:
1
2
3
<FilesMatch "\.hta$">
Require all denied
</FilesMatch>
Sanitize shared webroots
- Disable upload/execution of types not used by the customer
- Periodic scans for
////signatures - Notify the customer when a suspicious artifact is published
“Before and after” (neutralized)
Before: snippet with //// (benign for demonstration)
1
2
3
4
5
var payload =
"////U29tZUJhc2U2NA==" +
"////TW9yZU5vaXNl" +
"////";
var cleaned = payload.replace(/\/\/\/\/+/g, "");
After: extracted bytes (pseudo-output)
1
[+] extracted 21 bytes -> sample_extracted/payload_01_1a2b3c4d5e6f.bin (sha256: 9f2...c1a)
Limitations and false positives
- The
////marker is not exclusive to malware; some devs use it as a visual separator. So, combine it with signals like ActiveX oratob+Replace/Join. - In modern variants, attackers may change the order:
hex → inflate → base64, or apply XOR before base64. The extractor above saves undecodable blobs for manual review.
Closing
The “//// + reassembly + decoding” pattern is practical to hunt for, including in Linux hosting environments that only serve the malicious page. The operational win comes from extracting without execution: you get IOCs quickly and avoid detonating on a sandbox/client.
