AES & Block Cipher Modes
AES เป็น block cipher มาตรฐานที่เข้ารหัสทีละ block (16 bytes) ตัว AES เองแข็งแรงมาก แต่ช่องโหว่ใน CTF มักอยู่ที่ 'mode of operation' ที่ใช้ผิด — โดยเฉพาะ ECB ที่รั่ว pattern และ CBC ที่จัดการ IV/padding ผิด บทนี้เน้นการตรวจจับและโจมตี mode ที่อ่อนแอ
1. AES และแนวคิด block cipher
AES เข้ารหัสข้อมูลทีละ block ขนาด 16 bytes (128 บิต) ด้วย key ขนาด 128/192/256 บิต ตัว algorithm เองยังไม่มีการโจมตีที่ practical — ดังนั้นใน CTF คุณแทบไม่ต้องโจมตี AES ตรงๆ แต่ให้มองหาวิธีใช้งานที่ผิด เช่น mode, การ reuse IV/nonce, padding oracle, หรือ key/IV ที่เดาได้
Mode of operation คือวิธีต่อ block หลายอันเข้าด้วยกัน เพราะข้อมูลจริงยาวกว่า 16 bytes mode ที่พบบ่อย: ECB, CBC, CTR, GCM — แต่ละแบบมีจุดอ่อนต่างกัน ECB อ่อนสุดและเจอบ่อยสุดใน CTF
2. ECB — จุดอ่อนคลาสสิก
ECB (Electronic Codebook) เข้ารหัสแต่ละ block อิสระจากกัน ด้วย key เดียวกัน ผลคือ plaintext block ที่เหมือนกัน → ciphertext block ที่เหมือนกันเป๊ะ ทำให้ pattern ของข้อมูลรั่วออกมา (รูปภาพที่เข้ารหัส ECB ยังเห็นเค้าโครงเดิมได้) นี่คือเหตุผลที่ ECB ห้ามใช้กับข้อมูลจริง
def detect_ecb(ct: bytes, bs=16):
blocks = [ct[i:i+bs] for i in range(0, len(ct), bs)]
return len(blocks) != len(set(blocks)) # มี block ซ้ำ = น่าจะ ECB
# ใน CTF: ส่ง plaintext ที่เป็นตัวซ้ำยาวๆ (เช่น 'A'*48)
# ถ้า ciphertext มี block 16-byte ซ้ำกัน = ECB3. CBC — IV และ bit-flipping
CBC (Cipher Block Chaining) แก้จุดอ่อน ECB โดย XOR plaintext block กับ ciphertext block ก่อนหน้าก่อนเข้ารหัส block แรกใช้ IV (Initialization Vector) แทน ทำให้ block ซ้ำไม่ออกมาเหมือนกัน แต่ CBC มีจุดอ่อนของตัวเอง: bit-flipping และ padding oracle
Bit-flipping: เพราะ plaintext block ถูก XOR กับ ciphertext block ก่อนหน้าตอนถอดรหัส การแก้ไข byte ใน C_(i−1) จะทำให้ byte ตำแหน่งเดียวกันใน P_i เปลี่ยนแบบควบคุมได้ (แลกกับ P_(i−1) ที่เละ) ใช้โจมตีเมื่อ block แรกเป็น IV ที่ควบคุมได้ เช่น เปลี่ยน role=user เป็น role=admin
4. ถอดรหัสเมื่อมี key (งานทั่วไป)
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
# CBC
cipher = AES.new(key, AES.MODE_CBC, iv)
pt = unpad(cipher.decrypt(ct), 16)
# ECB
cipher = AES.new(key, AES.MODE_ECB)
pt = unpad(cipher.decrypt(ct), 16)
print(pt)5. Decision Tree
| สังเกต | ทำต่อ |
|---|---|
| block ซ้ำใน ciphertext | ECB → byte-at-a-time / pattern leak |
| แทรก plaintext ได้ + ECB | ECB chosen-plaintext decryption |
| CBC + ควบคุม IV/prev block ได้ | bit-flipping เปลี่ยน plaintext |
| CBC + เซิร์ฟเวอร์บอก padding ผิด/ถูก | Padding Oracle (ดูหัวข้อแยก) |
| IV/nonce ซ้ำ (CTR/GCM) | keystream reuse → XOR สองข้อความ |
| มี key | ถอดตรงๆ ด้วย pycryptodome |
6. Quick Reference
- block AES = 16 bytes; โจมตีที่ mode ไม่ใช่ที่ AES
- ECB: block ซ้ำ → ciphertext ซ้ำ (ตรวจด้วย set ของ block)
- ECB + chosen-plaintext → byte-at-a-time decryption
- CBC: P_i = Dec(C_i) ⊕ C_(i−1); แก้ C_(i−1) → flip P_i
- IV/nonce reuse = หายนะ (keystream/pattern รั่ว)
- เครื่องมือ: pycryptodome, CyberChef (AES Decrypt + ECB detect)
🧭 จับมือทำทีละขั้น (มีแค่ Kali) + ถ้าติดไปไหนต่อ
สมมติเจอโจทย์ที่บอกว่าเข้ารหัสด้วย AES แต่ไม่รู้ว่าใช้ mode อะไร มีแค่เครื่อง Kali เปล่าๆ ทำตามนี้ทีละขั้น
- 1ถ้ามี source code ให้ (python/java/etc.) ให้ `grep -i 'MODE_\|cipher' *.py` หา `AES.MODE_ECB/CBC/CTR/GCM` ตรงๆ ก่อน — เร็วสุดถ้ามีให้
- 2ไม่มี source → เช็คความยาว ciphertext: เป็นทวีคูณของ 16 เสมอไหม (มี padding = ECB/CBC) หรือยาวเท่า plaintext พอดี (สงสัย CTR/GCM/stream)
- 3คุมส่ง input เอง (chosen-plaintext) ได้ไหม? ถ้าได้ ส่งข้อมูลซ้ำๆ ยาวๆ เช่น `'A'*48` แล้วดู ciphertext ที่ได้กลับมา
- 4มี hex/base64 ที่เข้ารหัสแล้ว — วางใน python แล้วเช็ค: `blocks=[ct[i:i+16] for i in range(0,len(ct),16)]; len(blocks)!=len(set(blocks))` ถ้า True = มี block ซ้ำ = ECB แน่นอน
- 5เป็น ECB → ไปทำตามหัวข้อ ECB ทันที (block-swap หรือ byte-at-a-time decryption ถ้าคุม input ได้)
- 6ไม่ซ้ำเลย (ทุก block ดูสุ่ม) แต่ยังเป็นทวีคูณของ 16 → น่าจะ CBC เช็คว่า 16 byte แรกของ ciphertext คือ IV แยกออกมาต่างหากไหม
- 7เป็น CBC → ลองแก้ 1 byte ใน ciphertext แล้วส่งกลับไปให้ server ถอด ดูว่า error/status/เวลาตอบต่างกันไหมเมื่อ padding ผิดกับถูก (เช่นลองแก้ byte สุดท้ายหลายค่า)
- 8server เผย error ต่างกันชัดเจน → นี่คือ Padding Oracle ไปทำตามหัวข้อ Padding Oracle เพื่อถอดทั้งข้อความโดยไม่ต้องรู้ key
- 9server ไม่บอก padding แต่ยัง parse ผลลัพธ์ต่อได้ (ไม่เช็ค integrity) → ลอง bit-flipping เปลี่ยนค่าที่ต้องการ (เช่น role=user → admin) ตามหัวข้อ CBC
- 10โจทย์ให้ key/IV มาตรงๆ → เปิด CyberChef (gchq.github.io/CyberChef) ลาก 'AES Decrypt' ใส่ key/IV/mode ให้ตรง หรือใช้ pycryptodome เขียนเอง
- 11ไม่ตรง ECB/CBC ชัดเจน (ความยาวไม่ทวีคูณ 16, ไม่มี padding oracle) → สงสัยเป็น CTR/GCM ที่ reuse nonce ไปดูหัวข้อ XOR เพราะหลักการ keystream reuse เหมือนกับ many-time pad
| ขั้นตอน/งาน | เครื่องมือใน Kali | ติดตั้งเพิ่ม (ถ้าไม่มี) | เครื่องมือออนไลน์ |
|---|---|---|---|
| ดู source code หา mode ที่ใช้ | cat, grep | - | - |
| ตรวจจับ ECB (block ซ้ำ) | python3 | - | gchq.github.io/CyberChef |
| ถอด AES เมื่อมี key/IV | python3 (pycryptodome) | pip install pycryptodome | CyberChef (AES Decrypt) |
| bit-flip ciphertext (CBC) | python3 | - | CyberChef (XOR) |
| ทดสอบ padding oracle กับ server | python3 (requests/pwntools) | pip install requests pwntools | - |
| เดา mode ด้วยลองผสมค่า | - | - | CyberChef (AES Decrypt ปรับ mode ทีละแบบ) |
หัวข้อที่เชื่อมโยง
โน้ตของฉัน
ยังไม่มีโน้ตสำหรับหัวข้อนี้