The Add/On Trap picoCTF 2026 Solution

Published: March 20, 2026

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.

bash
unzip -P picoctf suspicious.zip
bash
ls -la
  1. Step 1Extract the extension
    Extract first. You *can* unzip -p individual 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:
    bash
    unzip -P picoctf suspicious.zip -d suspicious/
    bash
    ls -la suspicious/
    bash
    # Quick triage without extracting (handy on huge archives):
    bash
    unzip -P picoctf -p suspicious.zip manifest.json | python3 -m json.tool
    Learn 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 .zip already. If you ever need to peel the CRX header off, the data starts at the offset where the bytes PK first appear (standard ZIP magic).

    Extract vs grep-the-archive. unzip -p archive.zip path/to/file streams 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.

  2. Step 2Inspect manifest.json
    Read 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.json
    bash
    grep -iE 'picoCTF|flag|secret' suspicious/manifest.json
    Learn 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.

  3. Step 3Search all JS files for the flag and encoded data
    Grep 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:
    bash
    grep -rn 'picoCTF' suspicious/
    bash
    # Base64 candidates: 20+ alphabet chars, optional = padding:
    bash
    grep -rnE '[A-Za-z0-9+/]{20,}={0,2}' suspicious/ --include='*.js'
    bash
    # String.fromCharCode arrays: bracket + comma-separated char codes:
    bash
    grep -rnE '\[\d{2,3}(,\d{2,3})+\]' suspicious/ --include='*.js'
    bash
    # Hex escape sequences inside JS string literals:
    bash
    grep -rnE '\\x[0-9a-f]{2}' suspicious/ --include='*.js'
    bash
    # Exfiltration sinks - the encoded payload is built nearby:
    bash
    grep -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 to picoCTF{hello}.
    • Charcode arrays: \[\d{2,3}(,\d{2,3})+\]. Matches [112,105,99,111,67,84,70,123], which decodes to picoCTF{.
    • 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 for picoCTF{ 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.

  4. Step 4Decode obfuscated strings
    Decode any encoded strings found. Common techniques in malicious extensions: base64, hex escape sequences, charcode arrays, and XOR with a hardcoded key.
    bash
    # Decode base64:
    bash
    echo '<base64_string>' | base64 -d
    bash
    # Decode hex array:
    python
    python3 -c "print(bytes.fromhex('<hex_string>').decode())"
    bash
    # Decode charcode array:
    python
    python3 -c "print(''.join(chr(c) for c in [<codes>]))"
    bash
    # Decode XOR array with key:
    python
    python3 -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 with bytes.fromhex(). String.fromCharCode arrays are JavaScript's native way to build strings from character codes - the Python equivalent is chr().

    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-XOR

    Search 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.

Want more picoCTF 2026 writeups?

Useful tools for Reverse Engineering

Related reading

What to try next