PythonChallenge解谜

写在前面

刷B站的时候发现有up主做互动视频是说编程解谜的,结果我在他视频的开头部分找到了解谜网站的地址就兴冲冲地过来自己玩起来了。在解谜的过程中发现还是学到了新的东西,于是干脆用这篇博客记录一下解谜的过程和答案。题目的话呢,大家自行到原网站去看就好啦!

Challenge 0

这个题目用来热身还是不错的,新手友好。
提示很简单直接:尝试改网址。
尽管图片的提示写着 2 38 2^{38} 238,我还是先将URL中的0.html改成了1.html,发现了第一个彩蛋:

2**38 is much much larger.

而当我试着将数字继续修改为2,3甚至-1时,网页给出的结果都是枯燥的404,不再赘述。
其实出题人还是很用心的,当直接将URL的尾部修改为2**38.html时,你会看到

give the answer, not the question.

至此,通过这一关的方法很清楚了,就是将2**38的计算结果,也就是274877906944替换掉URL中的0就可以了。
后来我才知道,还有一个彩蛋是238.html,然后看到

No... the 38 is a little bit above the 2...

哈哈哈…

Challenge 1

首先映入眼帘的是一张图片和下面的一串乱码,显然需要搞清楚这串乱码(密文)隐藏了什么信息。
不过作为第1关,难度很低,从图片中直接获得了密钥信息:将所有的字母按照字母顺序后移两位。
有两个需要注意的点(不知道是不是作者的那句“做前三思”的提示是不是指这个):

  • 密文中有非字母的字符
  • ‘y’和‘z’应该分别对应到‘a’和‘b’

给出我的初始答案

puzzle = "g fmnc wms bgblr rpylqjyrc gr zw fylb. rfyrq ufyr amknsrcpq ypc dmp." \
         "bmgle gr gl zw fylb gq glcddgagclr ylb rfyr'q ufw rfgq rcvr gq qm jmle. " \
         "sqgle qrpgle.kyicrpylq() gq pcamkkclbcb. lmu ynnjw ml rfc spj."
answer = ''.join([chr(ord(c) + 2) if c.isalpha() else c for c in puzzle])
answer.replace('{', 'a').replace('|', 'b')         

得到明文

"i hope you didnt translate it by hand. thats what computers are for.doing it in by hand is inefficient and that's why this text is so long. using string.maketrans() is recommended. now apply on the url."

好的,出题人想让你学会用string.maketrans()这个字符串的方法。不过对于Python3string.maketrans()被内建函数所取代。下面是这个使用该内建函数的代码

import string
alphab = string.ascii_lowercase
trans_law = str.maketrans(alphab, alphab[2:] + alphab[:2])
puzzle.translate(trans_law)

显然,这个方法优雅了很多,从这里还学到了maketrans()更多的用法。
按照破译出的提示,还需要将密钥作用到URL上,于是map被修改成ocr,进入下一关。

Challenge 2

看到“ocr”这个词的时候就知道题目跟文本识别有关系,不过这个关系原来只是个噱头,还是要看网页源码(作者贴心的将“MAYBE”大写了,图片那么模糊反正也不会有人真的去识别图片啦),在源码的注释里果然有玄机,注释完整内容就不贴在这里了,

find rare characters in the mess below:

就是要从一串乱码里找到字符拼出内容来咯。

answer = ''.join([c for c in puzzle if c.isalpha()])
print(answer)

答案是equality,从前面两道题目我们已经知道还是继续修改URL来进入下一关。

Challenge 3

根据提示,要找到一个小写字母,这个字母两侧都要紧邻有且只有三个大写字母。
图片的作用完全就是配个插图,整个页面也是很简单,所以还是从网页源代码里找,果然又是一长串的字符。
用正则来找一下

import re
answer = re.findall(re.compile('[a-z][A-Z]{3}[a-z][A-Z]{3}[a-z]'), puzzle)
print(answer)

为了保证是“有且仅有”,因此匹配的字符串样式的首尾都要加一个小写字母。然后发现原来答案不只一个

['qIQNlQSLi',
 'eOEKiVEYj',
 'aZADnMCZq',
 'bZUTkLYNg',
 'uCNDeHSBj',
 'kOIXdKBFh',
 'dXJVlGZVm',
 'gZAGiLQZx',
 'vCJAsACFl',
 'qKWGtIDCj']

依次连接正中间的小写字母,得到linkedlist,即为通往下一关的钥匙。

Challenge 4

linkedlist.html导向的网页显示

linkedlist.php

