The Challenge
The server implements a word-guessing game using binary search semantics over a sorted word list. The server’s response lengths (number of candidate words remaining after each guess) are known in advance: [3952, 825, 23, 3, 2, 2, 1, 1, 1, 1, 1, 1, 1]. The goal is to reconstruct the password that matches this exact shrinking response sequence.
Approach
The challenge provides words.txt — a sorted word list. The response length after guessing a prefix is the number of words in the list that start with that prefix. Given the known sequence of response sizes, search all printable ASCII characters at each position: try appending each character to the current prefix and count how many words still match. The character that produces risp_len[i] words for position i is the correct next character.
get_words_by_prefix does a range scan over the sorted list: words matching prefix c are in [prefix + c, prefix + next(c)). The trova function recurses depth-first until the full password is found.
Solution
|
|
next_char computes the ASCII successor byte for the range upper bound. get_words_by_prefix filters the sorted word list to all words whose last known byte equals last_char — equivalent to a binary search range query. trova is a depth-first backtracking search: at each position it tries every character, and if the resulting word count matches the pre-known response, it recurses one level deeper.
What I Learned
When a server leaks the size of its binary-search window at each step, a known-response-count attack fully reconstructs the target without ever connecting to the server. It is a side-channel analogue of blind SQLi: instead of timing, the oracle is response cardinality. The sorted word list makes the range query efficient.