StevenPZChan
StevenPZChan
14 min read

Categories

Tags

继续挑战,发挥想象力


第27题地址speedboat.html

  • zigzag.jpg
  • 网页标题是between the tables,题目内容为空,源码里面有两行隐藏内容:

    <!– did you say gif? –>
    <!– oh, and this is NOT a repeat of 14 –>

图片名字叫zigzag.jpgzigzag有蜿蜒来回反复的意思,图上也用画笔大致描绘出了这种意思。
发现图片上面有个超链接bell.html,点开后发现需要密码,密码提示是the order matters,那这题就是要找密码了。
根据提示,是要获取zigzag.gif

from io import BytesIO
import requests
from PIL import Image

with requests.Session() as sess:
    sess.auth = ('butter', 'fly')
    response = sess.get('http://www.pythonchallenge.com/pc/hex/zigzag.gif').content
    img = Image.open(BytesIO(response))
img

png

图片杂乱无章的,跟电视机花屏了似的。
提示说不是第14题italy.html的翻版,也就是不让我们想去画圈圈了,要思考一下zigzag的意义。

想到还有标题between the tables在表格之中还没有用到。
图片中会有什么表格呢?那就是palette调色板了。

在电脑图形学中,调色板(英语:Palette)要么是指用于数字图像管理的给定有限颜色组(颜色表),要么是屏幕上一组有限选择的小图形单元(诸如“工具选板”)。
根据上下文(工程师技术规范、广告中、程序员手册、图形文件规范、用户手册等),术语“调色板”和相关术语,譬如“网页调色板”和“RGB调色板”等,可以有稍微不同的含义。

From wikipedia.org
palette = img.getpalette()
table = {}
for i in range(256):
    R, G, B = palette[3 * i : 3 * (i + 1)]
    assert R == G == B
    table[i] = R
print(table)
{0: 37, 1: 229, 2: 162, 3: 136, 4: 59, 5: 212, 6: 9, 7: 41, 8: 24, 9: 156, 10: 148, 11: 112, 12: 254, 13: 91, 14: 106, 15: 49, 16: 248, 17: 213, 18: 220, 19: 15, 20: 85, 21: 159, 22: 62, 23: 78, 24: 76, 25: 111, 26: 103, 27: 150, 28: 154, 29: 68, 30: 25, 31: 169, 32: 126, 33: 185, 34: 140, 35: 234, 36: 244, 37: 21, 38: 88, 39: 32, 40: 16, 41: 223, 42: 2, 43: 28, 44: 42, 45: 165, 46: 253, 47: 94, 48: 161, 49: 137, 50: 124, 51: 84, 52: 82, 53: 225, 54: 96, 55: 219, 56: 163, 57: 184, 58: 45, 59: 17, 60: 139, 61: 255, 62: 236, 63: 89, 64: 44, 65: 83, 66: 189, 67: 118, 68: 230, 69: 73, 70: 170, 71: 239, 72: 66, 73: 102, 74: 52, 75: 192, 76: 182, 77: 145, 78: 250, 79: 48, 80: 10, 81: 20, 82: 238, 83: 69, 84: 99, 85: 71, 86: 26, 87: 36, 88: 1, 89: 4, 90: 81, 91: 149, 92: 132, 93: 206, 94: 252, 95: 13, 96: 146, 97: 249, 98: 197, 99: 54, 100: 93, 101: 79, 102: 205, 103: 153, 104: 218, 105: 131, 106: 3, 107: 188, 108: 172, 109: 110, 110: 113, 111: 168, 112: 235, 113: 222, 114: 180, 115: 183, 116: 216, 117: 80, 118: 22, 119: 129, 120: 245, 121: 114, 122: 95, 123: 61, 124: 177, 125: 201, 126: 142, 127: 176, 128: 92, 129: 6, 130: 243, 131: 87, 132: 215, 133: 122, 134: 116, 135: 242, 136: 191, 137: 202, 138: 190, 139: 72, 140: 160, 141: 186, 142: 164, 143: 204, 144: 105, 145: 104, 146: 217, 147: 167, 148: 107, 149: 227, 150: 231, 151: 121, 152: 214, 153: 29, 154: 155, 155: 175, 156: 98, 157: 0, 158: 224, 159: 228, 160: 47, 161: 232, 162: 74, 163: 133, 164: 11, 165: 247, 166: 226, 167: 196, 168: 86, 169: 199, 170: 207, 171: 141, 172: 194, 173: 70, 174: 130, 175: 179, 176: 173, 177: 152, 178: 14, 179: 40, 180: 221, 181: 50, 182: 19, 183: 46, 184: 23, 185: 151, 186: 67, 187: 147, 188: 7, 189: 127, 190: 53, 191: 5, 192: 237, 193: 135, 194: 101, 195: 166, 196: 210, 197: 200, 198: 97, 199: 109, 200: 171, 201: 198, 202: 33, 203: 12, 204: 18, 205: 77, 206: 195, 207: 39, 208: 203, 209: 65, 210: 157, 211: 108, 212: 57, 213: 64, 214: 63, 215: 208, 216: 251, 217: 115, 218: 174, 219: 8, 220: 123, 221: 241, 222: 144, 223: 117, 224: 55, 225: 38, 226: 58, 227: 178, 228: 31, 229: 143, 230: 193, 231: 125, 232: 120, 233: 51, 234: 75, 235: 158, 236: 246, 237: 128, 238: 211, 239: 119, 240: 35, 241: 34, 242: 134, 243: 90, 244: 30, 245: 27, 246: 209, 247: 187, 248: 233, 249: 181, 250: 100, 251: 138, 252: 56, 253: 43, 254: 60, 255: 240}

