今天要挑戰的題目是:
題目說「it seems to have the key at the beginning」
→ 常見情形是檔案第一行是 substitution cipher 的 key(長度 26),接下來是被加密的訊息(密文)。
目標:根據檔案開頭的 key 還原密文,並從解出的英文中找到 flag。
解題思路
先取出檔案的key。
判斷 key 的意義(大致可分為兩種):
-情形 A:key 表示 cipher alphabet,也就是密文字母表。要解密時,對每個密文字母 c,找 c 在 key 的 index → 對應回標準 alphabet a..z(index 對應的明文字母)。
-情形 B:key 表示 plaintext alphabet order(不常見),即 a..z 對應到 key。解法會不同,因此需要嘗試兩種方向,看看哪個產出可讀英文。
對密文只替換英文字母(保留大小寫),其他字元(空白、標點)不變。
若結果為可讀英文,就找 flag(通常是 The flag is: 或 picoCTF{...})。
解題步驟
我用了一個小 script(自動下載 / 解析 / 嘗試兩種 mapping):
cat > solve_substitution.py <<'PY'
import sys, string, urllib.request
def get_text(src):
if src.startswith("http://") or src.startswith("https://"):
data = urllib.request.urlopen(src).read()
try:
return data.decode()
except:
return data.decode('latin1')
else:
return open(src, encoding="utf8").read()
def try_decode(key, ciphertext):
alpha = string.ascii_lowercase
results = []
if len(key) == 26:
key = key.lower()
map1 = { key[i]: alpha[i] for i in range(26) }
key[index_of_cipher_in_alpha]
map2 = { alpha[i]: key[i] for i in range(26) }
for name,mapping in [("assume key == cipher_alphabet (decode by key->plain)", map1),
("assume key == plaintext_alphabet order (decode by a..z->key)", map2)]:
out = []
for ch in ciphertext:
low = ch.lower()
if low in mapping:
plain = mapping[low]
out.append(plain.upper() if ch.isupper() else plain)
else:
out.append(ch)
results.append((name, "".join(out)))
else:
results.append(("key-not-26", "Key length != 26; raw key: " + key))
return results
def main():
src = sys.argv[1] if len(sys.argv)>1 else "https://artifacts.picoctf.net/c/154/message.txt"
txt = get_text(src).strip().splitlines()
first = txt[0].strip()
if " " in first:
key = first.split()[0]
else:
key = first
cipher_lines = txt[1:] if len(txt)>1 else []
if not cipher_lines:
rest = first.split(maxsplit=1)
if len(rest) > 1:
key = rest[0]; cipher_lines = [rest[1]]
ciphertext = "\n".join(cipher_lines).strip()
print("Parsed key:", repr(key))
print("\nCiphertext (preview):\n", ciphertext[:500], "\n")
decs = try_decode(key, ciphertext)
for name, dec in decs:
print("-----", name, "-----")
print(dec[:4000])
print()
print("Done.")
if __name__ == '__main__':
main()
PY
chmod +x solve_substitution.py
./solve_substitution.py
系統輸出:
兩種嘗試中,第一種(assume key == cipher_alphabet) 產生的是清楚的英文段落,其中包含:
The flag is: picoCTF{5UB5717U710N_3V0LU710N_357BF9FF}
因此我們可以確定第一種 mapping 正確,此題正確Flag就是:picoCTF{5UB5717U710N_3V0LU710N_357BF9FF}
原理說明及心得
為何第一種 mapping 是正確方向?
直觀檢查:把密文字母 c 在 key 中尋找其 index,然後用 a..z 的那個 index 做對應,得到的解是通順英文 → 這是 cipher-alphabet 的典型行為。
如果相反方向(把 a..z 映到 key),解出來是無意義的雜湊文字(script 也輸出作比較),因此可以以可讀性判斷正確 mapping。
看到「key 在開頭」就先檢查第一行是否為 26 字母排列。
針對 substitution 題,寫一支小工具自動嘗試並輸出通常能快速得到答案。