OK,我们来访问linkedlist.php,找到本关的题目。
在网页源码中看到了如下提示

urllib may help. DON'T TRY ALL NOTHINGS, since it will never 
end. 400 times is more than enough.

urllib这是要请求网页了。
并且发现当前图片其实是一个跳转链接linkedlist.php?nothing=12345,尝试点击图片。新页面给出的信息如下

and the next nothing is 44827

继续把URL修改为linkedlist.php?nothing=44827,得到

and the next nothing is 45439

至此,我们可以确定后面很多很多页面都将是类似的结构。但是我们不知道最终要获取的信息具体是什么格式,所以暂且将所有页面内容都输出。
之前写爬虫还是习惯用requests

import requests

URL_PREFIX = 'http://www.pythonchallenge.com/pc/def/linkedlist.php'

def get_page(_code):
    r = requests.get(url=URL_PREFIX, params={'nothing': _code})
    return r.text
    
code = 12345
for i in range(400):
    page = get_page(code)
    match = page.find('is')
    code = page[match+3:]
    print(page)

在请求到350+次时,得到peak.html,此即下一关的URL。
不过在进入下一关前,我想分享一下我遇到的一些情况:
第三次(其实作者是为手动改URL的人准备的提示)请求

  • nothing=45439
<font color=red>Your hands are getting tired </font>and the next nothing is 94485

接下来请求了80+次得到:

  • nothing=16044
Yes. Divide by two and keep going.

将初始的12345替换为8022之后,请求50+次时出现

  • nothing=82682
There maybe misleading numbers in the
text. One example is 82683. Look only for the next nothing and the next nothing is 63579

看来我代码里面去匹配‘is’这个单词是不够的,anyway,不影响继续闯关也就无关紧要了。

Challenge 5

这一关本来对Pythoner来说是秒杀,因为题目是问peak hell的读音像什么词,那显然是pickle了。但是当请求pickle.html时,除了得到

yes! pickle!

就什么也没有了…嗯??不是说有33关吗?看来还另有玄机。
回到原来的页面peak.html寻找线索,但是这个页面也是简单得不能再简单了,也没什么跳转链接…第一次卡关出现了…
沉淀了几天,再回来思考一下这一关。重新注意到peak.html的源码中的这个标签

<peakhell src="banner.p">
<!-- peak hell sounds familiar ? -->
</peakhell>

显然<peakhell></peakhell>是作者自定义的一个标签,看来线索只可能是banner.p,结合这一关的核心内容它应该是一个.pickle文件,可以先来看一下http://www.pythonchallenge.com/pc/def/banner.p是什么内容