我们发现,调色板中每个序号对应的RGB值都不会是序号,里面存在着几个回环。
这应该就是所谓的表格了,可是这个怎么用呢?


在一筹莫展的时候我们把图像的像素点拿出来看看:

print(list(img.getdata())[:20])
[215, 208, 203, 12, 254, 60, 139, 72, 66, 189, 127, 176, 173, 70, 170, 207, 39, 32, 126, 142]

太惊喜了!

每个像素按表格查询的结果正是下一个像素的值!我们看看是不是都是这样的:

diff = {}
nextp = None
for i, pixel in enumerate(img.getdata()):
    if nextp is not None:
        if pixel != nextp:
            diff[i] = (nextp, pixel)
    nextp = table[pixel]
print('diff count:', len(diff))
for k in list(diff.keys())[:20]:
    print(f'Index {k}: {diff[k][0]} != {diff[k][1]}')
diff count: 9464
Index 114: 153 != 66
Index 115: 189 != 90
Index 116: 81 != 104
Index 119: 130 != 57
Index 171: 242 != 49
Index 172: 137 != 65
Index 173: 83 != 89
Index 174: 4 != 38
Index 434: 21 != 83
Index 435: 69 != 89
Index 436: 4 != 224
Index 437: 55 != 170
Index 440: 32 != 89
Index 441: 4 != 70
Index 486: 149 != 0
Index 490: 228 != 23
Index 491: 78 != 154
Index 492: 155 != 17
Index 493: 213 != 128
Index 755: 168 != 64

看来不符合表格的内容就是我们需要的:

img_new = img.copy()
width, height = img.size
img_data = img_new.load()
diff1 = bytearray()
diff2 = bytearray()
for index in range(width * height):
    x, y = index % width, index // width
    if index in diff:
        diff1.append(diff[index][0])
        diff2.append(diff[index][1])
        img_data[x, y] = 255
    else:
        img_data[x, y] = 0

print(diff1[:20], diff2[:20])
img_new
bytearray(b'\x99\xbdQ\x82\xf2\x89S\x04\x15E\x047 \x04\x95\xe4N\x9b\xd5\xa8') bytearray(b'BZh91AY&SY\xe0\xaaYF\x00\x17\x9a\x11\x80@')

png

不符合表格内容的标注之后是一幅字,上面写着not key word不是关键字,还有一个busy?,应该是指后面那个以BZh开头的bzip2压缩格式:

from bz2 import decompress

decoded_str = decompress(diff2).decode()
print(decoded_str[:100])
../ring/bell.html del assert repeat raise or class is exec return except print return switch from ex

看上去是一堆Python的关键字和别的一些东西。按上图提示应该是要把Python关键字都去掉。
根据密码提示the order matters我们按顺序执行:

from keyword import iskeyword

py2keywords = {'exec', 'print'}
words = decoded_str.split()
notkeyword = set(word for word in words if not iskeyword(word)) - py2keywords
for word in words:
    if word in notkeyword:
        print(word)
        notkeyword.remove(word)
../ring/bell.html
repeat
switch

好了,打开链接到bell.html,输入用户名repeat和密码switch,来到了下一题。

总结:这一题需要发散性思维,不过还是些图像处理的内容。

本题代码地址27_speedboat.ipynb