---
# System prepended metadata

title: 現代密碼學 序章 writeup
tags: [112-1社課]

---

# 現代密碼學 序章 writeup

:::spoiler 目錄
[toc]
:::

## Image Decode

### 題目

近期拉萊耶的考古團隊，發現了一張遠古時代遺留下來的文件 "output.txt"，考古學家根據壁畫的提示判斷，該檔案內紀錄著歷代先知們傳承下來的「真理」，只要能解讀它就可以成為「神」！但是，沒有人知道如何解讀它，直到你出現。

你，一名聰明且富有冒險精神的 CTFer，決定要破解它！你在文件的附近還發現了一個使用上古程式語言「π森」撰寫的腳本 `task.py`，你猜測就是它產生了 `output.txt` ！

萬事俱備，因果之輪開始轉動

勇敢的 CTFer 啊，成為神吧！

提示 1：看清楚!好像不只 base64 喔?
提示 2：圖片是二進位資料還是一般字串呢? 寫檔應該要開什麼模式呢?

`task.py`：

```python=
from base64 import b64encode

with open("flag.jpg", "rb") as img, open("output.txt", "w") as out:
    secret = b64encode(img.read().hex().encode()).decode()
    out.write(secret)
```

### 解法

從 `secret = b64encode(img.read().hex().encode()).decode()` 可以看出這張圖片是先被 `.hex()` 轉成 Hex String 再被 `b64encode()` 編碼。

因此我們只要逆向操作，先從 base64 解碼，在用將 Hex String 轉回二進制資料即可！不過要記得因為圖片二進制資料，所以寫檔時的模式必須是 "wb"

```python=
from base64 import b64decode

with open("output.txt", "r") as f, open("result.jpg", "wb") as res:
    b64_string = f.read()
    original_content = bytes.fromhex(b64decode(b64_string).decode())
    res.write(original_content)
```

## XOR property

### 題目

在某個不具名的異世界，有一把可以淨化萬物的聖器「flag」。

因為 flag 的威力太強，合稱三大罪惡之源的恐懼之王迪亞波羅、憎恨之王墨菲斯托，以及毀滅之王巴爾齊心合力封印了 flag。

我是個專精封印術式的 CTFer，我在觀察 flag 的封印後發現，牠們是利用一種叫 XOR 的術式、將代表了三大罪惡的門之鑰跟 flag 混和在一起。

據我判斷，要解除封印需要利用 XOR 術式反轉的特性，然後只要再....

哎呀！不知為何突然肚子好痛，這樣吧！我研究筆記就放在書房的書架上，你看到封面寫著 `task.py` 的那個就是了，不說了，我先去廁所了！

`task.py`：

```python=
# 提示：有工具就不需要自己動手
# 以下均為 hex string 表示，解密時需要轉回 bytes
a_XOR_c = "144d696775452d35783c65220c276a714105560d6c152c3c11383c1078417272100705535d1631620456"
b_XOR_c = "2249762906546d5807071b360d78557b60053c000500533a44415a187d1a4c0e4867437d52091b59270c"
a_XOR_b_XOR_flag = "5576663e077e3b3a3742216c6e0d6059115f0f391a4c204f182642476a0479131704196f5b4072545127"
```

### 解法

根據 XOR 的自反性，同樣的數字被 XOR 兩次會消失，所以 `(a XOR c) XOR (b XOR c) = a XOR b` ，此時同理，只要再 `(a XOR b XOR flag) XOR (a XOR b) = flag` ，即可得到 flag。

在 XOR 前記得要先將 Hex String 轉回 bytes 再進行運算；另外在這邊 pwn 的 xor 函數很方便，推薦直接使用！