(lp0
(lp1
(S' '
.
.
.
I95
tp373
aa.

中间的省略号是一大串相似的内容,完全没有头绪。
现在尝试用Python来获取它的内容:

import pickle
import requests

URL = 'http://www.pythonchallenge.com/pc/def/banner.p'

r = requests.get(URL) 
with open("banner.p",'wb') as fw:
    fw.write(r.content)
with open("banner.p", "rb") as fr:
    content = pickle.load(fr)

content是一个元素个数为len(content) == 23的列表:

[[(' ', 95)],
 [(' ', 14), ('#', 5), (' ', 70), ('#', 5), (' ', 1)],
 [(' ', 15), ('#', 4), (' ', 71), ('#', 4), (' ', 1)],
... ...
 [(' ', 6), ('#', 3), (' ', 5), ('#', 6), (' ', 4), ('#', 5), (' ', 4), ('#', 2), (' ', 4), ('#', 4), (' ', 1), ('#', 6), (' ', 4), ('#', 11), (' ', 4), ('#', 5), (' ', 6), ('#', 3), (' ', 6), ('#', 6)],
 [(' ', 95)]]

观察后不难发现,列表中的每个元素都是一个一维列表,其元素是长度为2的元组。个元组的0号元是“”和“#”中的一个,每个列表中的1号元的数字之和均为95。看来是一个二值图,这里可以用一行代码来解决:

print('\n'.join([''.join([element[0] * element[1] for element in content[irow]]) for irow in range(len(content))]))

输出是


              #####                                                                      ##### 
               ####                                                                       #### 
               ####                                                                       #### 
               ####                                                                       #### 
               ####                                                                       #### 
               ####                                                                       #### 
               ####                                                                       #### 
               ####                                                                       #### 
      ###      ####   ###         ###       #####   ###    #####   ###          ###       #### 
   ###   ##    #### #######     ##  ###      #### #######   #### #######     ###  ###     #### 
  ###     ###  #####    ####   ###   ####    #####    ####  #####    ####   ###     ###   #### 
 ###           ####     ####   ###    ###    ####     ####  ####     ####  ###      ####  #### 
 ###           ####     ####          ###    ####     ####  ####     ####  ###       ###  #### 
####           ####     ####     ##   ###    ####     ####  ####     #### ####       ###  #### 
####           ####     ####   ##########    ####     ####  ####     #### ##############  #### 
####           ####     ####  ###    ####    ####     ####  ####     #### ####            #### 
####           ####     #### ####     ###    ####     ####  ####     #### ####            #### 
 ###           ####     #### ####     ###    ####     ####  ####     ####  ###            #### 
  ###      ##  ####     ####  ###    ####    ####     ####  ####     ####   ###      ##   #### 
   ###    ##   ####     ####   ###########   ####     ####  ####     ####    ###    ##    #### 
      ###     ######    #####    ##    #### ######    ###########    #####      ###      ######
                                                                                              ​

也就是channel.html,过关!

Challenge 6

作者解释了打赏的部分和谜题无关(不过至少知道了作者的署名是thesamet),那么这一关的信息也很有限:一张“拉链”的图片。
注意到网页源码中的注释

<-- zip

查看zip.html

yes. find the zip.

zip除了表示拉链的意思,还是一种常用的压缩包的格式,因此这里可能是在提示去找压缩包。
尝试直接把channel.html改成channel.zip。果然浏览器就帮我自动下载了这个名称的压缩包。解压并打开获得的文件目录,里面居然有910个.txt文件,除了一个readme.txt外,其余都是一串数字命名的。
打开readme.txt

welcome to my zipped list.

hint1: start from 90052
hint2: answer is inside the zip

而且随机打开一个其他文件,都是Next nothing is xxx。这套路跟Challenge 4一样啊。不过既然我都已经把所有文件下载到本地了,似乎没有必要按照它要求的顺序遍历文件,尽管从代码量上也没什么明显区别。

import os

txts = os.listdir('~/Downloads/channel/')
for txt in txts:
    with open('~/Downloads/channel/' + txt, 'r') as f:
        print(txt, f.readlines())

不一样的是46145.txt

Collect the comments.

收集注释?readme.txt中的第二条提示还没有使用。这里涉及到一个知识点,zip文件是可以添加注释的(比方说SOF上的这个问题)。

import zipfile
import re

zf = zipfile.ZipFile('~/Downloads/channel.zip', 'r')
code = 90052
while code: 
    content = str(zf.read(f'{code}.txt'))
    match = re.search('Next nothing is ([0-9]+)', content)
    print(zf.getinfo(f'{code}.txt').comment.decode('utf-8'), end='')
    try:
        code = match.group(1)
    except AttributeError:
        break

输出结果是:

****************************************************************
****************************************************************
**                                                            **
**   OO    OO    XX      YYYY    GG    GG  EEEEEE NN      NN  **
**   OO    OO  XXXXXX   YYYYYY   GG   GG   EEEEEE  NN    NN   **
**   OO    OO XXX  XXX YYY   YY  GG GG     EE       NN  NN    **
**   OOOOOOOO XX    XX YY        GGG       EEEEE     NNNN     **
**   OOOOOOOO XX    XX YY        GGG       EEEEE      NN      **
**   OO    OO XXX  XXX YYY   YY  GG GG     EE         NN      **
**   OO    OO  XXXXXX   YYYYYY   GG   GG   EEEEEE     NN      **
**   OO    OO    XX      YYYY    GG    GG  EEEEEE     NN      **
**                                                            **
****************************************************************
 **************************************************************

结果是hockey,有点意思,当访问hockey.html时,却得到

it's in the air. look at the letters.

回来再仔细看看上面的hockey字符图片,原来每个字母都是由另外的一个字母组成的,而这些“飘在空气中”的字母组成的单词是oxygen(这个双关很赞),这才是这关的最终答案。很有意思。

Challenge 7

来了来了它来了,我就知道会有在图片像素上做文章的题目。
http://www.pythonchallenge.com/pc/def/oxygen.png的页面标题居然给出了图片的大小:oxygen.png (629×95)。加载这个图片的时间相对较长,所以玄机就在图片之中。
未完待续…

答案总结如下

题目序号下一关的关键词彩蛋
027487790694412382**38
1ocr
2equality
3linkedlist
4peak(nothing=) 454391604482682
5channelpickle
6oxygenziphockey
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值