Moth
In this simple reverse challenge we need to find the password for moth
.
The binary
In Ghidra, we can have a quick look at the main function:
We need to find a 0x51
characters long password.
In verify
, we seem to be performing 3 seperate checks in a 9 by 9 grid:
The first check makes sure that our value is be between 1 and 5 (‘a’ to ‘e’) and that it smaller than our zone size (We are calling a zone a group of characters in the key with the same value).
The second check makes sure that if the key has the same value at 2 coordinates then our password must have different values. In other words, in a given zone, each value needs to be distinct.
The last check makes sure that two adjacent cells (orthogonally or diagonally) have different values.
These checks looks a lot like sudoku checks…
The puzzle
If you are into sudokus (or an avid Cracking the cryptic fan) you can try and solve this puzzle by hand:
Here are the rules:
- Every zone has every digit from 1 to the size of the zone
- Orthogonal and diagonal cells cannot have the same value
TRADITIONAL SUDOKU RULES DO NOT APPLY
Wave function collapse
If you are too lazy to solve the above problem by hand you can always write a program to do so.
My preferred method for sudoku like problems is using the wave function collapse algorithm.
Here’s how the algorithm goes:
- Set the initial constraints on each cell
- While there remain undecided cells (a cell with more than one value)
- Find the most constrained undecided cell
- Chose a value in the available values for that cell
- Propagate this change by modifying the constraints of every necessary cell
- If a cell is over constrained -> backtrack
Here’s my python implementation that solves this problem
import copy
def reduce(grid: list[list[set[int]]], x: int, y: int, value: int):
grid = copy.deepcopy(grid)
color = g[y][x]
# No same number in a given color
for yy in range(9):
for xx in range(9):
if g[yy][xx] != color:
continue
if xx == x and yy == y:
continue
if value in grid[yy][xx]:
grid[yy][xx].remove(value)
# No same number around around
for dy in [-1, 0, 1]:
for dx in [-1, 0, 1]:
if dy == 0 and dx == 0:
continue
if x + dx < 0 or x + dx >= 9:
continue
if y + dy < 0 or y + dy >= 9:
continue
if value in grid[y + dy][x + dx]:
grid[y + dy][x + dx].remove(value)
return grid
def verify(grid: list[list[set[int]]]):
for y in range(9):
for x in range(9):
if len(grid[y][x]) == 0:
raise ValueError("Empty cell")
def solve(grid: list[list[set[int]]], stack: list[tuple[int, int]], indentation=0):
if len(stack) == 0:
return grid
stack.sort(key=lambda coords: len(grid[coords[1]][coords[0]]))
x, y = stack.pop(0)
values = [v for v in grid[y][x]]
for v in values:
try:
grid[y][x] = set([v])
grid2 = reduce(grid, x, y, v)
verify(grid2)
return solve(grid2, copy.deepcopy(stack), indentation=indentation+1)
except ValueError as e:
continue
raise ValueError()
grid = [[set(range(1, g2[y][x] + 1)) for x in range(9)] for y in range(9)]
all = [(x, y) for x in range(9) for y in range(9)]
SOLUTION = solve(grid, all)
print(SOLUTION)
print(''.join([''.join([['a', 'b', 'c', 'd', 'e'][list(SOLUTION[y][x])[0] - 1] for x in range(9)]) for y in range(9)]))
Here is the solved grid:
[[{2}, {3}, {1}, {5}, {4}, {2}, {1}, {2}, {1}],
[{1}, {4}, {2}, {3}, {1}, {3}, {4}, {3}, {4}],
[{2}, {3}, {1}, {4}, {2}, {5}, {2}, {1}, {2}],
[{4}, {5}, {2}, {5}, {3}, {1}, {3}, {5}, {3}],
[{3}, {1}, {4}, {1}, {4}, {5}, {4}, {1}, {2}],
[{4}, {2}, {5}, {2}, {3}, {2}, {3}, {5}, {4}],
[{3}, {1}, {3}, {1}, {5}, {4}, {1}, {2}, {1}],
[{2}, {5}, {2}, {4}, {2}, {3}, {5}, {4}, {3}],
[{1}, {3}, {1}, {3}, {5}, {4}, {1}, {2}, {1}]]
Flag
Here is the password for the binary:
bcaedbabaadbcacdcdbcadbebabdebecaceccadadedabdbebcbcedcacaedababebdbcedcacacedaba
When we run the binary with this password we get:
Well done, flag is ECW{md5(input)}
FLAG: ECW{8b39553c944cdce4ea4f9a692168093b}