CyberKavach QuestCon Series: Upside-Down Vault

 


🧩 CTF Write-Up: Upside-Down Vault

Author: Sai Veer
Flag Format: QUESTCON{...}
Challenge Type: Multi-Layer Crypto + Stego + Web Service
Difficulty: Medium–Hard


🗃️ Files Provided

encrypted_flag.bin
layer1_puzzle.json
layer2_blob.json
layer2.enc
privkey.enc
vault.py
vault_pub.pem
vault_secret.png

🧠 Challenge Overview

The challenge consists of:

  • 3 cryptographic layers

  • 1 steganographic HMAC extraction

  • 1 server verification stage

The flag is revealed only after:

  1. Extracting the hidden HMAC secret from vault_secret.png.

  2. Solving all cryptographic layers.

  3. Using the recovered secrets to correctly sign and “seal” a request to the local server (vault.py).

  4. Finally, retrieving the decrypted flag from /sealed.


🧩 Step-by-Step Walkthrough


Step 1 — Extract HMAC Secret from PNG

The image vault_secret.png hides an HMAC key in its LSBs.

Why

The server expects this secret in ctf_secret/secret_mac.
It’s later used to generate HMAC signatures for /sign requests.

Script — extract_hmac_from_vault_secret.py

#!/usr/bin/env python3
from PIL import Image

def extract(path='vault_secret.png'):
    img = Image.open(path)
    w, h = img.size
    pix = img.load()

    def gen_positions(n):
        pos = []
        x = 13
        for i in range(n):
            pos.append((x % w, (i * 7) % h))
            x = (x * 1103515245 + 12345) & 0xFFFFFFFF
        return pos

    positions = gen_positions(4096)
    bits = []
    for x, y in positions:
        r, g, b = pix[x, y]
        bits.append(str(r & 1))

    data = []
    for i in range(0, len(bits), 8):
        bb = bits[i:i+8]
        if len(bb) < 8:
            break
        val = int(''.join(bb), 2)
        if val == 0:
            break
        data.append(val)

    mask = [0x5F, 0xA3, 0xC7, 0x1D]
    out = bytes([data[i] ^ mask[i % len(mask)] for i in range(len(data))])
    print(out.decode())

if __name__ == '__main__':
    extract()

Run

python3 extract_hmac_from_vault_secret.py

Output

stego_hmac_1b047faecc67feb3

Save this:

HMAC_SECRET=$(python3 extract_hmac_from_vault_secret.py)

Used later in /sign HMAC and ctf_secret/secret_mac.


Step 2 — Factor Fermat-Friendly Composite

layer1_puzzle.json contains an RSA-style modulus N = p * q, where p and q are close.

Why

Factoring gives us p and q.
p (in hex) is used to derive the next AES key.

Script — fermat_solve.py

#!/usr/bin/env python3
from math import isqrt
import json, sys

N = int(json.load(open("layer1_puzzle.json"))["N"])

a = isqrt(N)
if a * a < N:
    a += 1
while True:
    b2 = a * a - N
    b = isqrt(b2)
    if b * b == b2:
        p = a - b
        q = a + b
        print(p)
        print(q)
        sys.exit(0)
    a += 1

Run

python3 fermat_solve.py > factors.txt

Output:

p = 1586187469...
q = 1586187469...

Convert p to hex for the next layer:

python3 - <<'PY' > pass1_hex.txt
p = int(open('factors.txt').read().splitlines()[0])
print(format(p, 'x'))
PY

Step 3 — Derive key2 & Decrypt layer2.enc

Why

layer2.enc is AES-CBC encrypted with:

key2 = SHA256(pass1_hex)

Generate key2

python3 - <<'PY'
import hashlib
p = open('pass1_hex.txt').read().strip()
open('key2.bin','wb').write(hashlib.sha256(p.encode()).digest())
print("sha256(pass1_hex) =", hashlib.sha256(p.encode()).hexdigest())
PY

Decrypt layer2

python3 decrypt_layer2.py

Output JSON

