Python Challenge 26의 Url은 다음과 같다

Python challenge 26 : http://www.pythonchallenge.com/pc/hex/bin.html


구성

엄허; 부끄러운 짤이 우리를 반겨준다. 영어를 해석하면

"서두르세요. 배를 놓칠 거 같습니다."

이다. 특별한 증거가 보이지 않으니, 저기 뭐냐 주석으로 넘어가자

주석에 힌트가 쓰여있는대로면 우리는 그의 이메일을 받았을 것이라고 한다. 우선 사과를 해야 될 대상을 떠올려보자. 필자가 잘살았다고 떵떵거리진 않아도 하늘 아래 부끄러움 없이 살았는데, 뭐지? 과거를 돌아볼 필요가 있을 거 같다. 


해결 아이디어

2021.06.03 - [Python/Python Challenge] - [Python Challenge 19] You are An Idiot!

 

[Python Challenge 19] You are An Idiot!

Python Challenge 19의 Url은 다음과 같다 Python challenge 19 : http://www.pythonchallenge.com/pc/hex/bin.html 구성 인도 사진이다. 특별한 게 안보이니까 주석을 보자 MIME Type의 e-mail이 보인다. 아래..

tutoreducto.tistory.com

이 문제의 마지막 레오폴드씨의 말을 기억하는가

그니까 우리가 뭘잘못했는지는 모르겠는데, 사과해야 된다고 한다. 사과하는 방법은 이메일로 하면 될 거 같다. 이메이 릉 19번 문제의 주석에 있다.

레오폴드 씨의 이메일은 leopold.moz@pythonchallenge.com이다. 메일을 보내자 얼마지나지 않아 다음과 같은 답변을 받을 수 있다.

일단 24문제를 풀었던 파일중 mybroken.zip이라는 파일이 있었다. 파일이 손상된 파일이었는데, 어딘가 해시로 장난질을 하는 게 있나 보다. 우선 해시를 저런 식으로 보냈다는 것은 정상적인 파일의 해시값이 저 모양이라는 것이다. 한번 mybroken.zip의 해시를 구해보자

### 26_1.py

import hashlib

if __name__=="__main__" :

    with open("mybroken.zip","rb") as f:
        data = f.read()

    print(hashlib.md5(data).hexdigest())

결과는 "bbf6616928e23ecfef4b717f281c53cc" 이다. 레오폴드 씨가 보낸 해시랑은 다르다. 파일 또한 열어보면 어딘가 손상이 되었다는 안내를 받을 수 있다. 보통 이런 복구는 쉬운 일이 아닌데, Python Challenge는 다행히도, 한 바이트만 손상시켜놨다.(뭐 어딘지 모르고 뭔지 모르는 게 문제이긴 한데, 그건 때려보자)

 

### 26_2.py

import hashlib

if __name__=="__main__" :

    tar = "bbb8b499a0eef99b52c7f13f4e78c24b"
    stop = False

    with open("mybroken.zip","rb") as f:
        ori_dat = f.read()

    for i in range(len(ori_dat)) :
        if(stop==True) :
            break
        for b in range(256) :
            tmp = ori_dat[:i]+bytes([b])+ori_dat[i+1:]
            if(hashlib.md5(tmp).hexdigest()==tar) :
                with open("notbroken.zip","wb") as f:
                    f.write(tmp)
                    stop = True

결과는 다음과 같은 사진이다.

스삐드! 위에 있던 문자열인 boat를 합치면 speedboat가 된다. 이게 답이다.

Answer Url : http://www.pythonchallenge.com/pc/hex/speedboat.html 

* 이건 여담인데, zip 파일은 단순하게 CRC가 안 맞아서 안 열리는 거기 때문에, 요즘의 zip/unzip프로그램에서 대부분 해석해서 그냥 연다.

개그다.

Python Challenge 25의 Url은 다음과 같다

