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 20como 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.