{
  "vigenere_ct": "naKnlKOknqyWnmtla2meZm5uamuYnWtkmQ==",
  "note": "Vigenere key is the decimal string of (p mod 10000)."
}

Step 4 — Vigenère Decryption

Why

The ciphertext above is base64-encoded and Vigenère-encrypted using the key:

key = str(p % 10000)

Script — vigenere_decrypt.py

#!/usr/bin/env python3
import json, base64

p = int(open('factors.txt').read().splitlines()[0])
doc = json.load(open('layer2_decrypted.json'))
ct = base64.b64decode(doc['vigenere_ct'])
vkey = str(p % 10000).encode()

pt = bytes((ct[i] - vkey[i % len(vkey)]) % 256 for i in range(len(ct)))
open('pass2.txt', 'wb').write(pt)
print("pass2 preview:", pt.decode())

Run

python3 vigenere_decrypt.py

Output

pass2 preview: finalkey_e2240e37518ad21b

Step 5 — Decrypt RSA Private Key

Why

privkey.enc is AES-CBC encrypted using:

key3 = SHA256(pass2)

Decrypt

python3 - <<'PY'
import hashlib
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad

pass2 = open('pass2.txt','rb').read().strip()
key3 = hashlib.sha256(pass2).digest()
ct = open('privkey.enc','rb').read()
iv, body = ct[:16], ct[16:]
privpem = unpad(AES.new(key3, AES.MODE_CBC, iv).decrypt(body), 16)
open('vault_priv.pem','wb').write(privpem)
print("vault_priv.pem written, size:", len(privpem))
PY

vault_priv.pem successfully generated.

Optionally verify it matches the public key:

python3 - <<'PY'
from Crypto.PublicKey import RSA
priv = RSA.import_key(open('vault_priv.pem','rb').read()).publickey().export_key()
pub = open('vault_pub.pem','rb').read()
print('pubs match?', priv.strip() == pub.strip())
PY

Step 6 — Start Server & Retrieve Flag

Prepare secrets

mkdir -p ctf_secret
echo -n "$HMAC_SECRET" > ctf_secret/secret_mac
chmod 600 ctf_secret/secret_mac
echo -n "lab_vault_t" > sealed

Run the server

python3 vault.py
# Running on http://127.0.0.1:5001

Request a signed token

export SECRET="$HMAC_SECRET"
payload="unlock_vault"
solutions="0|0|0"

mac=$(python3 -c "import hmac,hashlib,os; s=os.environ['SECRET'].encode(); msg=(\"$payload\"+'|'+'$solutions').encode(); print(hmac.new(s,msg,hashlib.sha256).hexdigest())")

curl -s -X POST http://127.0.0.1:5001/sign \
  -H "Content-Type: application/json" \
  -d "{\"payload\":\"$payload\",\"solutions\":[0,0,0],\"mac\":\"$mac\"}" | jq .

✅ Successful output:

{
  "signature": "BASE64_SIG_STRING..."
}

Retrieve the flag

curl -s http://127.0.0.1:5001/sealed | jq .

Output

{
  "flag": "QUESTCON{Yo3_cant_spell_@merican_without_3rica}"
}

🎉 FLAG → QUESTCON{Yo3_cant_spell_@merican_without_3rica}


🧩 Vulnerability Summary

Layer Weakness Explanation
1 Fermat-friendly RSA p and q are close, easy to factor.
2 Predictable Vigenère key key = str(p % 10000) makes brute-force trivial.
3 Layered symmetric derivation Chained SHA256/AES scheme builds on recoverable values.
4 Stego HMAC LSB steganography with predictable positions & small XOR mask.
5 Server gating Local files and correct HMAC simulate secure gate logic.

🏁 Final Notes

  • Ensure no trailing newlines in ctf_secret/secret_mac or sealed.

  • Always use raw byte operations for Vigenère decryption.

  • The challenge is an exercise in multi-layer key derivation, weak crypto design, and practical forensics.



Comments

Popular posts from this blog

From Open Networks to Safe Systems: How Firewalls Block the Hacker’s Doorway

CyberKavach QuestCon Series: VecNet