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

Python Challenge 20의 Url은 다음과 같다

Python challenge 20 : http://www.pythonchallenge.com/pc/hex/idiot2.html


구성

영어로는 표지판에

"이 울타리 뒤쪽으로는 사유재산입니다."

라고 쓰여 있다. 아래 영어는

"그래도 주의깊게 살펴보는 것은 허용됩니다."

라고 적혀있다.

우선 특별한 점이 없으니 파일을 검사해보자. unreal.jpg로 다운로드되는 파일은 헤더 변조도 없어 보이고, 특별히 숨겨놓은 스트링값도 없어 보인다... 으흠; 주석을 볼까

주석도 특별한게 없다. 난항이 될 거 같다.


해결 아이디어

우선 이 문제를 해결하기 위해서는 헤더를 주의 깊게 쳐다볼 필요가 있다.

여러분과 필자가 보고 있는 이 요청정보는 idiot2.html이 아닌 unreal.jpg의 정보라는 것을 기반으로 잡고 가자. 자세히 보면 독특한 response header가 보인다. 바로 content-range라는 응답인데, 이게 전체 크기인 2123456789에서(참 대충도 짓는다. 아마 2의 32 제곱 만들고 싶었던 듯) 0-30202 bytes만을 가져온다.

원래 이런 식이라면 HTTP코드는 206(partial content)이 되어야 한다.(그래야 나머지 데이터도 수신받을 수 있다.) 그러나 304(not modified)로 되었다는 것은 우리한테 모든 정보가 전달되지 않고 강제로 종료시켰다는 의심을 할 수 있다. HTTP코드를 자세하게 모르시는 분들은 아래를 확인하자

https://developer.mozilla.org/ko/docs/Web/HTTP/Status

HTTP 상태 코드 - HTTP | MDN

HTTP 응답 상태 코드는 특정 HTTP 요청이 성공적으로 완료되었는지 알려줍니다. 응답은 5개의 그룹으로 나누어집니다: 정보를 제공하는 응답, 성공적인 응답, 리다이렉트, 클라이언트 에러, 그리고

developer.mozilla.org

일단 30202 뒤쪽으로 해서 콘텐츠를 더 받을 수 있도록 코드를 작성해 보자

### 20_1.py from requests import * if __name__=="__main__" : header = {"Authorization" : "Basic YnV0dGVyOmZseQ==", "user-agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36", "Range": "bytes=30203-/2123456789" } url = "http://www.pythonchallenge.com/pc/hex/unreal.jpg" res = get(url,headers=header) 

아참, 지난 시간까지는 requests 모듈의 get이나 post함수에 auth()라는 파라미터를 주었는데 이번에는 헤더에 base64로 butter:fly값이 인코딩 된 값을 Authorization으로 쓰고 있다.(왜 그러냐고? 모른다. 기존 헤더를 보니 그랬다.)

결과는 다음과 같다.

>>> res.headers {'Content-Type': 'application/octet-stream', 'Content-Transfer-Encoding': 'binary', 'Content-Range': 'bytes 30203-30236/2123456789', 'Transfer-Encoding': 'chunked', 'Date': 'Thu, 03 Jun 2021 14:11:29 GMT', 'Server': 'lighttpd/1.4.35'} >>> res.text "Why don't you respect my privacy?\n"

오호; 다음 값은 30237~ 부터 있는 것이 자명하다. 계속해서 요청해 보면 각각의 범위에서 다음과 같은 값을 찾을 수 있다.

  • 0-30202 : unreal.jpg
  • 30203-30236 : "Why don't you respect my privacy?\n"
  • 30237-30283 : 'we can go on in this way for really long time.\n'
  • 30284-30294 : 'stop this!\n'
  • 30295-30312 : 'invader! invader!\n'
  • 30313-30346 : 'ok, invader. you are inside now. \n'
  • 30347-? : ''

그만 오라고 열심히 설명하다가 30347bytes부터 그만두고 ''만 나온다. 맨 뒤쪽을 검색을 해보면

  • 2123456744-2123456788 : 'esrever ni emankcin wen ruoy si drowssap eht\n'

응? 이상한 언어가 나온다. 우리가 거꾸로 뒤에서부터 탐색을 하는 거 니까, 글자도 거꾸로 나왔다. ㅋㅋㅋㅋ 무슨 콘셉트질인지... 거꾸로 읽어 보자.

'the password is your new nickname in reverse'

