[Python3] 손상된 ZIP 압축 파일 복구하기

Posted by 적분 ∫2tdt=t²+c
2018.06.24 03:00 프로그래밍

며칠 전에 서버에서 작업을 진행하다가 용량 문제 때문에 잘 안 쓰는 대량의 파일들을 zip으로 압축해둔적이 있습니다. 문제는 zip 포맷의 한계로 4GB 이상의 파일을 다룰수가 없다는 문제가 있는걸 모르고 그냥 압축을 진행한 뒤 생성된 압축파일을 검증하지도 않고 그냥 원본 파일들을 다 지워버렸던 거죠. 뒤늦게 알아차리고 zip파일 압축을 해제하려고 했으나 손상된 파일이라며 압축 해제를 계속 실패했습니다...


Archive:  archive.zip

  End-of-central-directory signature not found.  Either this file is not

  a zipfile, or it constitutes one disk of a multi-part archive.  In the

  latter case the central directory and zipfile comment will be found on

  the last disk(s) of this archive.

note:  archive.zip may be a plain executable, not an archive

unzip:  cannot find zipfile directory in one of archive.zip or

        archive.zip.zip, and cannot find archive.zip.ZIP, period.


(아 안돼...)


여러 압축 유틸을 받아서 열어보았지만, 복구되는 파일은 4개에 불과했습니다. (전체 압축한게 1만개가 넘었는데...) 도저히 자료를 그대로 날릴수가 없어서 ZIP 파일 포맷 명세를 확인해보았습니다.


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


정말 다행인게 ZIP 포맷은 오픈소스로 명세가 다 공개되어있고, 또한 압축이 파일 단위로 진행되기에 일부 손상된 파일이 있어도 나머지 파일들은 멀쩡하게 복구할 수 있다는 거였습니다.


ZIP파일의 구조를 간략하게 설명하자면, 먼저 N개의 File Entry가 차례대로 나열되고, 마지막에 Central Directory 정보가 덧붙습니다. 사실 실제 압축된 파일의 정보들은 File Entry에 들어가고, Central Directory 정보에는 기타 메타데이터 및 빠른 파일 색인을 위한 정보들이 있는것이라서 이 부분은 없어도 파일을 복구하는데에는 문제가 없습니다.


File Entry의 구조는 다음과 같습니다.

OffsetBytes

Description

04

파일헤더 시그니처 ('PK\x03\x04'의 값)

42버전
62범용 비트 플래그
82압축 방법
102마지막 수정 시간
122마지막 수정 날짜
144CRC-32
184압축된 크기
224압축되기 전 크기 (r)
262

파일 이름 길이 (n) (바이트 단위)

282기타 필드 길이 (m) (바이트 단위)
30n

파일 이름

30+nm

기타 필드

30+n+m

r

압축된 데이터 (총 r바이트 길이)

이 부분이 계속 반복되면서 총 N개의 파일을 표현하는 것이죠. 만약 정상적인 파일이라면 하나의 File Entry를 다 읽은 다음에는 바로 다음 File Entry가 등장해야합니다. 이건 파일 헤더 시그니처 값이 'PK\x03\x04'가 나오는지를 통해서 확인할 수 있습니다.


그럼 제 파일은 왜 압축 해제 유틸에서 제대로 열리지 않았을까요? 이는 앞의 4개까지는 File Entry가 제대로 나왔는데, 그 다음부터는 잘못된 값들이 덮어씌워져 파일 헤더가 제대로 나타나지 않았기 때문입니다.


PK.. ~~~~~~ PK.. ~~~~~~~~~~~ PK.. ~~~  PK.. ~~~~~

원래는 위와 같은 모양으로 데이터가 나와야하는데 중간에 잘못된 값이 덮어씌워져서


PK.. ~~~~~~ PK.. ~~~~~~~~~ABCBASBD~~  PK.. ~~~~~

PK..가 등장할것으로 예상되는 지점에 다른 값이 섞여버려 있더라구요. 이렇게 되면 두번째 파일과 세번째 파일은 사실 값이 깨졌기 때문에 복구할수가 없습니다. 그러나 네 번째 파일부터는 또 제대로 PK..라고 시그니처가 등장하기 때문에 이 지점부터는 또 복구가 가능합니다. 따라서 중간에 예상치 않은 잘못된 값이 들어왔을 경우 다음 헤더 시그니처인 PK\x03\x04의 위치를 찾아 점프하는 식으로 살릴 수 있는 모든 File Entry를 발견할 수 있습니다.


그래서 급하게 코딩을 했습니다.




코드를 실행할때 손상된 zip파일의 경로를 argument로 넘겨주면, 그 중에서 복구 가능한 File Entry만 추출하여 restored.zip으로 저장합니다. 이렇게 되면 restored.zip파일은 비록 Central Directory는 없지만 다른 압축 유틸에서 충분히 복구가 가능한 상태가 됩니다.


압축을 시도했던 원본 파일들이 약 1만개가 넘었는데, 이 방법을 통해 720개를 제외한 나머지 9천 여개의 파일들을 복구할 수 있었습니다. (십년 감수했지요) 혹시나 저와 같이 문제를 겪으시는 분들을 위해 코드를 공유하니 자유롭게 쓰시면 되겠습니다.


물론 제일 좋은건 손상된 zip파일을 만들지 않는 것이구요!


결론1: 파일을 지우기 전에 3번만 더 생각하자.

결론2: ZIP파일은 오래된 포맷이라 4GB 이상의 파일을 지원하지 않는다!



Tags
이 댓글을 비밀 댓글로
    • 2018.07.19 12:37
    비밀댓글입니다
    • python3랑 pycharm community edition 설치해서 위 코드 넣고 실행하시면 될듯합니다. 맥을 써보지는 않아서 맥에서 개발작업을 해보지는 않았지만, 맥에서도 pycharm을 많이 쓴다고 들었거든요
    • 2018.07.19 14:42
    비밀댓글입니다
    • 2018.07.19 17:24
    비밀댓글입니다
    • sys.argv[1] 부분을 복구를 원하시는 파일 경로로 바꿔넣으세요. 그 아래 43번 라인의 sys.argv[1]도 마찬가지이고요.

      w = open('restored.' + 'my.zip', 'wb')
      with open('my.zip', 'rb') as f:

      처럼요
    • 2018.08.10 19:02
    비밀댓글입니다
    • '사진'이라는 파일이 없다는 겁니다. zip 파일이면 '사진.zip'이라고 해야하지 않을까요?
    • 2018.08.12 13:01
    비밀댓글입니다