Python/Python Challenge

[Python Challenge 6] ZIP과 놀기

Python Challenge 6의 url은 다음과 같다.

Python challenge 6 : http://www.pythonchallenge.com/pc/def/channel.html

 

now there are pairs

 

www.pythonchallenge.com

* zipline을 활용한 해결방법을 기술하고 있지 않습니다.


구성

으흠 화면에는 PayPal로 연결되는 그림 하나밖에 없다. href 걸려있는 것도 실제로 PayPal로 이동하는 링크이다. 주석을 보자

아래 주석으로 돈을 보내달라는 이야기 인데, 거짓말일 거다. 설마 풀리는 그것도 웃기는 경우니까 ㅋ 맨 윗줄에 zip이라는 주석이 있다. channel.html이라는 소스를 channel.zip으로 변경하면 압축파일을 다운로드할 수 있다.

 

압축파일에는 약910개정도의 텍스트 파일이 있다. 예시로 하나 열어보면 linkedlist문제 때와 같은 Next nothing is XXXX라는 문자열이 있다. 제일 아래를 보면 다음과 같이 readme가 있다. 

readme.txt를 open하면 다음과 같은 안내문이 있다.

welcome to my zipped list.

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

글쿠만 90052로 부터 시작하는 코드를 작성하자

### 코드 6.py

import re

if __name__=="__main__" :

  nothing = 90052

  while(True) :
    try :
      with open("./channel/"+str(nothing)+".txt","r") as f:
        data = f.read()
        nothing = re.findall("Next nothing is (\d+)",data)[-1]
        print(nothing)
    except Exception as e:
      print(data) 
      break

머지않아 다음과 같은 결과를 만날 수 있다.

...
91038
44221
992
8700
45100
68628
67824
46145
Collect the comments.

응? 이게 무슨 소리인지? comments를 모으라고 한다. comments가 의견/주석처럼 쓰이는 단어인데, txt 파일에 comment가 있단 말인가? 힌트는 readme.txt에 있는 hint2 answer is ininside the zip에 있다.

 

해결 아이디어

zip파일의 헤더 구조를 일단 읽고 오자

https://en.wikipedia.org/wiki/ZIP_(file_format) 

 

ZIP (file format) - Wikipedia

From Wikipedia, the free encyclopedia Jump to navigation Jump to search Family of archive file formats ZIP is an archive file format that supports lossless data compression. A ZIP file may contain one or more files or directories that may have been compres

en.wikipedia.org

이번 문제를 풀기위한 설명만을 하자면 ZIP 파일의 구조는 504 b0304~ (로컬 파일 헤더)  + (데이터 디스크립터) + 504 b0102~ (센트럴 딕셔너리 file header)로 이루어져 있다. 모든 파일에 대해서 위와 같은 파일 형식이 들어가는데, 우리가 집중해야 되는 곳은 504b0102로 시작하는 센트럴 딕셔너리 file header이다.

이곳의 offset+len(file_name)+len(Extra_file)가 file_comment 인다. 우리 문제에서는 다행히도 comment는 한 글자로 표현되어있기 때문에, 다음과 같이 파싱 해 보았다.

### 코드 6_1.py

if __name__=="__main__" :
  plz_answer = list()
  avoid_list = [0,32,42]

  with open("channel.zip","rb") as f:
    data = f.read()
    for i in range(len(data)) :
      if(data[i:i+4] == b"\x50\x4b\01\02" and data[i-1] not in avoid_list) :
        plz_answer.append(chr(data[i-1]))
        

  print("".join(plz_answer))

특수문자인 *, 공백(" "), 줄바꿈이 나오길래 이들은 제외하고 출력한 결과를 보고 싶었다. 다음과 같다.

EYEYONYE
OG
YGEYENGGOXEEXGEYOOGXEXOEE
NXGENNGXEXYXNOOOGGOOGYYNYX
GNN
XYEYONXEX
OX
NX
GOYNXNYXEEENGYEOOGOOO
XYGEEYXNYOOXXGNYXOOGONG
ENYEOENEY
NYEXOYGNYGOO
EYGYXGEOOEXEOGY
OONXXXEGXEXGOYEOYEYNYGGXNEOXOYXEYOOXXXXO
EEGYG

??? 아무의미가 없는 문자열이었다. 문득 스쳐 지나간 생각은

 

"순서에 맞게 풀면 ASCII Art가 나오나?"

 

였다. 아래는 위의 아이디어를 구현하기 위해 사용한 코드이다.

### 코드 6_2.py

import re

if __name__=="__main__" :
  struct = dict()

  ### ZIP 파일의 센트럴 딕셔너리를 파싱하는영역 
  ### 결과로 sturct를 만든다. struct는 확장자를 제외한 파일명을 key로 하고
  ### comment를 value로 하는 딕셔너리
  with open("channel.zip","rb") as f:
    data = f.read()
    for i in range(len(data)) :
      if(data[i:i+4] == b"\x50\x4b\01\02") :
        number = list()
        
        ### 파일이름등에 따라 comment의 위치가 변하기 때문에 44~100으로 범위를 설정하고
        ### .txt를 만나면 멈추도록 해두었다.
        for j in range(44,100) :
          if(data[i+j] >=48 and data[i+j]<= 57) :
            number.append(chr(data[i+j]))
          if(data[i+j:i+j+4] == b"\x2e\x74\x78\x74") :
            break
        if(len(number)!=0) :
          number = "".join(number)
          struct[int(number)] = chr(data[i+50+len(number)])

  ### 생성된 struct에서 정답을 찾는다.
  nothing = 90052
  answer = struct[nothing]
  while(True) :
    try :
      with open("./channel/"+str(nothing)+".txt","r") as f:
        nothing = re.findall("Next nothing is (\d+)",f.read())[-1]
        answer += struct[int(nothing)]
    except Exception as e:
      print(answer)
      break

* 필자는 구현할때 offset을 정수 값으로 때려 넣었는데, 나중에 Doc을 보니까 file name와 extra file의 길이가 offset 22부터 해서 저장되더라 제기랄 그거 볼걸 풀었으니 귀찮게 하지 말고 넘어가자

 

다음과 같은 출력결과를 얻을 수 있었다.

찾았다

Answer Url? : http://www.pythonchallenge.com/pc/def/hockey.html 

 

어? 다음과 같은 안내문이 있다.

공기 중에 있다고 한다. 글자를 보라는 안내문에 따라 HOCKEY를 이루고 있는 글자인 oxygen이 답인 듯하다

 Answer Url : http://www.pythonchallenge.com/pc/def/oxygen.html


여담으로 이 문제를 쉽게 해결하는 방법은 모듈 zipline을 이용하는 것이다. zipline을 활용하면 필자가 수동으로 했던 헤더 분석 및 추출 등을 보다 쉽게 할 수 있다. (필자는 왜 수동으로 허튼짓을 헷냐고? 내 맘이다 절대 늦게 봐서가 아니다)