Python challenge 25 : hhttp://www.pythonchallenge.com/pc/hex/lake.html


구성

퍼즐이다. 퍼즐이라고 하면 어릴 때 재미있게 놀았던 기억이 새록새록 떠오른다. 특별한 점이 안보이니, 주석을 살펴보자

주석에 힌트가 있다. "파도들이 보이시나요?" 라는 건데, 어떻게 풀어야 할까


해결 아이디어

일단 waves니까 wave가 여러 개 있는 거 같다. 이미지의 이름이 lake1.jpg인 것을 미루어보아, 여러 개의 파일이 있을 줄 알았는데, 웬걸?

없단다. wave라니까 wav 확장자로 한번 들어가 볼까?

정확했다. 몇 번까지 있는지 모르겠으니 다운로드하여서 wav 폴더 안에 넣어두는 코드를 작성해 보자

### 25_1.py

from requests import *

if __name__=="__main__" :
    url = "http://www.pythonchallenge.com/pc/hex/lake"
    cnt = 1

    while(True) :
        new_url = url + str(cnt) + ".wav"
        res = get(new_url,auth=("butter","fly"))
        
        if(res.status_code!=200) :
            break
        
        with open("./25_wav/"+str(cnt)+".wav","wb") as f:
            f.write(res.content)

        cnt += 1

성공적인 결과는 아래와 같이 25개의 음성(wav확장자) 파일을 가질 수 있다.

파일크기는 전부 동일하다. 여기서 더 나아갈 수 있는 힌트는 기존 사진인 lake1.jpg에서 찾을 수 있다.

퍼즐처럼 이렇게 들어간다는 거다. 그러면 어떻게 넣을 수 있을까?

해결 코드에 사용된 주요 함수는 다음과 같다., 

  • wave.getnframe() : wave파일에 몇 개의 프레임이 들어있는지 확인
  • wave.readframe(frame) : frame만큼의 크기를 읽어 .byte로 반환
  • Image.frombytes(mode, size, bytes) : 새로운 이미지를 생성하는데, bytes에서 읽어 온다. 크기가 넘어가면 자른다. 아래 참조

https://www.geeksforgeeks.org/python-pil-image-frombytes-method/

 

Python PIL | Image.frombytes() Method - GeeksforGeeks

A Computer Science portal for geeks. It contains well written, well thought and well explained computer science and programming articles, quizzes and practice/competitive programming/company interview Questions.

www.geeksforgeeks.org

* Image.frombytes(mode, size, bytes)의 사용법은 위에 링크로 올린다. 크기가 넘어가면 잘리는 example을 볼 수 있다.

 

이를 이용한 해결 코드는 다음과 같다. 

### 25_2.py

from PIL import Image
import wave