```python=
from task import a_XOR_c, a_XOR_b_XOR_flag, b_XOR_c

from pwn import xor

a_XOR_b_XOR_flag = bytes.fromhex(a_XOR_b_XOR_flag)
a_XOR_c = bytes.fromhex(a_XOR_c)
b_XOR_c = bytes.fromhex(b_XOR_c)

flag = xor(a_XOR_b_XOR_flag, xor(a_XOR_c, b_XOR_c))

print(flag.decode())
```

## XOR decryption

### 題目

BlanHacker 日記：

* 2023 年 11 月 30 日
    我討厭題敘很多幹話的密碼學題目！ 因為隔壁的 AlanHacker 出的題目題敘都長得不得了，而且幹話很多，所以為了教訓他，我從他電腦裡面偷出了他社課題目的 flag，把他用 XOR 加密後再放回去。呀哈哈！看你還敢不敢再亂寫題敘。
* 2023 年 11 月 31 日
    那個 AlanHacker 就是遜啦！居然跪著求我解開加密ww根本笑死。 看在他態度誠懇，我就勉為其難幫他解密吧！
* 2023 年 11 月 32 日
     ....糟糕，我忘記當初加密的 key 是多少了... 怎麼辦?我只記得當初是用 1 byte 的整數作為 key 來加密 flag，但不知怎麼回事，key 居然不見了!? 有沒有誰?有沒有誰能幫幫我?
     
日記本旁邊是一個叫做 `task.py` 的文件，裡面寫著 BlanHacker 加密後的 flag

請你幫幫這可憐的小傢伙，解密出原來的 flag 吧！

保證 flag 格式為 crypto{XXX}

`task.py`：

```python=
# secret 為 hex string 表示，解密時需轉回 bytes
secret = "e2f3f8f1f5eefaa0b4def5c9e8f2dee0deb6d8d1b2deb1e7dee2e0e4d2e0d3dec2e8f1c9b2f3fc"
```

### 解法

1 byte 能表示的數字範圍為 $[0, 255]$ ，這其實很少！因此我們暴力計算每個組合 XOR 後的結果，再檢查開頭是否符合 flag 格式即可

```python=
from task import secret
from pwn import xor

secret = bytes.fromhex(secret)

for i in range(256):
    if b"crypto" in xor(secret, bytes([i])):
        print(xor(secret, bytes([i])).decode())
```

## Hash Cipher

### 題目

道理我都懂，所以為什麼 Hash 不能拿來加密？人家 Base64 都可以加密了！！ 所以我寫了一段程式來加密 flag，我相信它很安全，絕對不可能會被破解。

嗯？不信的話就來試試啊！ 拿去！加密的程式在 `task.py` 裡面、輸出在 "output.txt" 裡面，都拿去！反正你也不可能破解的了。

`task.py`：

```python=
from hashlib import sha3_256
 
with open("flag.txt", "r") as f, open("output.txt", "w") as out:
    flag = f.read()
    secret = ""
    for c in flag:
        secret += sha3_256(c.encode()).hexdigest()[:5]
    out.write(secret)
```

### 解法

從 `task.py` 看得出它加密的方式只是把每個字做 sha3_256 後取 Hex String 的前 5 個字而已，而 ASCII 可顯示字元只有在整數 $[32, 127]$ 的範圍內，因此一樣暴力的計算所有可能並建表——這邊是用 dict 建

然後再對於被加密的每五個字，我從剛建的表中搜尋對應的字元，組合起來即可得到 flag

```python=
from hashlib import sha3_256

with open("output.txt", "r") as f:
    cipher = f.read()

prefix_map = dict()
for i in range(32, 128):
    prefix_map[sha3_256(bytes([i])).hexdigest()[:5]] = chr(i)

flag = ""
for i in range(0, len(cipher), 5):
    flag += prefix_map[cipher[i:i + 5]]

print(flag)
```

## ECB

### 題目

嗚哇！你居然還認為自己能竄改我用 AES-ECB 加密的訊息？雜~魚♡

看來有人不知道 AES 目前除了社交工程和旁道攻擊外還沒有發現其他有效攻擊方法呢~噗哧噗哧

