Description
What kind of information can an Add/On reach? Is it possible to exfiltrate them without you noticing? Download the browser extension suspicious.zip (password: picoctf) and inspect it to uncover the hidden flag.
Download suspicious.zip and extract it using the password 'picoctf'.
Inspect all the extension files for hidden data.
unzip -P picoctf suspicious.zipls -laSolution
Walk me through it- Step 1Extract the extensionExtract first. You *can*
unzip -pindividual files for grep-only triage, but for an unknown extension you want all files on disk so you can inspect the manifest, follow imports, and see the full directory layout.bash# Extract everything to a working directory:bashunzip -P picoctf suspicious.zip -d suspicious/bashls -la suspicious/bash# Quick triage without extracting (handy on huge archives):bashunzip -P picoctf -p suspicious.zip manifest.json | python3 -m json.toolLearn more
Extension formats are all ZIP archives under the hood. Chrome
.crx= ZIP + a small CRX3 signature header prepended; Firefox.xpi= ZIP, no header; this challenge ships a plain.zipalready. If you ever need to peel the CRX header off, the data starts at the offset where the bytesPKfirst appear (standard ZIP magic).Extract vs grep-the-archive.
unzip -p archive.zip path/to/filestreams a single file to stdout without writing anything to disk - good for one-shot extraction during triage. For real analysis (manifest review, cross-file string search, deobfuscation), extract everything: tools and editors work better against a real directory tree.Every extension contains:
manifest.json(configuration + permissions), background scripts / service workers, content scripts (injected into web pages), and optional popup HTML/CSS/JS. Malicious extensions slip past the Chrome Web Store / Firefox AMO review by being benign on submission and pulling malicious payloads later, or by hiding behavior behind obfuscation. - Step 2Inspect manifest.jsonRead the manifest. It tells you exactly what the extension can do, which JS files run in which context, and where the flag is most likely embedded.python
python3 -m json.tool suspicious/manifest.jsonbashgrep -iE 'picoCTF|flag|secret' suspicious/manifest.jsonLearn more
What each suspicious permission actually grants:
"<all_urls>"- the extension can inject content scripts into every website you visit and make cross-origin HTTP requests to any server. Effectively converts the extension into a universal man-in-the-browser."webRequestBlocking"- lets the extension synchronously intercept, modify, redirect, or cancel any outgoing HTTP request before it leaves the browser. Adblockers use this; so do credential-stealing exfiltration scripts."cookies"- read and write cookies for any domain (paired with host permissions). Game over for session cookies on every site you're logged into."nativeMessaging"- communicate with a co-installed native binary outside the browser sandbox. Real malware uses this to hand control to a persistence-installing helper."tabs"- read URLs and titles of every open tab."storage"- on its own, benign. Combined with the above, it's where exfiltrated data is staged.
A legitimate extension needs a small subset of these. Asking for several of them at once - particularly
<all_urls>+cookies+webRequestBlocking- is the classic credential-stealer permission stack. - Step 3Search all JS files for the flag and encoded dataGrep raw first (cheap, often just works), then sweep for each obfuscation form by signature. Also flag any exfiltration call (fetch/XHR/sendBeacon) - those point at where the encoded payload is built.bash
# Raw plaintext first - sometimes the flag is just sitting there:bashgrep -rn 'picoCTF' suspicious/bash# Base64 candidates: 20+ alphabet chars, optional = padding:bashgrep -rnE '[A-Za-z0-9+/]{20,}={0,2}' suspicious/ --include='*.js'bash# String.fromCharCode arrays: bracket + comma-separated char codes:bashgrep -rnE '\[\d{2,3}(,\d{2,3})+\]' suspicious/ --include='*.js'bash# Hex escape sequences inside JS string literals:bashgrep -rnE '\\x[0-9a-f]{2}' suspicious/ --include='*.js'bash# Exfiltration sinks - the encoded payload is built nearby:bashgrep -rnE '(fetch|XMLHttpRequest|sendBeacon|navigator\.sendBeacon)' suspicious/ --include='*.js'Learn more
Concrete signatures by obfuscation type:
- Base64:
[A-Za-z0-9+/]{20,}={0,2}. Real base64 strings of meaningful length plus optional=padding.cGljb0NURntoZWxsb30=decodes topicoCTF{hello}. - Charcode arrays:
\[\d{2,3}(,\d{2,3})+\]. Matches[112,105,99,111,67,84,70,123], which decodes topicoCTF{. - Hex escapes:
\\x[0-9a-f]{2}inside JS string literals."\x70\x69\x63\x6f"=pico. - Unicode escapes:
\\u[0-9a-f]{4}. Same idea, longer form:"\u0070\u0069\u0063\u006f"=pico.
Decoder strategy: try cheapest first. Pipe each candidate through base64 decode; print outputs that contain printable ASCII. Fall back to charcode-array decode (Python:
chr()mapped over the list). Then hex/unicode escapes (codecs.decode(s, 'unicode_escape')). Print all candidate decodes; eyeball forpicoCTF{or anything that reads as English.Exfiltration sinks are useful as triangulation. The flag is rarely a static string; it's usually built into the data the extension would send out. Find the
fetch()/XMLHttpRequest/navigator.sendBeacon()call, walk back through the variables that build its body argument, and the encoded form is right there. See the CTF Encodings guide for the full decoder ladder, and the Web Challenges guide for adjacent extension-and-DOM patterns. - Base64:
- Step 4Decode obfuscated stringsDecode any encoded strings found. Common techniques in malicious extensions: base64, hex escape sequences, charcode arrays, and XOR with a hardcoded key.bash
# Decode base64:bashecho '<base64_string>' | base64 -dbash# Decode hex array:pythonpython3 -c "print(bytes.fromhex('<hex_string>').decode())"bash# Decode charcode array:pythonpython3 -c "print(''.join(chr(c) for c in [<codes>]))"bash# Decode XOR array with key:pythonpython3 -c "key=b'<key>'; data=[<bytes>]; print(''.join(chr(b^key[i%len(key)]) for i,b in enumerate(data)))"Learn more
Each obfuscation technique has a straightforward reversal. Base64 is a reversible encoding that represents binary data as printable ASCII using 64 characters (A-Z, a-z, 0-9, +, /). It expands data by 33% (3 bytes become 4 characters) and is identifiable by its character set and optional
=padding at the end. Hex encoding represents each byte as two hex digits - easily reversed withbytes.fromhex(). String.fromCharCode arrays are JavaScript's native way to build strings from character codes - the Python equivalent ischr().XOR obfuscation with a hardcoded key is trivial to reverse once the key is found. The key is almost always near the data, in one of two layouts:
// Layout 1: key as a sibling const in the same IIFE (function() { const k = "secretKey42"; const enc = [0x13, 0x0c, 0x07, 0x06, ...]; // ... decode loop using k and enc })(); // Layout 2: key string immediately above the encoded blob const KEY = "hunter2"; const PAYLOAD = "AwAEAB0F..." // base64-then-XORSearch for short string constants (5-32 chars) declared near any large numeric array or base64 blob. XOR is symmetric - applying the key again decodes - and rolling XOR cycles the key index modulo the key length. If the key is genuinely missing, frequency analysis on the ciphertext recovers the key length (Kasiski/IC), then per-position single-byte XOR brute force recovers each key byte.
For heavily obfuscated JavaScript, tools like de4js, jsnice.org, or running the code in a controlled Node.js environment (with network calls mocked out) can automatically deobfuscate it. The goal is always to determine what data is being collected, where it is being sent, and what the actual malicious behavior is - which is exactly the process of real malware reverse engineering applied to browser extensions.
Flag
picoCTF{4dd_0n_tr4p_...}
Extract the .zip extension, search all JS files for picoCTF directly, then look for encoded forms: base64 strings, \x hex escapes, String.fromCharCode arrays, or XOR-encoded byte arrays. The flag is hidden in the extension's JavaScript source.