if __name__=="__main__" :
    url = "http://www.pythonchallenge.com/pc/hex/lake"
    cnt = 1

    wavs = [wave.open("./25_wav/"+str(x)+".wav") for x in range(1,26) ]
    ans = Image.new('RGB',(300,300))
    num_frames = wavs[0].getnframes()
    
    for index, w in enumerate(wavs) :
        tmp = w.readframes(num_frames)
        piece = Image.frombytes('RGB',(60,60),tmp)
        ans.paste(piece,(60*(index%5),60*(index//5)))
        
    ans.save("ans.png")

결과는 다음과 같다.

찾았다. decent

Answer Url : http://www.pythonchallenge.com/pc/hex/decent.html

Python Challenge 24의 Url은 다음과 같다

Python challenge 24 : http://www.pythonchallenge.com/pc/hex/ambiguity.html


구성

오우! 쒰! 미로다. 픽셀이 깨져 보이는 지경에 이르렀다. 일단 특별한 점이 안 보인다. 주석에도 특별한 게 안 보인다. 다운로드한 사진의 이름이 maze.png인 걸로 봐서 미로를 풀어야 되는 거 같다.


해결 아이디어

우선 미로라면 당연히 미로의 시작과 끝이 있을 것이다. 그거부터 찾아보자. 기본적인 아이디어는 경계선에 끝과 끝에 있을 것으로 생각이 된다.

### 24_1.py

from PIL import Image

if __name__=="__main__" :

    img = Image.open("maze.png")

    for x in range(img.width) :
        bottom = img.getpixel((x,img.height-1))
        entry = img.getpixel((x,0))
        print(entry,bottom)

    for y in range(img.height) :
        left = img.getpixel((img.width.0))
        right = img.getpixel((img.width-1,y))
        print(left,right)

그렇긴 한데 허허허... 이미지가 너무 크다. (255,255,255,255)가 벽인걸로 보이니까 그걸 빼고 탐색을 해보자.

 

### 24_2.py

from PIL import Image

if __name__=="__main__" :

    img = Image.open("maze.png")
    wall = (255,255,255,255)

    for x in range(img.width) :
        bottom = img.getpixel((x,img.height-1))
        top = img.getpixel((x,0))
        if(bottom!=wall or top!=wall) :
            print(x, top, bottom)

    for y in range(img.height) :
        left = img.getpixel((img.width,0))
        right = img.getpixel((img.width-1,y))
        if(left != wall or right != wall) :
            print(y,left,right)

결과로 나온 값중에 (0,0,0,255)인 값이 있다. top, bottom의 (width-2, 0) 와 (1, height-1)이다. 일단 미로를 풀어야 되는 건 맞는 거 같다. 미로 값들의 픽셀을 구해서 배열에 넣어보자.

탐색 알고리즘의 가장 대표적인 2가지 BFS와 DFS 중에 미로를 탐색하기 좋은 알고리즘은 BFS이다. 그래 코드 한번 짜봦

### 24_2.py

from PIL import Image
import queue

def bfs_check(pos,max_x,max_y,alr) :
    if(pos[0]>=0 and pos[0]<max_x and
       pos[1]>=0 and pos[1]<max_y and
       pos not in alr) :
        return True
    else :
        return False
    
if __name__=="__main__" :

    img = Image.open("maze.png")

    d = [(0,1),(0,-1),(1,0),(-1,0)]
    width, height = img.size
    wall = (255,255,255,255)

    ent = (width-2,0)
    ext = (1,height-1)
    print(img.getpixel(ent),img.getpixel(ext))

    cur_q = queue.Queue()
    cur_q.put(ext)

    ans_dict = dict()

    while(cur_q.empty()==False) :
        cur_pos = cur_q.get()

        if(cur_pos == ent) :
            break

        for a in d :
            next_pos = (cur_pos[0] + a[0], cur_pos[1] + a[1])
            if(bfs_check(next_pos,width,height,ans_dict)) :
                if(img.getpixel(next_pos) != (255,255,255,255)) :
                   ans_dict[next_pos] = cur_pos
                   cur_q.put(next_pos)

    ans = list()
    while(cur_pos!=ext) :
        ans.append(img.getpixel(cur_pos))
        cur_pos = ans_dict[cur_pos]

자 코드가 길다고 볼 수도 있는데, 필자같이 파이선을 잘 못하는 사람들을 위해서 플로우 차트를 그려주겠다. 다음과 같다. 

그런데, 경로를 탐색하다 보면 다음과 같은 특징이 있다.

  • 픽셀은 (n,0,0,255)로 이루어져 있다. n은 모른다. 1 <n <255의 값이다.
  • 맨 처음 다음부터 홀수번째에만 n이 값이 있다. 짝수는 0이다.

그래서 그 값들을 보면 PK로 시작을 한다. (zip 파일의 헤더) 따라서 byte로 변환을 하기 위한 코드를 다음과 같이 작성하자

### 24_3.py

from PIL import Image
import queue

def bfs_check(pos,max_x,max_y,alr) :
    if(pos[0]>=0 and pos[0]<max_x and
       pos[1]>=0 and pos[1]<max_y and
       pos not in alr) :
        return True
    else :
        return False
    
if __name__=="__main__" :

    img = Image.open("maze.png")

    d = [(0,1),(0,-1),(1,0),(-1,0)]
    width, height = img.size
    wall = (255,255,255,255)

    ent = (width-2,0)
    ext = (1,height-1)
    print(img.getpixel(ent),img.getpixel(ext))

    cur_q = queue.Queue()
    cur_q.put(ext)

    ans_dict = dict()

    while(cur_q.empty()==False) :
        cur_pos = cur_q.get()

        if(cur_pos == ent) :
            break

        for a in d :
            next_pos = (cur_pos[0] + a[0], cur_pos[1] + a[1])
            if(bfs_check(next_pos,width,height,ans_dict)) :
                if(img.getpixel(next_pos) != (255,255,255,255)) :
                   ans_dict[next_pos] = cur_pos
                   cur_q.put(next_pos)

    ans = list()
    while(cur_pos!=ext) :
        ans.append(img.getpixel(cur_pos)[0])
        cur_pos = ans_dict[cur_pos]

    ans = ans[1::2]

    with open("24","wb") as f:
        f.write(bytearray(ans))
    img.close()

성공이다. 24.zip을 압축 해제하면 lake라는 사진과 mybroken이라는 압축파일이 나온다.

일단 풀었다. lake 찾았다.

Answer Url : http://www.pythonchallenge.com/pc/hex/lake.html

Python Challenge 23의 Url은 다음과 같다

Python challenge 23 : http://www.pythonchallenge.com/pc/hex/bonus.html


구성

화면에는 누렁이가 있다. 특별한 건 안보이니까 주석을 보자

영어는 다음과 같이 써있다.

  • 해야 할 일 : 사과를 구해야 할 일이 있었나요? 지금이 바로 그 사람에게 사과를 할 좋은 시간이빈다. 이 레벨에서 할일이 딱히 없더라도 그사람에게 좋은 매너를 보여주세요
  • 찾을 수 없을 거예요. 이건 문서화 안된 모듈입니다.
  • 'va gur snpr bs jung?'

으흠; 해결 아이디어를 바로 만나보자


해결 아이디어

  • 'va gur snpr bs jung?'

이거의 해석이 먼저이다.

정말 출제자에게 미안한 이야기이지만, 보자마자 rot13이겠다 싶었다. 너무 영어 문장처럼 생겼다. Python code로 짜는 거 정도야 아주 예전에 풀었던 문제에서 할 수 있지만, 우리 조금 출제자의 의도대로 움직여 주자. 두 번째 주석인 문화 되지 않은 모듈이라는 점에서 힌트를 얻자. 문서화되지 않은 모듈 중 rot13과 밀접하게 동작을 하는 모듈은 의외로 Python principle이라고 여겨지는 'this'모듈이다.

>>> import this
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!

뭐 그렇다. 특히 이 this라는 모듈은 this.s라는 클래스 문자열을 가지고 있는데, 

>>> print(this.s)
Gur Mra bs Clguba, ol Gvz Crgref

Ornhgvshy vf orggre guna htyl.
Rkcyvpvg vf orggre guna vzcyvpvg.
Fvzcyr vf orggre guna pbzcyrk.
Pbzcyrk vf orggre guna pbzcyvpngrq.
Syng vf orggre guna arfgrq.
Fcnefr vf orggre guna qrafr.
Ernqnovyvgl pbhagf.
Fcrpvny pnfrf nera'g fcrpvny rabhtu gb oernx gur ehyrf.
Nygubhtu cenpgvpnyvgl orngf chevgl.
Reebef fubhyq arire cnff fvyragyl.
Hayrff rkcyvpvgyl fvyraprq.
Va gur snpr bs nzovthvgl, ershfr gur grzcgngvba gb thrff.
Gurer fubhyq or bar-- naq cersrenoyl bayl bar --boivbhf jnl gb qb vg.
Nygubhtu gung jnl znl abg or boivbhf ng svefg hayrff lbh'er Qhgpu.
Abj vf orggre guna arire.
Nygubhtu arire vf bsgra orggre guna *evtug* abj.
Vs gur vzcyrzragngvba vf uneq gb rkcynva, vg'f n onq vqrn.
Vs gur vzcyrzragngvba vf rnfl gb rkcynva, vg znl or n tbbq vqrn.
Anzrfcnprf ner bar ubaxvat terng vqrn -- yrg'f qb zber bs gubfr!

이렇다. 이 문자열은 rot13으로 암호화되어있으며, 해독을 위해서는 this.d딕셔너리에 매핑해야 된다.

>>> this.d
{'A': 'N', 'B': 'O', 'C': 'P', 'D': 'Q', 'E': 'R', 'F': 'S', 'G': 'T', 'H': 'U', 
'I': 'V', 'J': 'W', 'K': 'X', 'L': 'Y', 'M': 'Z', 'N': 'A', 'O': 'B', 'P': 'C', 
'Q': 'D', 'R': 'E', 'S': 'F', 'T': 'G', 'U': 'H', 'V': 'I', 'W': 'J', 'X': 'K', 
'Y': 'L', 'Z': 'M', 'a': 'n', 'b': 'o', 'c': 'p', 'd': 'q', 'e': 'r', 'f': 's', 
'g': 't', 'h': 'u', 'i': 'v', 'j': 'w', 'k': 'x', 'l': 'y', 'm': 'z', 'n': 'a', 
'o': 'b', 'p': 'c', 'q': 'd', 'r': 'e', 's': 'f', 't': 'g', 'u': 'h', 'v': 'i', 
'w': 'j', 'x': 'k', 'y': 'l', 'z': 'm'}

우리의 문자열은 this.d를 이용해서 번역하는 코드는 다음과 같다.

### 23.py

import this

if __name__=="__main__" :
    src = "va gur snpr bs jung"
    ans = ''.join([this.d[x] for x in src if x!= " "])

출력은 inthefaceof이다. python 원칙에 쓰여있다. in the face of ambiguity

찾았다. ambiguity

Answer Url : http://www.pythonchallenge.com/pc/hex/ambiguity.html

Python Challenge 22의 Url은 다음과 같다

Python challenge 22 : http://www.pythonchallenge.com/pc/hex/copper.html


구성

화면에는 조이스틱 하나밖에 안 보인다. 특별한 점은 안보이니, 주석을 보자

주석의 힌트를 따라 white.gif로 이동해 보자

이게 필자가 화면을 낮춘게 아니라 진짜 저 검은색 화면이 다다 ㅋㅋㅋㅋ 이상한데.. 조이스틱과 연관이 있을지도?

 


해결 아이디어

일단 저 그림이 수상해 보여서 forensically에 돌려보았다.

https://29a.ch/photo-forensics/#forensic-magnifier

 

Forensically, free online photo forensics tools

Forensically is a set of free tools for digital image forensics. It includes clone detection, error level analysis, meta data extraction and more.

29a.ch

* steghide와 같은 역할을 해주는 인터넷 사이트이다. 직관적이라 자주 이용하고 있다.

사진을 보면 정가운데에 빨간 점이 있다... 응?

gif의 특징상 frame의 조합으로 이루어 지는 경우가 많은데, 이 빨간 박스들의 위치가 문제를 푸는데 도움이 많이 될 것 같은 기분이 들었다.

### 22_1.py

from PIL import Image

if __name__=="__main__" :
    cnt = 1
    with Image.open("white.gif") as img :
        for f in range(img.n_frames) :
            img.seek(f)
            print(img.getbbox())

처음에는 프레임별로 저장해 봤는데 한 120장정도가 나오더라, 저장해본 결과는 끔찍했다. 따라서 코드를 바꾸어 출력으로 한 게 22_1.py이고, 결과는 다음과 같다.

...

(102, 100, 103, 101)
(102, 100, 103, 101)
(102, 100, 103, 101)
(102, 100, 103, 101)
(102, 102, 103, 103)
(100, 102, 101, 103)
(100, 102, 101, 103)
(98, 102, 99, 103)
(98, 100, 99, 101)
(98, 100, 99, 101)
(98, 100, 99, 101)
(98, 100, 99, 101)
(98, 100, 99, 101)
(98, 100, 99, 101)
(98, 100, 99, 101)
(98, 98, 99, 99)
>>> 

* PIL의 getbbox()는 사진에서 빈공간을 제외하고 나오는 왼쪽 / 위 / 오른쪽 / 아래 끝 점을 픽셀로 표현해준다. 

 

위의 결과를 보고 유추해볼 수 있는내용은 다음과 같았다.

 

"100,100,100,100을 가운데로 움직이는 조이스틱인가?"

 

코드를 다음과 같이 짜보았고 예상은 정확하게 들어맞았다.

### 22_2.py

from PIL import Image, ImageDraw

if __name__=="__main__" :
    cnt = 1

    ans = Image.new("RGB",(300,200))
    draw = ImageDraw.Draw(ans)
    l,u,r,d = (0,0,0,0)
    x,y = (100,100)
    
    with Image.open("white.gif") as img :
        for f in range(img.n_frames) :
            img.seek(f)
            l,u,r,d = (x-100 for x in img.getbbox())
            x += l
            y += u
    
            draw.point([x,y])

    ans.save("ans.png")

결과는 다음과 같다.

어... 뭔가 의미가 있는데, 글자가 겹쳤다. 22_1.py 출력을 자세하게 보면, 결과가 l==0과 u==0으로 멈추는 구간이 있다. 그걸 이용해서 조이스틱이 멈추면 옆으로 이동하는 코드를 짜 보자

### 22_3.py

from PIL import Image, ImageDraw

if __name__=="__main__" :
    cnt = 1

    ans = Image.new("RGB",(300,200))
    draw = ImageDraw.Draw(ans)
    l,u,r,d = (0,0,0,0)
    x,y = (0,100)
    
    with Image.open("white.gif") as img :
        for f in range(img.n_frames) :
            img.seek(f)
            l,u,r,d = (x-100 for x in img.getbbox())

            if(u==0 and l==0) :
                x += 50
            x += l
            y += u
    
            draw.point([x,y])

    ans.save("ans.png")

결과는 다음과 같다.

찾았다. bonus

Answer url : http://www.pythonchallenge.com/pc/hex/bonus.html

Python challenge 21은 웹을 통해 푸는 문제가 아니다. 있지 않다. 저번 시간 풀었던 packer.pack을 계산해서 푸는 문제이다.


구성(준비물)

다음과 같다.

  • packer.pack : 저번시간 풀고 나온 거
  • readme.txt : 안내문

일단 readme.txt부터 보자

번역하면 다음과 같다. 

 

"좋아요! 여기가 레벨 21입니다.

그리고 당연하게도 당신이 풀고 나면 22단계에 가있을 거예요!

 

이 레벨을 풀기 위한 것들은 다음과 같습니다.

 

* 우리는 어릴 때 이 게임을 하곤 했어요

* 어떻게 해야 될지 모를 땐, 전 거꾸로 바라보곤 했습니다."

 

?? 일단 pakcer.pack의 확장자를 특정하기 위해 hxd로 까 보자

시작 헤더는 89 9C이다. 이는 zlib의 확장자 헤더이다. 일단 까 볼까

### 21_1.py

import zlib

if __name__=="__main__" :
    with open("package.pack","rb") as f:
        data = f.read()

    data2 = zlib.decompress(data)
    
    with open("1.dat","wb") as f:
        f.write(data2)

??? 실행결과로 나온 1.dat은 똑같은 89 9C파일에 234KB로 달라진 게 없는 거 같다. 어떻게 해결해야 되는 것일까


해결 아이디어

안 바뀐 거 같아 보여도 바뀌었다.

### 21_2.py

import zlib

if __name__=="__main__" :
    with open("package.pack","rb") as f:
        data = f.read()

    data2 = zlib.decompress(data)
    print(data2 == data)
    
    with open("1.dat","wb") as f:
        f.write(data2)

결과는 "FALSE"이다. 일단 반복될 느낌이 드니까 무한루프 안에 위에 코드를 걸어두자

### 21_3.py

import zlib

if __name__=="__main__" :
    with open("package.pack","rb") as f:
        data = f.read()

    while(True) :
        try :
            data = zlib.decompress(data)
            print(data[0:2])
        except :
            with open("1.dat","wb") as f:
                f.write(data)
            break
        

결과로 나온 1.dat은 이번에는 42 5A의 헤더를 가진다. 얘는 bz2를 이용해서 decompress를 걸 수 있다. 두 개가 반복될 느낌이 심하게 드니까 다음과 같은 코드를 짜자

### 21_4.py

import zlib, bz2

if __name__=="__main__" :
    with open("package.pack","rb") as f:
        data = f.read()

    while(True) :
        if(data.startswith(b"x\x9c")) :
            data = zlib.decompress(data)
            print(data[0:2])
        elif(data.startswith(b"BZ")) :
            data = bz2.decompress(data)
            print(data[0:2])
        else :
            with open("1.dat","wb") as f:
                f.write(data)
            break

결과는 아래와 같이 끝난다.

...


b'BZ'
b'BZ'
b'BZ'
b'BZ'
b'BZ'
b'BZ'
b'BZ'
b'BZ'
b'\x80\x8d'
>>> 

으흠; 80 8D라는 헤더는 없는데.... 힌트를 한번 빌리자.

 

"어떻게 해야 될지 모를 땐, 전 거꾸로 바라보곤 했습니다."

 

파일의 맨 뒤를 보면 9C 78로 끝난다. 그러니까 파일이 뒤집혀 있다는 것이다. 

### 21_5.py

import zlib, bz2

if __name__=="__main__" :
    with open("package.pack","rb") as f:
        data = f.read()

    while(True) :
        if(data.startswith(b"x\x9c")) :
            data = zlib.decompress(data)
            print(data[0:2])
        elif(data.startswith(b"BZ")) :
            data = bz2.decompress(data)
            print(data[0:2])
        elif(data.endswith(b"\x9cx")) :
            data = data[::-1]
        else :
            with open("1.dat","wb") as f:
                f.write(data)
            break

완벽하다. 1.dat은 다음과 같은 파일이다.

"sgol ruoy ta kool"이라고 적혀있다. 거꾸로 읽으면 "look at your logs"이다. 여기서 필자는 한참 헸갈렸는데, 압축해 재를 할 때마다 타입에 따라 문자열을 지정하면 아스키 아트를 발견할 수 있다. 확실하다.

### 21_6.py

import zlib, bz2

if __name__=="__main__" :

    ans = ""
    
    with open("package.pack","rb") as f:
        data = f.read()

    while(True) :
        if(data.startswith(b"x\x9c")) :
            data = zlib.decompress(data)
            ans += "1"
        elif(data.startswith(b"BZ")) :
            data = bz2.decompress(data)
            ans += "2"
        elif(data.endswith(b"\x9cx")) :
            data = data[::-1]
            ans += "\n"
        else :
            with open("1.dat","wb") as f:
                f.write(data)
            break

    print(ans)

그렇다. 아스키 아트는 다음과 같다.

구리다. 찾았다.

Answer Url : http://www.pythonchallenge.com/pc/hex/copper.html

+ Recent posts