HackThisSite - Programming Mission 7

Challenge

Level 7

This level is about image processing, inspired by pay-tv cracking. Code a program which is able to automatically unscramble the lines of a given image. Type in all characters from the image. Begin with the upper line, and add the lower line without a blank in between.

限时 180 秒GET /missions/prog/7/ 生成随机实例(图在 /missions/prog/7/BMP), POST /missions/prog/7/index.php 字段 solution

图像结构

实例图是 200×100 的 PNG(Content-Type: image/png,注意 /missions/prog/7/BMP 会 301 到带尾斜杠的地址, 用 curl -L 或直接请求 /BMP/,否则可能拿到 0 字节 body)。

对像素做颜色统计就能一眼看出结构:

1
2
3
4
5
shape (100, 200, 3)
(245, 184, 143) px 1031 rows 28 span (3, 98) maxrow 95
(129, 127, 77) px 871 rows 27 span (7, 91) maxrow 65
(113, 82, 86) px 6 rows 6 span (18, 81) maxrow 1
(113, 66, 99) px 5 rows 5 span (20, 91) maxrow 1
  • 只有两个颜色的像素数在 800 以上(1031 / 871),它们就是两行文字的字形颜色;
  • 其余颜色每个只有 4–6 个像素,是逐像素噪声(不是行级噪声);
  • 两种字形色各自的 27–28 行散布在整幅图的高度上(span 3–98 / 7–91),说明加扰是行置换: 原始图的两行文字,它们的像素行被打乱后均匀撒在整张图里。

这就给出还原思路:先判断每一行原本在图像里的纵坐标,再把每个颜色的行按这个坐标排序,就能把两行文字拼回来。

行序判定:B 通道中值

背景是一条垂直渐变,B 通道随行号单调变化;而行置换并不改变每一行的像素内容, 于是每一行自身的 B 通道统计量就保留了它原来的纵坐标信息。实测 (min+max)/2(midrange)足够稳定:

1
2
3
4
5
6
7
8
import numpy as np
from PIL import Image

a = np.array(Image.open("chal_live.bmp").convert("RGB")).astype(int)
H, W, _ = a.shape
bm = np.array([(a[y, :, 2].min() + a[y, :, 2].max()) / 2.0 for y in range(H)])
order = sorted(range(H), key=lambda y: bm[y]) # 按估计出的原始位置排序
rank = {y: k for k, y in enumerate(order)} # rank 0 == 原图最下面一行

对每个字形色取出它的行、按 rank 排序后渲染(白底黑字、5 倍放大),就得到两段干净的字形条带:

1
2
3
colour A (129, 127, 77): 27 rows -> ranks [8, 10, 12, ..., 46, 48, 49]
colour B (212, 179, 160): 27 rows -> ranks [50, 51, 52, ..., 75, 76]
mean ranks {'A': 29.5, 'B': 63.0} -> submit order: strip B first, then A

注意这里的 rank从下往上计数的(rank 0 = 底部):色带 B 的 rank 均值更大, 说明它原本位于上面那一行;所以拼接顺序必须是 B 的文字 + A 的文字。

读出字符

条带放大 5 倍后字形是纯黑白的点阵,逐像素打成 ASCII 就能直接读 (也可以直接交给视觉模型读同一张图,两者结果一致):

1
2
line 1 (strip B): J 6 7 L 3 6
line 2 (strip A): M E 5 F E Y

Submit

同一个实例的提交实测(每行是 solution=<串> 的返回判定):

1
2
3
4
submit 'ME5FEYJ67L36' -> ok=False bad=True      # 先上后下、但左右顺序错
submit 'J67L36ME5FEY' -> ok=True bad=False # 接受
submit 'ME5FEY' -> ok=False bad=True # 只交一行不行
submit 'J67L36' -> ok=False bad=True

被接受后 profile 的 Programming 列表出现 (7),积分 6919。

Key points

  • 图像加扰(置乱行)不构成任何机密性:每一行的内容完好无损,背景渐变反而成了免费的坐标编码器。 任何能读像素统计的对手都能在毫秒级还原;真实世界的付费电视加扰最终也是被同类思路破解的。
  • 真正需要内容保护时应该在压缩/加密层处理,而不是在像素排列上做手脚。
  • 工程层面值得记的一点:限时关卡必须把读数→提交做成单进程, 本例 fetch → 排序 → 渲染 实测 1.93 秒,读字用视觉模型也在 180 秒预算内。
J67L36ME5FEY