PlayIt:pwntools 连远程数独,然后手搓太累
题面:远程服务返回一个 9×9 数独盘面,需要在终端填入数字,submit 后返回 flag。
拿到题的第一反应就是起 pwntools 连上去看看:
from pwn import *
context.log_level = 'debug'
io = remote('xxxxx-p9999.chal.buptmerak.cn', 9999)
io.interactive()
连上之后是这么个画面:

输入 help 会告诉你指令格式:set <行> <列> <数字>。
手搓确实能过——但是第二盘开始就烦了。于是让 AI 写了个自动求解脚本。
三个坑
① 盘面行的格式要精确匹配
盘面长这样:
1 | . . . | 7 4 . | . 2 . |
注意每行前面还有个行号,然后三组三列用 | 分隔。正则要能同时吃掉行号和分隔符:
m = re.match(r'^\s*(\d)\s*\|(.*)\|\s*$', line)
而且三列之间的小格是 . 和数字交替的,得先把 . 统一成 0 再切。
② ANSI 颜色码必须先剥离
服务端输出带颜色转义:
\x1b[1;32m1\x1b[0m | \x1b[2m.\x1b[0m ...
不剥离就直接正则匹配,百分之百失败。 这一步踩了很久:
ANSI = re.compile(r'\x1b\[[0-9;]*[A-Za-z]')
clean = ANSI.sub('', raw)
③ 提交后不要等提示符
这是最坑的一个。我一开始写成:
io.sendline(b'submit')
io.recvuntil(b'> ') # ← 卡死在这里
结果无限等待。原因是 submit 之后服务端直接吐 flag 然后关连接,根本不会再给提示符。
改成收完拉倒:
io.sendline(b'submit')
print(io.recvall(timeout=5).decode(errors='replace'))
求解器
标准回溯,没什么花活:
def solve(grid):
for r in range(9):
for c in range(9):
if grid[r][c] == 0:
for v in range(1, 10):
if ok(grid, r, c, v):
grid[r][c] = v
if solve(grid):
return True
grid[r][c] = 0
return False
return True
解完按 set r c v 逐格发:
for r in range(9):
for c in range(9):
if given[r][c] == 0:
io.sendline(f'set {r+1} {c+1} {ans[r][c]}'.encode())
注意行列号是 1-based,从 0-based 索引要加 1。
复盘
这题难度不高,但把交互式题的两个通用套路讲清楚了:
- 先
interactive()手动摸清协议,别上来就写自动化——连指令格式都不知道,写出来的脚本必然要改十遍; - 收尾要防卡死。远程服务”发完就走”的场景很常见,
recvuntil(固定串)就是不稳定的根源,用recvall(timeout=...)更保险。
另外,写正则前先打印原始字节流看一眼,比对着文档猜格式快得多。