Continuing from this post: The Python Challenge (Levels 0-5)!
Previously on the Python Challenge, we passed the first 6 levels, 0-5. Now, let’s continue exploring the levels!
Level #6¶
http://www.pythonchallenge.com/pc/def/channel.html
Title: now there are pairs
On the page itself, there is only an image:
And in the page source, there is one seeming hint (and some information related to the author’s Paypal):
<!– <– zip –>
I have no idea how to continue from here, so I decided to change the html
into zip
and see what happens: http://www.pythonchallenge.com/pc/def/channel.zip
And surely, it’s a ZIP file!
When I open the ZIP, there seems to be 909 files named <number>.txt
and one readme.txt
that says,
welcome to my zipped list.
hint1: start from 90052
hint2: answer is inside the zip
90052.txt
:
Next nothing is 94191
94191.txt
:
Next nothing is 85503
OK, so another linked list, huh?
import io
import requests
import zipfile
r = requests.get('http://www.pythonchallenge.com/pc/def/channel.zip')
zf = zipfile.ZipFile(io.BytesIO(r.content))
n = b'90052'
while True:
with zf.open('%d.txt' % int(n)) as f:
data = f.read()
n = data.rpartition(b' ')[2]
if not n.isdigit():
print('nothing is not digit!', data, n)
break
nothing is not digit! b'Collect the comments.' b'comments.'
“Collect the comments.” Comments?
Google tells me that in a ZIP file, each file can have a “comment”. OK. Fine.
n = b'90052'
c = b''
while True:
c += zf.NameToInfo['%d.txt' % int(n)].comment
with zf.open('%d.txt' % int(n)) as f:
data = f.read()
n = data.rpartition(b' ')[2]
if not n.isdigit():
print('nothing is not digit!', data, n)
break
print(c.decode())
nothing is not digit! b'Collect the comments.' b'comments.' **************************************************************** **************************************************************** ** ** ** 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”.
http://www.pythonchallenge.com/pc/def/hockey.html:
it’s in the air. look at the letters.
Looking at the banner, I see that the letters “HOCKEY” are made of, respectively, “OXYGEN”. So that’s our new URL.
Level #7¶
https://www.pythonchallenge.com/pc/def/oxygen.html
Title: smarty
There is only an image shown on the page:
And nothing at all in the page source.
Looking closer at the image, there is a strip of greyscaled blocks in the middle. Let’s see… Maybe that contains some data?
Inspecting the image with GIMP, I see the blocks are all 7×9 pixels, except for the first one which is 5×9 pixels. There are 87 of those blocks. Now we can use Python to find out the data!
from PIL import Image # pip install pillow
r = requests.get('http://www.pythonchallenge.com/pc/def/oxygen.png')
im = Image.open(io.BytesIO(r.content))
data = []
for i in range(87):
r,g,b,a = im.getpixel((i*7, im.height//2))
assert r == g == b, 'Not greyscale pixel'
data.append(r)
data = bytes(data)
data
b'smart guy, you made it. the next level is [105, 110, 116, 101, 103, 114, 105, 116, 121]'
🙂
bytes([105, 110, 116, 101, 103, 114, 105, 116, 121])
b'integrity'
Level 8¶
https://www.pythonchallenge.com/pc/def/integrity.html
Title: working hard?
There is an image and caption:
Where is the missing link?
I realize that when I click the bee, I will be redirected to http://www.pythonchallenge.com/pc/return/good.html where I am prompted for a username and password.
In the source, there is this little blob at the end:
<!--
un: 'BZh91AY&SYA\xaf\x82\r\x00\x00\x01\x01\x80\x02\xc0\x02\x00 \x00!\x9ah3M\x07<]\xc9\x14\xe1BA\x06\xbe\x084'
pw: 'BZh91AY&SY\x94$|\x0e\x00\x00\x00\x81\x00\x03$ \x00!\x9ah3M\x13<]\xc9\x14\xe1BBP\x91\xf08'
-->
I remember from somewhere that “BZhX” is the header of a bz2 compressed blob. Is this one of them?
import bz2
un = b'BZh91AY&SYA\xaf\x82\r\x00\x00\x01\x01\x80\x02\xc0\x02\x00 \x00!\x9ah3M\x07<]\xc9\x14\xe1BA\x06\xbe\x084'
pw = b'BZh91AY&SY\x94$|\x0e\x00\x00\x00\x81\x00\x03$ \x00!\x9ah3M\x13<]\xc9\x14\xe1BBP\x91\xf08'
bz2.decompress(un), bz2.decompress(pw)
(b'huge', b'file')
OK… That was easier than I thought.
Setting the auth
variable here for future use:
auth = 'huge', 'file'
Level 9¶
http://huge:file@www.pythonchallenge.com/pc/return/good.html
Title: connect the dots
(I am going to skip the description of the level page from now on. Usually it’s an image, sometimes with a caption.)
Page source:
<!--
first+second=?
first:
146,399,163,403,170,393,169,391,166,386,170,381,170,371,170,355,169,346,167,335,170,329,170,320,170,
310,171,301,173,290,178,289,182,287,188,286,190,286,192,291,194,296,195,305,194,307,191,312,190,316,
190,321,192,331,193,338,196,341,197,346,199,352,198,360,197,366,197,373,196,380,197,383,196,387,192,
389,191,392,190,396,189,400,194,401,201,402,208,403,213,402,216,401,219,397,219,393,216,390,215,385,
215,379,213,373,213,365,212,360,210,353,210,347,212,338,213,329,214,319,215,311,215,306,216,296,218,
290,221,283,225,282,233,284,238,287,243,290,250,291,255,294,261,293,265,291,271,291,273,289,278,287,
279,285,281,280,284,278,284,276,287,277,289,283,291,286,294,291,296,295,299,300,301,304,304,320,305,
327,306,332,307,341,306,349,303,354,301,364,301,371,297,375,292,384,291,386,302,393,324,391,333,387,
328,375,329,367,329,353,330,341,331,328,336,319,338,310,341,304,341,285,341,278,343,269,344,262,346,
259,346,251,349,259,349,264,349,273,349,280,349,288,349,295,349,298,354,293,356,286,354,279,352,268,
352,257,351,249,350,234,351,211,352,197,354,185,353,171,351,154,348,147,342,137,339,132,330,122,327,
120,314,116,304,117,293,118,284,118,281,122,275,128,265,129,257,131,244,133,239,134,228,136,221,137,
214,138,209,135,201,132,192,130,184,131,175,129,170,131,159,134,157,134,160,130,170,125,176,114,176,
102,173,103,172,108,171,111,163,115,156,116,149,117,142,116,136,115,129,115,124,115,120,115,115,117,
113,120,109,122,102,122,100,121,95,121,89,115,87,110,82,109,84,118,89,123,93,129,100,130,108,132,110,
133,110,136,107,138,105,140,95,138,86,141,79,149,77,155,81,162,90,165,97,167,99,171,109,171,107,161,
111,156,113,170,115,185,118,208,117,223,121,239,128,251,133,259,136,266,139,276,143,290,148,310,151,
332,155,348,156,353,153,366,149,379,147,394,146,399
second:
156,141,165,135,169,131,176,130,187,134,191,140,191,146,186,150,179,155,175,157,168,157,163,157,159,
157,158,164,159,175,159,181,157,191,154,197,153,205,153,210,152,212,147,215,146,218,143,220,132,220,
125,217,119,209,116,196,115,185,114,172,114,167,112,161,109,165,107,170,99,171,97,167,89,164,81,162,
77,155,81,148,87,140,96,138,105,141,110,136,111,126,113,129,118,117,128,114,137,115,146,114,155,115,
158,121,157,128,156,134,157,136,156,136
-->
To proceed, first I decided to put this data into Python and see what I can get.
first = [
146,399,163,403,170,393,169,391,166,386,170,381,170,371,170,355,169,346,167,335,170,329,170,320,170,
310,171,301,173,290,178,289,182,287,188,286,190,286,192,291,194,296,195,305,194,307,191,312,190,316,
190,321,192,331,193,338,196,341,197,346,199,352,198,360,197,366,197,373,196,380,197,383,196,387,192,
389,191,392,190,396,189,400,194,401,201,402,208,403,213,402,216,401,219,397,219,393,216,390,215,385,
215,379,213,373,213,365,212,360,210,353,210,347,212,338,213,329,214,319,215,311,215,306,216,296,218,
290,221,283,225,282,233,284,238,287,243,290,250,291,255,294,261,293,265,291,271,291,273,289,278,287,
279,285,281,280,284,278,284,276,287,277,289,283,291,286,294,291,296,295,299,300,301,304,304,320,305,
327,306,332,307,341,306,349,303,354,301,364,301,371,297,375,292,384,291,386,302,393,324,391,333,387,
328,375,329,367,329,353,330,341,331,328,336,319,338,310,341,304,341,285,341,278,343,269,344,262,346,
259,346,251,349,259,349,264,349,273,349,280,349,288,349,295,349,298,354,293,356,286,354,279,352,268,
352,257,351,249,350,234,351,211,352,197,354,185,353,171,351,154,348,147,342,137,339,132,330,122,327,
120,314,116,304,117,293,118,284,118,281,122,275,128,265,129,257,131,244,133,239,134,228,136,221,137,
214,138,209,135,201,132,192,130,184,131,175,129,170,131,159,134,157,134,160,130,170,125,176,114,176,
102,173,103,172,108,171,111,163,115,156,116,149,117,142,116,136,115,129,115,124,115,120,115,115,117,
113,120,109,122,102,122,100,121,95,121,89,115,87,110,82,109,84,118,89,123,93,129,100,130,108,132,110,
133,110,136,107,138,105,140,95,138,86,141,79,149,77,155,81,162,90,165,97,167,99,171,109,171,107,161,
111,156,113,170,115,185,118,208,117,223,121,239,128,251,133,259,136,266,139,276,143,290,148,310,151,
332,155,348,156,353,153,366,149,379,147,394,146,399
]
second = [
156,141,165,135,169,131,176,130,187,134,191,140,191,146,186,150,179,155,175,157,168,157,163,157,159,
157,158,164,159,175,159,181,157,191,154,197,153,205,153,210,152,212,147,215,146,218,143,220,132,220,
125,217,119,209,116,196,115,185,114,172,114,167,112,161,109,165,107,170,99,171,97,167,89,164,81,162,
77,155,81,148,87,140,96,138,105,141,110,136,111,126,113,129,118,117,128,114,137,115,146,114,155,115,
158,121,157,128,156,134,157,136,156,136
]
len(first), len(second), (min(first), max(first)), (min(second), max(second))
(442, 112, (77, 403), (77, 220))
I’m somewhat stuck here for a while, not knowing what to do. Then I went back to the problem page: the title is “connect the dots”. What if… The first
and second
arrays are coordinates on the image? And I should connect them?
from PIL import ImageDraw
r = requests.get('http://www.pythonchallenge.com/pc/return/good.jpg', auth=auth)
im = Image.open(io.BytesIO(r.content))
d = ImageDraw.Draw(im)
for i in range(0, len(first)-2, 2):
d.line(((first[i], first[i+1]), (first[i+2], first[i+3])), fill=(255,0,0))
im
That’s a.. bull?
for i in range(0, len(second)-2, 2):
d.line(((second[i], second[i+1]), (second[i+2], second[i+3])), fill=(255,0,0))
im
Yes, I am sure that is a bull.
Level 10¶
http://www.pythonchallenge.com/pc/return/bull.html
Title: what are you looking at?
len(a[30]) = ?
Clicking on the bull leads to this text page, http://www.pythonchallenge.com/pc/return/sequence.txt:
a = [1, 11, 21, 1211, 111221,
Looking at the array, I notice that each element has a length of a multiple of 2 (except for the first element). How does the next element come from the previous one?
……
OK, I think I get it. After many times of trial and error, I found out that 11 means the previous term has “one 1”, 21 means there are “two 1s”, 1211 means there are “one 2, one 1” and so on.
Googling this sequence, it is actually called the “Look-and-say sequence”. (Wikipedia, OEIS) So, OK. This is a math problem. Let’s do it with Python.
a = '1'
for i in range(1, 31):
b = ''
c = 1
for j in range(1, len(a)):
if a[j] != a[j-1]:
b += '%d%s' % (c, a[j-1])
c = 1
else:
c += 1
b += '%d%s' % (c, a[-1])
a = b
# print('a[%d]: %s' % (i, a))
print('len(a[%d]): %d' % (i, len(a)))
len(a[1]): 2 len(a[2]): 2 len(a[3]): 4 len(a[4]): 6 len(a[5]): 6 len(a[6]): 8 len(a[7]): 10 len(a[8]): 14 len(a[9]): 20 len(a[10]): 26 len(a[11]): 34 len(a[12]): 46 len(a[13]): 62 len(a[14]): 78 len(a[15]): 102 len(a[16]): 134 len(a[17]): 176 len(a[18]): 226 len(a[19]): 302 len(a[20]): 408 len(a[21]): 528 len(a[22]): 678 len(a[23]): 904 len(a[24]): 1182 len(a[25]): 1540 len(a[26]): 2012 len(a[27]): 2606 len(a[28]): 3410 len(a[29]): 4462 len(a[30]): 5808
Great! The answer is 5808.
Level 11¶
https://huge:file@www.pythonchallenge.com/pc/return/5808.html
Title: odd even
There is nothing in the source worth noting.
Oh geez… This level is hard! It looks hard at least.
“odd even”… What does that mean?
Looking at the image closer, I realize that… how can I explain this… there are blanks between two pixels in the image. What if we remove the blanks?
r = requests.get('http://www.pythonchallenge.com/pc/return/cave.jpg', auth=auth)
im = Image.open(io.BytesIO(r.content))
im2 = Image.new('RGB', (im.width//2, im.height))
for y in range(im.height):
for x in range(im.width//2):
im2.putpixel((x,y), im.getpixel((x*2+y%2,y)))
im2
Alright! There is a faint text in the top right corner that says “evil”.
Level 12¶
http://huge:file@www.pythonchallenge.com/pc/return/evil.html
Title: dealing evil
In the source, interestingly, the image’s filename is evil1.jpg
:
<img src="evil1.jpg">
So… What is evil2.jpg
?
Sure! I changed the .jpg
suffix to .gfx
, but my browser did not recognize that format and downloaded the file for me.
Running file
didn’t help too much:
$ file evil2.gfx
evil2.gfx: data
Fine. What can I do then?
I Googled gfx file
, and this is what Google tells me:
A GFX file is an animation file used by video games, such as Batman: Arkham Asylum and Mass Effect. It contains vector and raster graphics in a format similar to the . SWF file format. GFX files also may include ActionScript interactive actions.
Source: fileinfo.com
… Not useful at all.
Are there any more evils?
I tried http://www.pythonchallenge.com/pc/return/evil3.jpg and got:
Sure… But it seems weird that another image is included but tells me there are no more… This seems off.
http://www.pythonchallenge.com/pc/return/evil4.jpg gave me an empty image… It’s weird that it didn’t just throw a 404 NOT FOUND
but instead gave me an OK response. Maybe it’s NOT actually an image?
r = requests.get('http://www.pythonchallenge.com/pc/return/evil4.jpg', auth=auth)
r.content
b'Bert is evil! go back!\n'
Bert is evil…? Who’s Bert?
http://www.pythonchallenge.com/pc/return/bert.html:
Yes! Bert is evil!
That’s… not helpful?
I remembered that I still have a evil2.gfx
untouched. I feel like there’s something there.
Let’s take a closer look.
r = requests.get('http://www.pythonchallenge.com/pc/return/evil2.gfx', auth=auth)
r.content[:10], len(r.content)
(b'\xff\x89G\x89\xff\xd8PIP\xd8', 67575)
Fine, still not helping me.
Going back to the problem page… A person dealing cards into 5 piles? The length of the GFX file is also 5? Something clicked in my brain: what if we deal the file?
data = [b'' for i in range(5)]
for i in range(5):
data[i%5] = r.content[i::5]
data[0][:10]
b'\xff\xd8\xff\xe0\x00\x10JFIF'
Great! It’s an image file (I saw the JFIF header)!
Let’s open these images and take a look…
from PIL import ImageFile
ImageFile.LOAD_TRUNCATED_IMAGES = True # To prevent OSError: image file is truncated
for d in data:
display(Image.open(io.BytesIO(d)))
Disproportionality! That’s our next level!
Level 13¶
http://huge:file@www.pythonchallenge.com/pc/return/disproportional.html
Title: call him
phone that evil
Clicking on the “5” in the image leads us to http://www.pythonchallenge.com/pc/phonebook.php, where there is apparently an XML file…
<?xml version="1.0"?>
<methodResponse>
<fault>
<value>
<struct><member><name>faultCode</name>
<value><int>105</int></value>
</member>
<member>
<name>faultString</name>
<value><string>XML error 5: empty document</string></value>
</member>
</struct>
</value>
</fault>
</methodResponse>
Googling up “xml faultcode 105” yields this StackOverflow question https://stackoverflow.com/q/7950297 where the answer states:
Are you just ‘browsing’ to the address of the xmlrpc folder? It looks from the response like you didn’t submit an xml ‘request’ document to the url. XMLRPC requires that you post a request in the form of commands via an xml file.
Oh. So this is XMLRPC… which is? Googling (again) “xmlrpc python”:
XML-RPC is a Remote Procedure Call method that uses XML passed via HTTP as a transport.
Great! So I just need to use that Python library!
import xmlrpc.client
xr = xmlrpc.client.ServerProxy('http://www.pythonchallenge.com/pc/phonebook.php')
xr.system.listMethods()
['phone', 'system.listMethods', 'system.methodHelp', 'system.methodSignature', 'system.multicall', 'system.getCapabilities']
xr.system.methodHelp('phone')
'Returns the phone of a person'
So I want to “phone” Bert, the evil we found out in the previous level?
xr.phone('Bert')
'555-ITALY'
“555-ITALY”? So the next level is “italy”!
That’s it for today, more coming soon!
Made with Jupyter Notebook!