248 lines
7.8 KiB
Python
248 lines
7.8 KiB
Python
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}")
|
|
|