https://s3-us-west-2.amazonaws.com/secure.notion-static.com/24af3c3c-9231-4b51-9d67-127bd43d95f0/Untitled.png

문제를 살펴 보면 주어진 pyc 파일 분석을 통해 특정 조건을 충족하는 두 파일의 각 해시값을 찾는 문제이다. 바이너리 또는 소스코드를 패치하여 우회하는 방법을 사용해서는 안된다고 했기 때문에 로직을 이해해서 문제를 푸는것이다.

첨부된 파일을 확인해 보는 checker.pyc 파일은 파이썬 스크립트가 컴파일된 파일이기 때문에 디컴파일을 이용해서 해당 프로그램의 코드를 확인 할 수 있다.

uncompyle6라는 툴을 이용해서 pyc 파일을 py 파일로 디컴파일이 가능하다.

uncompyle6를 설치하는 방법은 아래와 같다.

pip install uncompyle6

사용 방법은 아래와 같다.

uncompyle6 <파일명>.pyc

uncompyle6를 이용해서 디컴파일을 진행해 보면 아래와 같은 코드가 출력이 된다.

# uncompyle6 version 3.6.7
# Python bytecode 3.6 (3379)
# Decompiled from: Python 3.8.2 (tags/v3.8.2:7b3ab59, Feb 25 2020, 22:45:29) [MSC v.1916 32 bit (Intel)]
# Embedded file name: checker.py
# Compiled at: 2020-04-17 16:12:09
# Size of source mod 2**32: 922 bytes

import sys, hashlib

def calc(filename):
	try:
		f = open(filename, 'rb')
	except OSError:
		print('cannot open', filename)
		sys.exit()
	else:
		data = f.read()
		f.close()
	h1 = hashlib.sha1(data).hexdigest()
	h2 = hashlib.sha256(data).hexdigest()
	h3 = hashlib.sha3_512(h1.encode()).hexdigest()
	return (h2, h3)

def main(file1, file2):
	fh1, fh2 = calc(file1)
	fh3, fh4 = calc(file2)
	if fh1 == fh3 or fh2 != fh4:
		print('Unfortunately, it is not the correct answer.')
	else:
		print('Great! I hope this problem will increase awareness and convince the industry to quickly move to safer algorithm.')

	if __name__ == '__main__':
		if len(sys.argv) == 3:
			main(sys.argv[1], sys.argv[2])
		else:
			print('Insufficient arguments')
			print('Usage: python3 checker.pyc [file1] [file2]')
			sys.exit()

위의 코드를 살펴보면 calc 함수와 main 함수로 나누어져 있다.

먼저 calc 함수를 확인해 보자.

def calc(filename):
	try:
		f = open(filename, 'rb')
	except OSError:
		print('cannot open', filename)
		sys.exit()
	else:
		data = f.read()
		f.close()
	h1 = hashlib.sha1(data).hexdigest()
	h2 = hashlib.sha256(data).hexdigest()
	h3 = hashlib.sha3_512(h1.encode()).hexdigest()
	return (h2, h3)

calc 함수의 기능을 살펴 보면 아래와 같다.

이번에는 main 함수를 확인해 보자.

def main(file1, file2):
	fh1, fh2 = calc(file1)
	fh3, fh4 = calc(file2)
	if fh1 == fh3 or fh2 != fh4:
		print('Unfortunately, it is not the correct answer.')
	else:
		print('Great! I hope this problem will increase awareness and convince the industry to quickly move to safer algorithm.')

main 함수의 기능을 살펴 보면 아래와 같다.

즉, 문제를 해결하기 위해서 fh1과 fh3이 달라야 하고, fh2와 fh4가 같아야한다. 쉽게 설명을 해보면 2개의 파일의 SHA-256 해시값이 달라야 하고, SHA-1해시의 SHA3-512 해시값은 같아야 한다.

해시값 특성상 다른 파일 2개에서 같은 해시를 갖는다는 것은 무리가 있다. 하지만 SHA-1 해시 특성상 해시의 제기능을 잃어 버린 상태이기 때문에 2개의 다른 파일에서 SHA-1 해시값이 같게 나오는 파일이 존재한다.

아래의 사진과 같이 올바른 doc 파일의 SHA-1의 해시값이 3713..42 라고 하면 해당 해시값을 가지는 Bad doc가 존재 할 가능성이 있다.

https://s3-us-west-2.amazonaws.com/secure.notion-static.com/44385d08-d525-47b5-addc-000949ee781e/2.png

SHA-1 해시값이 같은 2개의 파일을 제공해 주는 사이트가 존재 한다. https://shattered.io/ 의 Attack proof 을 확인해 보면 PDF1 과 PDF2 를 다운로드 받아 볼 수 있다.

다운로드 경로는 아래와 같다. PDF1 : https://shattered.io/static/shattered-1.pdf PDF2 : https://shattered.io/static/shattered-2.pdf

2개의 파일을 Hash Tab 이라고 하는 툴을 이용해서 sha1 해시값을 한번 확인해 보자.

https://s3-us-west-2.amazonaws.com/secure.notion-static.com/f712b77e-7984-4477-8db9-b950734b29e2/3.png

실제로 CRC32와 MD5 해시값은 다르지만 SHA-1 해시값은 같은 값을 가지고 있는 것을 확인할 수 있다.

아래와 같이 첨부되었던 프로그램의 디컴파일 코드를 조금 수정해서 각각의 파일의 SHA3-512 해시값을 출력해보자.

import sys, hashlib

def calc(filename):
	try:
		f = open(filename, 'rb')
	except OSError:
		print('cannot open', filename)
		sys.exit()
	else:
		data = f.read()
		f.close()
	h1 = hashlib.sha1(data).hexdigest()
	h2 = hashlib.sha256(data).hexdigest()
	h3 = hashlib.sha3_512(h1.encode()).hexdigest()
	h4 = hashlib.sha3_512(data).hexdigest()
	return (h2, h3, h4)

def main(file1, file2):
	fh1, fh2, f1_sha3_512 = calc(file1)
	fh3, fh4, f2_sha3_512 = calc(file2)
	if fh1 == fh3 or fh2 != fh4:
		print('Unfortunately, it is not the correct answer.')
	else:
		print('Great! I hope this problem will increase awareness and convince the industry to quickly move to safer algorithm.')
		print(sys.argv[1]+' SHA3-512 hash is {}'.format(f1_sha3_512))
		print(sys.argv[2]+' SHA3-512 hash is {}'.format(f2_sha3_512))

if __name__ == '__main__':
	if len(sys.argv) == 3:
		main(sys.argv[1], sys.argv[2])

위의 코드를 실행해 보면 아래와 같이 출력이 된다.