와우! 우리의 NICKNAME은? butter? 아니다 아니다 위에서 우리 보고 invader라고 말하지 않는가. 일단 password가 invader의 반대말인 redavni인 것을 알고 넘어가자 우리 보고 invader라고 한 것이 화나니 조금만 더 뒤로 찾아보자

  • 2123456712-2123456743 : 'and it is hiding at 1152983631.\n'

나이스 이제 좀 유의미한 인덱스를 주는 거 같다. 이동해보자

  • 1152983631-1153223363 : some byte file

일단 파일 사이즈가 늘었다. 한번 콘텐츠를 다운로드하여보자

### 20_2.py from requests import * if __name__=="__main__" : header = {"Authorization" : "Basic YnV0dGVyOmZseQ==", "user-agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36", "Range": "bytes=1152983631-/2123456789" } url = "http://www.pythonchallenge.com/pc/hex/unreal.jpg" res = get(url,headers=header) with open("somefile","wb") as f: f.write(res.content)

그래서 다운로드한 somefile를 hxd로 뜯어보자

헤더 시그니처를 보니 ZIP 파일이다. 503B0304는 국 룰이다.

zip으로 변경해서 압축해제를 하려면 비밀번호를 요구한다. 그래 그 비밀번호

Answer PW : redavni

아 조금 독특한건 압축해제한 파일에 readme를 보면 우리는 이미 20번문제는 해결한 상태라고한다. 이제 같이 압축이 풀린 .pack을 해결하는게 22번 문제라고 한다.

Python Challenge 19의 Url은 다음과 같다

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


구성

인도 사진이다. 특별한 게 안보이니까 주석을 보자

MIME Type의 e-mail이 보인다. 아래 있는 데이터로 문제를 풀어나가야 될 것 같다.


해결 아이디어 

우선 붙임파일을 base64 decode 해야 될 거 같다. 하는 김에 반환된 바이트 객체를 indian.wav로 바꾸자

### 19.py

import base64

if __name__=="__main__" :

    with open("hex.txt","r") as f:
        data = f.read()
        data = data.replace("\n","")

    data = base64.b64decode(data)

    with open("indian.wav","wb") as f:
        f.write(data)

hex.txt에 붙임파일을 집어넣어두었다. 하여튼 출력된 indian.wav는 다음과 같다.

indian.wav
0.11MB

 

 

중간에 sorry! 라는 말만 나오는 5초짜리 음성이다. 으흠; 여기서 막혔다. 일단 Python Challenge의 성격상 모든 것을 python으로 처리하니, python의 모듈 wave(wav처리 모듈)을 다운로드하여서 여러 가지 해봤는데, 답이 없다...

해결할 수 있는 키노트는 어이없게도 이 사람이 indian이라는 점이다. 

indian >> endian

그니까 endian type을 바꾸면 된다는 것이다.(지금은 LITTLE이다. BIG으로 바꾸고 오라는 거다)

다음은 코드이다. 모듈은 soundfile이라는 모듈을 이용했다.

https://pysoundfile.readthedocs.io/en/latest/#soundfile.SoundFile

 

SoundFile — PySoundFile 0.10.3post1-1-g0394588 documentation

Parameters: file (str or int or file-like object) – The file to open. This can be a file name, a file descriptor or a Python file object (or a similar object with the methods read()/readinto(), write(), seek() and tell()). mode ({'r', 'r+', 'w', 'w+', 'x

pysoundfile.readthedocs.io

### 19_1.py

import soundfile

if __name__=="__main__" :
    ind = soundfile.SoundFile("indian.wav")

    soundfile.write("big.wav",ind.read(),ind.samplerate,ind.subtype,"BIG",ind.format)

근데.... 파일이 깨진다. 결국 구글 신의 힘을 빌려 알아낸것은 BIG endiantype으로 변형할 때는 파라미터가 변경되어야 한다고 한다. 참나.. 이런 건 어떻게 안다는 말인가? 최종적인 코드는 다음과 같다. soundfile에는 framerate에는 마땅한 파라미터가 없어 처음에 가져온 wave를 다시 이용했다.

### 19_2.py

import wave

if __name__=="__main__" :
    with wave.open("indian.wav","rb") as ori :
        new = wave.open("big.wav","wb")
        new.setnchannels(ori.getnchannels())
        new.setsampwidth(ori.getsampwidth()//2)
        new.setframerate(ori.getframerate()*2)
        frames = ori.readframes(ori.getnframes())
        wave.big_endiana = 1
        new.writeframes(frames)
    new.close()

출력은 다음과 같다.

big.wav
0.11MB

 

 

 

idiot으로 들어가 보면 레오파드씨가 우리를 반겨준다.

다음으로 넘어가자 

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

+ Recent posts