Write up for UTCTF 2024 - Crypto/Cryptordle

Challenge

Just guess the word in 6 tries. What do you mean it’s hard?

#!/usr/bin/env python3
import random

wordlist = open('/src/wordlist.txt', 'r').read().split('\n')

for word in wordlist:
    assert len(word) == 5
    for letter in word:
        assert letter in 'abcdefghijklmnopqrstuvwxyz'

for attempt in range(3):
    answer = random.choice(wordlist)
    num_guesses = 0
    while True:
        num_guesses += 1

        print("What's your guess?")
        guess = input().lower()

        assert len(guess) == 5
        for letter in guess:
            assert letter in 'abcdefghijklmnopqrstuvwxyz'

        if guess == answer:
            break

        response = 1
        for x in range(5):
            a = ord(guess[x]) - ord('a')
            b = ord(answer[x]) - ord('a')
            response = (response * (a-b)) % 31
        print(response)
    if num_guesses > 6:
        print("Sorry, you took more than 6 tries. No flag for you :(")
        exit()
    else:
        print("Good job! Onward...")

if num_guesses <= 6:
    print('Nice! You got it :) Have a flag:')
    flag = open('/src/flag.txt', 'r').read()
    print(flag)
else:
    print("Sorry, you took more than 6 tries. No flag for you :(")

Expalin

This is a similar problem to the “Wordle” game. I adopted a method of reducing candidates based on a word list. However, this does not guarantee complete success.

Solve

import random
from pwn import *

r = remote("betta.utctf.live", 7496)

with open("wordlist.txt", 'r') as f:
    wordlist = f.read().split()

def calc_response(word1, word2):
    response = 1

    for x in range(5):
        a = ord(word1[x]) - ord('a')
        b = ord(word2[x]) - ord('a')
        response = (response * (a-b)) % 31

    return response

for i in range(3):
    candidates = wordlist

    while True:
        word = random.choice(candidates)

        r.recvuntil(b"What's your guess?\n")
        r.sendline(bytes(word, "utf-8"))

        response = r.recvline()[:-1]

        if str(response, "utf-8").find("Good job! Onward...") != -1:
            break
        elif str(response, "utf-8").find("Sorry, you took more than 6 tries. No flag for you :(") != -1:
            exit()

        response = int(response)
        temp_candidates = []

        for w in candidates:
            if calc_response(word, w) == response:
                temp_candidates.append(w)
        candidates = temp_candidates

r.recvuntil(b"Nice! You got it :) Have a flag:\n")
print(str(r.recvline(), "utf-8")[:-1])
❯ python3 utctf_Cryptordle.py
[+] Opening connection to betta.utctf.live on port 7496: Done
utflag{sometimes_pure_guessing_is_the_strat}
[*] Closed connection to betta.utctf.live port 7496