480 words
2 minutes
TCTT25 Crypto: Bad62
Bad62 [100 pts] - Cryptography Write-up
โจทย์
มันคือการเข้ารหัส Bad62 ที่แสนเลวร้ายของฉัน
หมายเหตุ รูปแบบของ Flag ที่เป็นคำตอบของข้อนี้คือ flag{message_10digits}
ดาวน์โหลดไฟล์
| ไฟล์ | ดาวน์โหลด |
|---|---|
| bad62.py | 📥 Download |
| enc.txt | 📥 Download |
| solved.py | 📥 Download |
| All Files | 📦 GitHub Folder |
ไฟล์โจทย์
bad62.py (โค้ดเข้ารหัสที่ใช้จริง)
import base62
flag = input('Flag: ')encoded = base62.encodebytes(flag.encode())print(encoded.lower())enc.txt (ข้อมูลที่ได้จากการ Encode)
cbm6okchgcvxtifbvfd68lmyh38nqxnjxmdtbathtougtkxdmwux9tcmo6rs0j7uufข้อสังเกต
- Base62 ปกติ จะมีค่า digit ดังนี้:
'0'..'9'= 0..9'A'..'Z'= 10..35'a'..'z'= 36..61
- เมื่อบังคับ
.lower()ส่งผลให้- ตัวเลขไม่เปลี่ยนค่า digit
- ตัวอักษรพิมพ์เล็กไม่เปลี่ยนค่า digit
- ตัวใหญ่แปลงเป็นเป็นตัวเล็กทำให้ ค่าดิจิตเพิ่มขึ้น 26
แนวคิดการแก้โจทย์
- ถอด
M0จากสตริงตัวเล็กล้วน - หาตำแหน่งตัวอักษรทั้งหมดที่อาจเป็นตัวใหญ่
- สำหรับแต่ละตำแหน่งคำนวณ “ค่าที่ควรลบออก” = 26 × 62^(N−1−i)
- ใช้ Beam Search:
- แบ่งตำแหน่งเป็นก้อนเล็ก ๆ (group_size=6)
- ทุกก้อนลองเลือก subset (2^6=64 แบบ) ว่าจะ “ลบหรือไม่ลบ”
- ให้คะแนนจาก:
- จำนวน Byte ASCII ที่พบ (ASCII 32..126)
- Prefix ตรง
flag{
- เก็บเฉพาะสถานะที่คะแนนสูงสุด
- เมื่อเดินครบทุกกลุ่ม ลองแปลงแต่ละชุดที่พบ กลับเป็น ASCII และหา
flag{...}ด้วย regex
อธิบาย Script
base62_decode(s)จะถอด Base62 เป็นจำนวนเต็มto_bytes_be(n,L)ทำการแปลงเป็นไบต์ big-endian ความยาว Lis_printable_ascii(bs)ให้คะแนน ASCII ที่อ่านได้prefix_bonus(bs)เช็ค prefix ที่เริ่มด้วยflag{try_decode_ascii(M,L_guess)→ ลองหลายความยาว หา Flag ด้วย regexsolve_bad62_head(encoded)→ อัลกอริทึม beam search
solved.py
ALPH = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"VAL = {c: i for i, c in enumerate(ALPH)}BASE = 62
def base62_decode(s: str) -> int: v = 0 for ch in s: v = v * BASE + VAL[ch] return v
def to_bytes_be(n: int, L: int) -> bytes: b = bytearray(L) for i in range(L - 1, -1, -1): b[i] = n & 0xFF n >>= 8 return bytes(b)
def is_printable_ascii(bs: bytes) -> int: score = 0 for b in bs: if 32 <= b <= 126: score += 1 return score
def prefix_bonus(bs: bytes, tgt=b"flag{") -> int: m = min(len(bs), len(tgt)) bonus = 0 for i in range(m): if bs[i] == tgt[i]: bonus += 5 return bonus
def try_decode_ascii(M: int, L_guess: int): import re for dL in (-2, -1, 0, 1, 2, 3): L = max(1, L_guess + dL) bs = to_bytes_be(M, L) try: s = bs.decode('ascii') except: continue if re.fullmatch(r"^flag\{.*_[0-9]{10}\}$", s): return s m = re.search(r"flag\{[^}]+\}", s) if m: return m.group(0) return None
def solve_bad62_head(encoded: str, group_size=6, keep_top=2000): N = len(encoded) letter_positions = [i for i, c in enumerate(encoded) if c.isalpha()]
pow62 = [1] * (N + 1) for k in range(1, N + 1): pow62[k] = pow62[k - 1] * BASE
diffs = {i: 26 * pow62[N - 1 - i] for i in letter_positions} M0 = base62_decode(encoded) L_guess = max(1, (M0.bit_length() + 7) // 8)
groups = [] pos_sorted = sorted(letter_positions) for i in range(0, len(pos_sorted), group_size): groups.append(pos_sorted[i:i + group_size])
states = [(0, 0)]
for g_index, grp in enumerate(groups): adds = [0] for p in grp: d = diffs[p] new_adds = [] for a in adds: new_adds.append(a) new_adds.append(a + d) adds = new_adds
head_need = min(L_guess, (g_index + 1) * group_size)
new_states = [] for (old_score, total_sub) in states: base_val = M0 - total_sub if base_val <= 0: continue for add in adds: M = base_val - add if M <= 0: continue bs = to_bytes_be(M, L_guess) head = bs[:head_need] s = is_printable_ascii(head) + prefix_bonus(head) new_states.append((s, total_sub + add))
new_states.sort(key=lambda x: x[0], reverse=True) states = new_states[:keep_top]
for (score, total_sub) in states: M = M0 - total_sub flag = try_decode_ascii(M, L_guess) if flag: return flag
return None
if __name__ == "__main__": enc = "cbm6okchgcvxtifbvfd68lmyh38nqxnjxmdtbathtougtkxdmwux9tcmo6rs0j7uuf" ans = solve_bad62_head(enc, group_size=6, keep_top=2000) print(ans)ผลลัพธ์
เมื่อรัน Python Script และใช้ข้อมูลตามไฟล์ enc.txt จะได้ Flag:
flag{t0day_1s_n07_g00d_bu7_1ts_s0_b@d_1467297483}Credits
Writeup by netw0rk7 | Original Repo
TCTT25 Crypto: Bad62
https://blog.lukkid.dev/posts/tctt25-crypto-bad62/