雜魚~ 雜~魚♡

好呀！ "task.py" 裡面放著三組明文-密文對，想試就儘管試吧！

解不出來可別哭喔~ 真是沒用呢♡雜~魚

提示 1：把每個 block 印出來看會比較清楚喔！

`task.py`：

```python=
m1 = "Deposit amount: 5 dollars"
c1 = "5797791557579e322e619f12b0ccdee8802015ee0467c419e7a38bd0a254da54"
m2 = "One million dolls is quite the collection"
c2 = "b1e952572d6b8e00b626be86552376e2d529a1b9cafaeb3ba7533d2699636323e7e433c10a9dcdab2ed4bee54da684ca"
m3 = "Hey nice binoculars"
c3 = "35d0c02036354fdf6082285e0f7bd6d2fdf526bd557b045bce65a3b3e300b55e"

# 請設計出一個 payload，使解密後結果為： "Deposit amount: One million dollars"
# 密文字串 c1、c2、c3 均是 16 進位字串、請轉回 bytes 運算
# flag：crypto{payload.hex()}
```

### 解法

這題如同提示所說，印出來比較好理解！

印出來後，你會發現第一條明文存款的部分會被拆開到第二區塊、而第二條明文的第一區塊 "One million doll" 與我們要的 "One million dollars" 非常接近，此時我們只要在湊到 "doll" 後面的 "ars" 找到接在一起即可！

可以發現，第三條明文的最後區塊不就剛好是 "ars" 嗎?所以我們只要把對應位置的密文組合起來即為我們要的答案

```python=
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad

from task import m1, m2, m3, c1, c2, c3

c1 = bytes.fromhex(c1)
c2 = bytes.fromhex(c2)
c3 = bytes.fromhex(c3)

print(m1[:16], m1[16:], sep = "|===|")
print(m2[:16], m2[16:32], m2[32:], sep = "|===|")
print(m3[:16], m3[16:], sep = "|===|")

payload = c1[:16] + c2[:16] + c3[16:]
print(f"crypto{{{payload.hex()}}}")
```

## Many Time Pad

### 題目

a：MTP

b：OTP？

a：M T P

b：MTP！

a：水啦！

b：Is it good to encrypt？

a：哩 encrypt 跨買都災

加密程式為 `task.py`、結果檔案為 "output.txt"

保證 flag 格式為 crypto{XXX}

提示 1：注意 flag 格式
提示 2：沒有什麼問題是暴力解決不了的

`task.py`：

```python=
from pwn import xor
from os import urandom

with open("flag.txt", "r") as f:
    flag = f.read().encode()

key = urandom(8)

with open("output.txt", "w") as f:
    f.write(xor(flag, key).hex())
```

### 解法

 `task.py` 中的 urandom 是設備隨機亂數，在密碼學 CTF 中看到它，在沒有其他外在因素的情況下，基本上不可能破解。
 
而提示有提到，注意 flag 格式。其實根據 XOR 的自反性，你會發現若我把明文拿去跟密文 XOR，我就能得到 key，而我們已知 flag 的前七個字是 "crypto{" ，因此我們可以解出部分的 key。

但是 urandom 產生的是 8 bytes 的 key 呀！還剩下 1 byte 怎麼辦?第二條提示就是用在這邊：嘗試暴力！如同 XOR decryption 那題一樣，我們一樣可以嘗試所有一個 byte 的可能，XOR 之後找出正確的 flag。

```python=
from pwn import xor

with open("output.txt", "r") as f:
    cipher = f.read()

cipher = bytes.fromhex(cipher)

known_key = xor(cipher[:7], b"crypto{")

for i in range(256):
    try:
        key = known_key + bytes([i])
        plain = xor(cipher, key).decode()
        if plain.isprintable() and plain[-1] == "}":
            print(plain)
    except:
        pass
```