Fentastic Moves
5 minutos de lectura
Se nos proporciona una instancia remota para conectarnos:
$ nc 188.166.175.58 32136
Let's see if you can find the best moves for 25 puzzles! (Don't take too long tho :P)
White to Move (always)
Example: e2e4
8
♚ 7
♛ ♘ 6
♞ 5
♖ ♟ 4
♜ 3
♔ 2
♞ 1
a b c d e f g h
What's the best move?
Solución
En este reto, se nos muestra un tablero de ajedrez y se nos pide el siguiente mejor movimiento. Además, la descripción del reto dice:
Garry told me to catch some fish 20 meters deep
Hay varias sugerencias aquí:
- “fish” se refiere a un motor de ajedrez conocido llamado Stockfish
- “20 meters deep” nos indica usar
go depth 20
como comando destockfish
- El nombre del reto contiene la cadena “Fen”, que hace referencia a la Notación de Forsyth–Edwards (FEN)
Analizando el tablero de ajedrez
En primer lugar, debemos analizar el tablero de ajedrez para obtener una cadena FEN que ingresar en stockfish
. Para eso, necesitamos entender cómo funciona FEN y luego programarlo en Python. Encontré que las piezas de ajedrez se pueden representar en Unicode:
pieces = {
u'\u2654': 'K',
u'\u2655': 'Q',
u'\u2656': 'R',
u'\u2657': 'B',
u'\u2658': 'N',
u'\u2659': 'P',
u'\u265A': 'k',
u'\u265B': 'q',
u'\u265C': 'r',
u'\u265D': 'b',
u'\u265E': 'n',
u'\u265F': 'p',
}
Ahora, podemos usar pwntools
para manejar la conexión TCP y comenzar a analizar el tablero de ajedrez. Solo necesitamos tener un poco de paciencia para analizar todos los códigos de escape ANSI:
host, port = sys.argv[1].split(':')
io = remote(host, port)
def board_to_fen() -> str:
fen = ''
io.recvuntil(b'\x1b[')
io.recvline()
for _ in range(8):
empty = 0
for data in io.recvline().strip().split(b'\x1b[')[2:-2]:
piece = data[data.index(b'm') + 1:]
if piece == b' ':
empty += 1
elif piece.decode() in pieces:
fen += str(empty or '') + pieces[piece.decode()]
empty = 0
fen += str(empty or '') + '/'
io.recvline()
return fen[:-1] + ' w - - 0 1'
Como verificación, podemos probar que el FEN funciona usando una aplicación web como esta.
Trabajando con stockfish
Una vez descargado stockfish
, podemos aprender a usarlo. También existe una API online, pero no es gratis para depth mayores que 13.
Por lo tanto, podemos ingresar a la cadena FEN, agregar go depth 20
y tomar la línea que contiene bestmove
. Por ejemplo:
$ ./stockfish/stockfish-ubuntu-x86-64
Stockfish 16 by the Stockfish developers (see AUTHORS file)
position fen 2n4N/3b4/R4R2/5B2/3Q2P1/7K/p5P1/5k2 w - - 0 1
go depth 20
info string NNUE evaluation using nn-5af11540bbfe.nnue enabled
info depth 1 seldepth 1 multipv 1 score cp 2306 nodes 107 nps 5944 hashfull 0 tbhits 0 time 18 pv f5e6 f1e2
info depth 2 seldepth 2 multipv 1 score cp 2295 nodes 178 nps 8090 hashfull 0 tbhits 0 time 22 pv f5d7 f1e2
info depth 3 seldepth 3 multipv 1 score cp 2260 nodes 256 nps 10666 hashfull 0 tbhits 0 time 24 pv f5d7 f1e1 a6a2
info depth 4 seldepth 4 multipv 1 score mate 2 nodes 338 nps 13520 hashfull 0 tbhits 0 time 25 pv f5d7 f1e1 a6e6
info depth 5 seldepth 4 multipv 1 score mate 2 nodes 410 nps 16400 hashfull 0 tbhits 0 time 25 pv f5d7 f1e1 a6e6
info depth 6 seldepth 4 multipv 1 score mate 2 nodes 482 nps 19280 hashfull 0 tbhits 0 time 25 pv f5d7 f1e1 a6e6
info depth 7 seldepth 4 multipv 1 score mate 2 nodes 554 nps 22160 hashfull 0 tbhits 0 time 25 pv f5d7 f1e1 a6e6
info depth 8 seldepth 4 multipv 1 score mate 2 nodes 626 nps 25040 hashfull 0 tbhits 0 time 25 pv f5d7 f1e1 a6e6
info depth 9 seldepth 4 multipv 1 score mate 2 nodes 698 nps 27920 hashfull 0 tbhits 0 time 25 pv f5d7 f1e1 a6e6
info depth 10 seldepth 4 multipv 1 score mate 2 nodes 770 nps 30800 hashfull 0 tbhits 0 time 25 pv f5d7 f1e1 a6e6
info depth 11 seldepth 4 multipv 1 score mate 2 nodes 844 nps 33760 hashfull 0 tbhits 0 time 25 pv f5d7 f1e1 a6e6
info depth 12 seldepth 4 multipv 1 score mate 2 nodes 918 nps 36720 hashfull 0 tbhits 0 time 25 pv f5d7 f1e1 a6e6
info depth 13 seldepth 4 multipv 1 score mate 2 nodes 1021 nps 40840 hashfull 0 tbhits 0 time 25 pv f5d7 f1e1 a6e6
info depth 14 seldepth 4 multipv 1 score mate 2 nodes 1097 nps 43880 hashfull 0 tbhits 0 time 25 pv f5d7 f1e1 a6e6
info depth 15 seldepth 4 multipv 1 score mate 2 nodes 1193 nps 47720 hashfull 0 tbhits 0 time 25 pv f5d7 f1e1 a6e6
info depth 16 seldepth 4 multipv 1 score mate 2 nodes 1265 nps 50600 hashfull 0 tbhits 0 time 25 pv f5d7 f1e1 a6e6
info depth 17 seldepth 4 multipv 1 score mate 2 nodes 1337 nps 53480 hashfull 0 tbhits 0 time 25 pv f5d7 f1e1 a6e6
info depth 18 seldepth 4 multipv 1 score mate 2 nodes 1409 nps 56360 hashfull 0 tbhits 0 time 25 pv f5d7 f1e1 a6e6
info depth 19 seldepth 4 multipv 1 score mate 2 nodes 1481 nps 59240 hashfull 0 tbhits 0 time 25 pv f5d7 f1e1 a6e6
info depth 20 seldepth 4 multipv 1 score mate 2 nodes 1553 nps 62120 hashfull 0 tbhits 0 time 25 pv f5d7 f1e1 a6e6
bestmove f5d7 ponder f1e1
Podemos usar esta herramienta con pwntools
también:
stockfish = process('./stockfish/stockfish-ubuntu-x86-64')
stockfish.recv()
def get_bestmove(fen: str):
stockfish.sendline(f'position fen {fen}\ngo depth 20'.encode())
stockfish.recvuntil(b'bestmove ')
return stockfish.recvline().decode().split(' ')[0]
Lo último que necesitamos es un bucle de 25 iteraciones:
round_prog = io.progress('Round')
for r in range(25):
round_prog.status(f'{r + 1} / 25')
fen = board_to_fen()
bestmove = get_bestmove(fen)
io.sendlineafter(b"What's the best move?\n", bestmove.encode())
io.recvline()
round_prog.success('25 / 25')
io.success(io.recvline().decode())
Flag
Si ejecutamos el script, después de muchos intentos, finalmente encontramos el movimiento correcto 25 veces seguidas y obtenemos la flag:
$ python3 solve.py 167.99.85.216:31668
[+] Starting local process './stockfish/stockfish-ubuntu-x86-64': pid 22354
[+] Opening connection to 167.99.85.216 on port 31668: Done
[+] Round: 25 / 25
[+] You did it! Here's your flag: HTB{th4nk_g0d_f0r_st0ckf1sh}
[*] Closed connection to 167.99.85.216 port 31668
El script completo se puede encontrar aquí: solve.py
.