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

แนวคิดการแก้โจทย์#

  1. ถอด M0 จากสตริงตัวเล็กล้วน
  2. หาตำแหน่งตัวอักษรทั้งหมดที่อาจเป็นตัวใหญ่
  3. สำหรับแต่ละตำแหน่งคำนวณ “ค่าที่ควรลบออก” = 26 × 62^(N−1−i)
  4. ใช้ Beam Search:
    • แบ่งตำแหน่งเป็นก้อนเล็ก ๆ (group_size=6)
    • ทุกก้อนลองเลือก subset (2^6=64 แบบ) ว่าจะ “ลบหรือไม่ลบ”
    • ให้คะแนนจาก:
      • จำนวน Byte ASCII ที่พบ (ASCII 32..126)
      • Prefix ตรง flag{
    • เก็บเฉพาะสถานะที่คะแนนสูงสุด
  5. เมื่อเดินครบทุกกลุ่ม ลองแปลงแต่ละชุดที่พบ กลับเป็น ASCII และหา flag{...} ด้วย regex

อธิบาย Script#

  • base62_decode(s) จะถอด Base62 เป็นจำนวนเต็ม
  • to_bytes_be(n,L) ทำการแปลงเป็นไบต์ big-endian ความยาว L
  • is_printable_ascii(bs) ให้คะแนน ASCII ที่อ่านได้
  • prefix_bonus(bs) เช็ค prefix ที่เริ่มด้วย flag{
  • try_decode_ascii(M,L_guess) → ลองหลายความยาว หา Flag ด้วย regex
  • solve_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:

Run Result

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/
Author
LUKKID
Published at
2025-12-13
License
CC BY-NC-SA 4.0