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}")