python challenge 27 解题总结

这一关是参照别人的方法做的,方法如下:

A picture showing an oar with a zigzag line. Title “between the tables”, clues “did you say gif?” and “oh, and this is NOT a repeat of 14”. There’s a link to bell [that’s level 28] but that’s password protected; the login domain is “the order matters”. Trying zigzag.gif as suggested, I see that the GIF is interlaced, which is new. Is that significant? [No, it’s not.] It’s a greyscale image with 256 levels of grey in the pixels, no clear pattern:

>>> zig = get_image('hex/zigzag.gif')
>>> zigdata = zig.tostring()
>>> hex(zigdata[:20])
'd7d0cb0cfe266c743b8b4842bd7fb0ad46aacf27207e8e'

The reference to level 14 suggests that spiral order is not it (and if it were, the opening bytes d7 d0 cb don’t suggest any file format). So what about zigzag order? I can’t find any place to start that looks like the beginning of a file. What about the palette? It appears to have each colour in it once:

>>> len(zig.getcolors())
256
>>> palette = zig.palette.getdata()[1][::3] # 3 bytes per pixel, equal RGB
>>> hex(palette[:20])
'25e5a2883bd40929189c9470fe5b6a31f8d5dc0f'

The values in the image data are the numbers of palette entries. What if we translate the image data by the palette, getting greyscale values?

>>> t = string.maketrans(''.join([chr(i) for i in range(256)]), palette)
>>> zigtrans = zigdata.translate(t)
>>> hex(zigtrans[:20])
'd0cb0cfe266c743b8b4842bd7fb0ad46aacf27207e8ea4'

Still nothing. Hang on, isn’t that very similar to the original data? It’s identical, except that it’s missing the first byte. Is it identical all the way to the end?

>>> zigdata[1:] == zigtrans[:-1]
False

What if we gather up all the bytes which are different in the two strings?

>>> deltas = filter(lambda p: p[0] != p[1], zip(zigdata[1:], zigtrans[:-1]))
>>> diffs = [''.join([p[i] for p in deltas]) for i in range(2)]
>>> diffs[0][:20]
'BZh91AY&SYxe0xaaYFx00x17x9ax11x80@'
>>> diffs[1][:20]
'99bd5182f289530415450437200495e44e9bd5a8'

On the one side, a bzip2-compressed datastream, on the other, what? I don't recognize it.

>>> bz = bz2.BZ2Decompressor().decompress(diffs[0])
>>> len(bz)
70644
>>> bz[:100]
'../ring/bell.html del assert repeat raise or class is exec return except print return switch from ex'

It’s a bunch of Python keywords plus ../ring/bell.html. Let’s see how many and what they are.

>>> keywords = bz.split(' ')
>>> keys = {}
>>> for k in keywords: keys[k] = 1
...
>>> keys.keys()
['and', 'elif', 'is', 'global', 'in', 'if', 'from', 'raise', 'for', 'except',
 'switch', 'finally', 'print', 'import', 'pass', 'repeat', 'return', 'exec',
 'else', 'assert', 'not', 'class', '../ring/bell.html', 'yield', 'try', 'while',
 'continue', 'del', 'break', 'or', 'def', 'lambda']
>>> len(keywords)
12000
>>> len(keys.keys())
32
>>> 12000 / 32
375

Are we meant to apply the same technique to this datastream? But if so, where’s the table? Does this datastream code for an image? (I tried several plausible ideas, but it looks like random noise each time.) Finally, in desperation, I tried every possible pair of keywords as username and password:

>>> auth_handler = urllib2.HTTPBasicAuthHandler()
>>> opener = urllib2.build_opener(auth_handler)
>>> for u in keys.keys():
...    for p in keys.keys():
...         try:
...             auth_handler.add_password('the order matters', 'www.pythonchallenge.com', u, p)
...             opener.open('http://www.pythonchallenge.com/pc/ring/bell.html')
...             print u,p
...             raise StopIteration
...         except urllib2.HTTPError:
...             pass
...
<addinfourl at 66194048 whose fp = <socket._fileobject object at 0x2a077a0>>
repeat switch
Traceback (most recent call last):
  File "<stdin>", line 7, in ?
