WIP lakectf
This commit is contained in:
247
2025/lake/misc/wordler/gemini_solve.py
Normal file
247
2025/lake/misc/wordler/gemini_solve.py
Normal file
@@ -0,0 +1,247 @@
|
||||
import os
|
||||
import re
|
||||
from pwn import remote
|
||||
|
||||
def get_word_lengths(input):
|
||||
|
||||
print("--- Decoded Output ---")
|
||||
print(input)
|
||||
print("----------------------\n")
|
||||
|
||||
# 2. Find the line containing "Structure:"
|
||||
structure_line = None
|
||||
for line in input.splitlines():
|
||||
if "Structure:" in line:
|
||||
structure_line = line
|
||||
break
|
||||
|
||||
if not structure_line:
|
||||
print("No 'Structure' line found.")
|
||||
return []
|
||||
|
||||
# 3. Extract the pattern part (remove "Structure: " prefix)
|
||||
# The split ensures we get everything after the colon and space
|
||||
pattern_str = structure_line.split("Structure:")[1].strip()
|
||||
|
||||
# 4. Split by the underscore '_' which acts as the delimiter between words
|
||||
word_segments = pattern_str.split('_')
|
||||
|
||||
# 5. Calculate lengths of each segment
|
||||
word_lengths = [len(segment) for segment in word_segments]
|
||||
|
||||
return word_lengths
|
||||
|
||||
def get_word_dict(wordlist_path='wordlist.txt'):
|
||||
"""
|
||||
Reads a wordlist file and suggests words that match the required lengths.
|
||||
"""
|
||||
if not os.path.exists(wordlist_path):
|
||||
print(f"Error: Wordlist file '{wordlist_path}' not found.")
|
||||
print("Please create a file named 'wordlist.txt' with one word per line.")
|
||||
return
|
||||
|
||||
print(f"\nReading wordlist from: {wordlist_path}")
|
||||
try:
|
||||
with open(wordlist_path, 'r', encoding='utf-8') as f:
|
||||
# Clean words: remove whitespace and filter out empty lines
|
||||
words = [line.strip() for line in f if line.strip()]
|
||||
except Exception as e:
|
||||
print(f"Error reading file: {e}")
|
||||
return
|
||||
|
||||
# Group words by length for efficient lookup
|
||||
words_by_len = {}
|
||||
for w in words:
|
||||
l = len(w)
|
||||
if l not in words_by_len:
|
||||
words_by_len[l] = []
|
||||
words_by_len[l].append(w)
|
||||
|
||||
return words_by_len
|
||||
|
||||
def parse_ansi_feedback(raw_bytes):
|
||||
"""
|
||||
Parses the raw bytes containing ANSI codes to extract letters and their colors.
|
||||
Returns a list of word_segments.
|
||||
Each segment is a list of tuples: (char, color_code)
|
||||
"""
|
||||
try:
|
||||
decoded = raw_bytes.decode('utf-8')
|
||||
except UnicodeDecodeError:
|
||||
decoded = raw_bytes.decode('utf-8', errors='ignore')
|
||||
|
||||
print("--- Raw Decoded String ---")
|
||||
print(decoded)
|
||||
print("--------------------------")
|
||||
|
||||
# Split into word chunks based on the underscore
|
||||
# We must be careful because the underscore is likely plain text between ANSI codes
|
||||
raw_segments = decoded.strip().split('_')
|
||||
|
||||
parsed_segments = []
|
||||
|
||||
# Regex to find ANSI code and the character immediately following
|
||||
# Matches: \x1b[93mU
|
||||
ansi_pattern = re.compile(r'\x1b\[(\d+)m([A-Z])')
|
||||
|
||||
for raw_seg in raw_segments:
|
||||
matches = ansi_pattern.findall(raw_seg)
|
||||
if matches:
|
||||
# matches is a list of ('93', 'U'), ('90', 'V'), etc.
|
||||
parsed_segments.append(matches)
|
||||
|
||||
return parsed_segments
|
||||
|
||||
def generate_constraints(segment_data):
|
||||
"""
|
||||
Converts parsed (color, char) data into logical constraints for a specific word.
|
||||
"""
|
||||
constraints = {
|
||||
'length': len(segment_data),
|
||||
'greens': {}, # index: char
|
||||
'yellows': [], # list of (index, char)
|
||||
'forbidden': set(), # chars that definitely aren't in the word
|
||||
'must_have': set() # chars that must exist (from greens/yellows)
|
||||
}
|
||||
|
||||
# First pass: Identify "Must Have" chars (Green/Yellow)
|
||||
for i, (color, char) in enumerate(segment_data):
|
||||
char = char.lower()
|
||||
if color == '92' or color == '93': # Green or Yellow
|
||||
constraints['must_have'].add(char)
|
||||
|
||||
# Second pass: Build specific constraints
|
||||
for i, (color, char) in enumerate(segment_data):
|
||||
char = char.lower()
|
||||
|
||||
if color == '92': # Green (Correct Position)
|
||||
constraints['greens'][i] = char
|
||||
|
||||
elif color == '93': # Yellow (Wrong Position)
|
||||
constraints['yellows'].append((i, char))
|
||||
|
||||
elif color == '90': # Grey (Not in word / Not here)
|
||||
# If char is in 'must_have', Grey just means "not at this position"
|
||||
# (or max count reached, but "not at this position" is safer for simple filtering)
|
||||
if char in constraints['must_have']:
|
||||
constraints['yellows'].append((i, char)) # Treat as "wrong pos" constraint
|
||||
else:
|
||||
constraints['forbidden'].add(char)
|
||||
|
||||
return constraints
|
||||
|
||||
def get_candidates_for_slots(wordlist_path, constraints_list):
|
||||
"""
|
||||
Reads the wordlist and applies constraints for each word segment.
|
||||
Returns a list of lists: [ [candidates_for_word_1], [candidates_for_word_2], ... ]
|
||||
"""
|
||||
if not os.path.exists(wordlist_path):
|
||||
print(f"Error: {wordlist_path} not found.")
|
||||
return []
|
||||
|
||||
try:
|
||||
with open(wordlist_path, 'r', encoding='utf-8') as f:
|
||||
all_words = [w.strip().lower() for w in f if w.strip()]
|
||||
except Exception as e:
|
||||
print(f"Error reading wordlist: {e}")
|
||||
return []
|
||||
|
||||
all_slots_candidates = []
|
||||
|
||||
# Process each word slot defined in the input
|
||||
for word_idx, constraints in enumerate(constraints_list):
|
||||
print(f"\n=== Solving Word {word_idx + 1} (Length {constraints['length']}) ===")
|
||||
|
||||
candidates = []
|
||||
|
||||
for w in all_words:
|
||||
# 1. Length Check
|
||||
if len(w) != constraints['length']:
|
||||
continue
|
||||
|
||||
# 2. Forbidden Char Check
|
||||
if any(c in constraints['forbidden'] for c in w):
|
||||
continue
|
||||
|
||||
# 3. Green Check (Exact matches)
|
||||
if any(w[i] != char for i, char in constraints['greens'].items()):
|
||||
continue
|
||||
|
||||
# 4. Yellow Check
|
||||
# Part A: The char must exist in the word
|
||||
if any(char not in w for char in constraints['must_have']):
|
||||
continue
|
||||
|
||||
# Part B: The char must NOT be at the yellow index
|
||||
if any(w[i] == char for i, char in constraints['yellows']):
|
||||
continue
|
||||
|
||||
candidates.append(w)
|
||||
|
||||
print(f"-> Found {len(candidates)} matches.")
|
||||
if candidates:
|
||||
preview = ', '.join(candidates[:5])
|
||||
if len(candidates) > 5:
|
||||
preview += "..."
|
||||
print(f"-> Examples: {preview}")
|
||||
|
||||
all_slots_candidates.append(candidates)
|
||||
|
||||
return all_slots_candidates
|
||||
|
||||
def construct_guess_string(all_slots_candidates):
|
||||
"""
|
||||
Takes the lists of candidates and forms a 'word_word_word' string.
|
||||
Strategically, it just picks the first valid candidate for now.
|
||||
"""
|
||||
chosen_words = []
|
||||
for i, candidates in enumerate(all_slots_candidates):
|
||||
if not candidates:
|
||||
print(f"Warning: No solution found for word {i+1}!")
|
||||
chosen_words.append("?????")
|
||||
else:
|
||||
# Simple strategy: Pick the first one
|
||||
chosen_words.append(candidates[0])
|
||||
|
||||
return "_".join(chosen_words)
|
||||
|
||||
|
||||
with remote("chall.polygl0ts.ch", 6052) as p:
|
||||
structure = p.read()
|
||||
|
||||
|
||||
# Run the function
|
||||
lengths = get_word_lengths(structure.decode())
|
||||
|
||||
if lengths:
|
||||
print(f"Word Lengths: {lengths}")
|
||||
print(f"Total Letters: {sum(lengths)}")
|
||||
|
||||
word_dict = get_word_dict('word_list.txt')
|
||||
|
||||
solve = []
|
||||
for l in lengths:
|
||||
solve.append(word_dict[l][0])
|
||||
|
||||
guess = "_".join(solve)
|
||||
|
||||
p.sendline(guess.encode())
|
||||
output = p.read()
|
||||
|
||||
print(output.decode())
|
||||
|
||||
# 1. Parse the structure
|
||||
segments = parse_ansi_feedback(output)
|
||||
|
||||
# 2. Convert to constraints
|
||||
all_constraints = [generate_constraints(seg) for seg in segments]
|
||||
|
||||
print(all_constraints)
|
||||
|
||||
# 3. Get Candidates
|
||||
candidate_lists = get_candidates_for_slots('word_list.txt', all_constraints)
|
||||
|
||||
guess = construct_guess_string(candidate_lists)
|
||||
|
||||
print(f"Best guess {guess}")
|
||||
|
||||
Reference in New Issue
Block a user