StopIteration

Ah, I see. repeat and switch are the only words in the list that aren’t Python keywords. [I missed a clue: I looked at the data from the differences, but not at their pixel positions. If we make an image using just these pixel positions, we get another clue:]

>>> im = Image.new('1', zig.size, 0)
>>> im.putdata([p[0] == p[1] for p in zip(zigdata[1:], zigtrans[:-1])])
>>> im.save('zigclue.png')

自己总结的攻略:

# between the tables
# 图中是一只浆,和之字形的线条
# 网页注释中 图片zigzag.jpg旁边有  <!-- did you say gif? -->
# <!-- oh, and this is NOT a repeat of 14 -->
#
# 下载 http://www.pythonchallenge.com/pc/hex/zigzag.gif
# 又是一张杂乱无章的灰度图
# 点击船桨图片进入 http://www.pythonchallenge.com/pc/ring/bell.html 要新的用户名和密码
# 看来这关的目标就是要找到用户名密码
# 既然提示不是第14关的重复,就不是螺旋重组图片,那么是什么呢?
# 又没思路了,下面是参考攻略完成的

 

源代码:

 

def level_27():
	zig=GifImagePlugin.GifImageFile(ur'd:zigzag.gif')
	zigdata=zig.tostring()
	print ''.join(['%X'%(ord(i),) for i in zigdata[:10]]) # 查看前面几个字节,没有头绪

	print len(zig.getcolors())
	palette=zig.palette.getdata()[1][::3] # 获取其调色板
	print ''.join(['%X'%(ord(i),) for i in palette[:10]])

	t=string.maketrans(''.join([chr(i) for i in range(256)]),palette)
	zigtrans=zigdata.translate(t) # 用调色板值转换像素值
	print ''.join(['%X'%(ord(i),) for i in zigtrans[:10]]) # 还是看不出来什么,不过似乎转换后的数据除了第一个字节外都与原数据很相似

	print zigdata[1:]==zigtrans[:-1]
	# 尝试将两组数据中所有不相同的字节放在一起
	deltas=filter(lambda p:p[0]!=p[1],zip(zigdata[1:],zigtrans[:-1]))
	diffs=[''.join([p[i] for p in deltas]) for i in range(2)]
	print diffs[0][:20] # 看起来是个bz文件
	print diffs[1][:20]

##	bz=bz2.BZ2Decompressor().decompress(diffs[0])
	bz=bz2.decompress(diffs[0])
	print len(bz)
	print bz[:100] # 输出是python的关键字和地址 ../ring/bell.html

	keywords=bz.split(' ')
	keys={}
	for k in keywords: keys[k]=1
	print keys.keys()

	print len(keywords)
	print len(keys.keys()) # 很多关键字
	print len(keywords)/len(keys.keys())

	# 将不一样的像素按位置显示出来
	im=Image.new('1',zig.size,0)
	im.putdata([ p[0]==p[1] for p in zip(zigdata[1:],zigtrans[:-1])])
	im.show() # 有个钥匙图样,左边是not,右边是word,下面是busy?
	# 暗示 not key word
	# 那么找找上面看到的那些关键字里面哪些不是key word

	for k in keys.keys():
		if not keyword.iskeyword(k):
			print k # 打印出来 switch 和 repeat 不是关键字,../ring/bell.html不算。

	# 用switch 和 repeat 分别做用户名和密码
	k1,k2='switch','repeat'
	t=((k1,k2),(k2,k1))
	auth_handler=urllib2.HTTPBasicAuthHandler()
	opener=urllib2.build_opener(auth_handler)
	for i in t:
		try:
			auth_handler.add_password('the order matters','www.pythonchallenge.com',i[0],i[1])
			r=opener.open('http://www.pythonchallenge.com/pc/ring/bell.html')
			if r:
				print 'got, %s,%s'%(i[0],i[1]) # 正确的用户名是repeat 密码是 switch  ==> http://www.pythonchallenge.com/pc/ring/bell.html
				break
		except urllib2.HTTPError:
			pass