Forensics

[포렌식] 윈도우 카카오톡 데이터베이스 복호화 분석 및 구현 #1

pental 2024. 3. 23. 01:48

본 글은 언제든지 비공개 되거나, 삭제될수 있음을 미리 알려드립니다. (뭐,, 카카오톡 본사에서 글 내려라 하지 않는 이상 없어지진 않지 않을까...?)

이 글은 여러 논문을 참조하여 분석 및 구현한것이며, 참고 문헌은 아래에서 확인 할 수 있다. 

분석 환경 및 도구 : MacBook Air M2, Python 3.9, DB Browser for SQLite, HxD,, 등등등등

먼저 윈도우용 카카오톡 데이터베이스 복호화를 하는 이유가 무엇인가 ?

직관적인 주제이며, 가장 결과물이 잘 나올수 있는 복호화 솔루션이 아닌가! 상용 포렌식 도구들에서도 카카오톡 복호화는 어려움을 겪는 부분이라, 쉽고, 빠르게 복호화 할수 있는 방법이 뭐가 있나 고민하다 분석하게 되었음.

(사실 이렇게 퍼블릭하게 코드도 공개해도 되는지는 모르겠다,, 혹시 아시는분 있다면, 댓글로 알려주시면 감사하겠습니다...)

 

카카오톡 윈도우 데이터는 어디에 저장되는가?

C:\Users\[User Name]\AppData\Local\Kakao\KakaoTalk\users\[USER ID]/chat_data/*.edb

위 경로에 고정적으로 edb파일로 카카오톡 채팅 기록이 저장된다. 요 파일의 구조는 어떻게 생겼냐?

사실 이걸 보고 그냥 냅따 DB Browser for SQLite 에 집어넣는 놈은 없길 바라며,,, 일단 위 사진처럼 더럽게 암호화 되어 있다.

2023년 2월에 공개된 논문에 의하면 이 암호화된 edb를 복호화 하는 방법은 아래와 같다고 한다.

Fig. 2.의 입력 파라미터인 pragma와 userId는 각종 데이터를 암호화하는 데 흔히 사용된다. pragma 생성 방안은 다음과 같다. 하드 코딩된 바이트 배열을 key로 하여, UUID와 ModelName, SerialNumber를 연접한 결과에 대해 AES 암호화를 수행한다. 위 결과에 대해 SHA512 해싱 수행 후 Base64 인코딩을 수행하면 pragma가 생성된다. 이때 사용되는 입력 파라미터들은 모두 기기 내에서 획득할 수 있는 정보들이다. UUID는 Windows 운영체제 정보에서 획득할 수 있으며, ModelName과 SerialNumber는 하드디스크 정보에서 획득할 수 있다.

오,, 이런 신기한 방법이 있다니, 일단 UUID와, ModelName, SerialNumber을 가져와 보자.

근데 ModelName과 SerialNumber을 어디서 가져와,,,?

CMD에서 제공하는 vol로 가져와? 가져오면 8글자로 짧아서 이건가,,

아니지 아마 카카오톡은 레지스트리에 넣어두지 않았을까? 오,, 빙고 아래와 같이 카카오톡에서는 UUID와, ModelName, SerialNumber을 역시 넣고 있다. 사실 논문에서는 알수 없는 정보이다.. 다른 방법을 찾아서 wmic같은걸 썼다고 해서 무조건 그게 맞다고 생각하면 안된다. 헛고생 하지 않으려면 레지스트리를 참고하자,,, 거의 이것때문에 얼마나 삽질을 한건지,,

HKCU\Software\Kakao\KakaoTalk\DeviceInfo\~

위 레지스트리 경로에서 sys_uuid, hdd_model, hdd_serial을 가져와서 사용한다. 논문대로 일단 만들어 보자.

from Crypto.Cipher import AES
from Crypto.Hash import SHA512
import base64
UUID = "USER_UUID"
ModelName = "HDD_MODEL"
SerialNumber = "HDD_SERIAL"
key = b'hardcoded bytes array'
iv = bytes([0] * 16)

pragma = f"{UUID}|{ModelName}|{SerialNumber}".encode('utf-8')
cipher = AES.new(key, AES.MODE_CBC, iv)
encrypted_pragma = cipher.encrypt(pragma)
base64_encrypted_pragma = base64.b64encode(encrypted_pragma)
hash_object = SHA512.new()
hash_object.update(base64_encrypted_pragma)
hashed_pragma = hash_object.digest()
print(base64_encrypted_pragma.decode('utf-8'))
print(hashed_pragma.hex())

ㅋㅋㅋㅋ,,, 사실 논문에서는 Pragma 생성을 위한 key를 공개하지 않았다. 이건 당연한거다. 일단 접을까 고민도 했지만 영차영차 삽질을 시작했다. 수많은 논문을 뒤져도 보고,,,

요래요래 프로그램을 분석을 해보면 어느순간 키다 뚜뚠 하고 나타난다. 비교적 정+동적 분석을 통해서 쉽게 찾을수 있다.

논문에서는 생성된 Pragma를 AES 128 CBC 돌리고 SHA512로 만들고 Base64로 인코딩 하면 된다니까 바로 코드에 떄려 박고 돌리면 ,,, 

두둥,,,, 뭔가 그럴싸한 뭔가가 나왔다.

즉, 이런 결과물이지 않은가? BASE64(SHA512(AES(PRAGMA)))

또 논문에서는 뭔가를 해야한다고 하니까 한번 해본다. 이것도 바로 코드로 작성한다.

Fig. 1.과 같이 대화 기록 데이터베이스를 복호화하기 위해서는 key, iv 쌍을 생성해야 한다. pragma와 userId를 512바이트가 될 때까지 반복하여 연접한 후 MD5 해싱을 수행하면 key가 된다. key에 대해 Base64 인코딩 수행 후 MD5 해싱을 수행하면 iv가 된다. 구체적인 알고리즘은 Fig. 2와 같다.
import hashlib
import base64
def generate_key_iv(pragma, userId):
    key = (pragma + userId).encode('utf-8')
    while len(key) < 512:
        key += key
    key = key[:512]
    key = hashlib.md5(key).digest()
    iv = hashlib.md5(base64.b64encode(key)).digest()
    return key, iv

pragma = "PRAGMA"
userId = "12345678"

key, iv = generate_key_iv(pragma, userId)

print("Key:", key.hex())
print("IV:", iv.hex())

pragma의 경우 위에서 구한 base64값을 넣어주고, userID의 경우 나는 먼저 알고 있기에, 알고 있는 정보를 넣었다.

userID를 아는 방법은 엄청나게 다양하다, 논문에서 소개하는 방법은 Brute-Forcing Attack 공격도 있지만, 나는 그냥 안드로이드나 아이폰에서 추출해서 Contact DB에서 UserId를 알아오는 방법을 선택해서 사용한다. 이 경우 데이터베이스의 주인의 연락쳐가 등록되어 있어야 한다. 연락처가 등록이 안되어 있다고 좌절하면 안된다. 쉬운 방법이 여러개 있기 때문이다.

C:\Users\[UserName]\AppData\Local\Kakao\KakaoTalk\users\login_list.dat

위 login_list.dat 파일에서는 저장된 로그인 목록을 확인 할 수 있다. 대부분의 사용자들은 전화번호로 로그인 하지 않는가?! 이를 추가하고 안드로이드나 아이폰에서 빼오면 UserID를 쉽게 확인 할 수 있는 방법도 존재한다. (이메일로 로그인하는 사람 바로 나야,)

여기까지 따라왔는가..? 정말 끈기가 대단한 사람이라 생각된다,,,, 

자 이제 Key, IV를 모두 획득했으니 EDB 복호화를 해보러 가볼까,,,? 논문에서는 다음과 같이 설명한다.

Windows 환경에서 카카오톡은 대화 기록 데이터베이스 파일 내에 수발신 된 모든 메시지를 저장한다. 해당 데이터베이스 파일은 대화방 별로 각각 “%LocalAppData%\Kakao\KakaoTalk\users\{userDir}\chat_data\chatLogs_{chatId}.edb”형식으로 암호화하여 생성한다. 이를 복호화하는 방안은 [4]에서 제시되었다. AES 알고리즘을 사용하여 페이지 크기인 4,096바이트 별로 복호화하며, 페이지 크기가 16바이트의 배수이므로 별도의 패딩은사용되지 않는다. 구체적인 알고리즘은 Fig. 1. 같다.

호오,,, AES를 또 쓴다 이거지? 그리고 별도의 패딩은 안하고,,

def decrypt_database(key, iv, encDB):
    decDB = b""
    i = 0
    while i < len(encDB):
        cipher = AES.new(key, AES.MODE_CBC, iv)
        decrypted_data = cipher.decrypt(encDB[i:i+4096])
        decDB += decrypted_data
        i += 4096
    return decDB

def save_decrypted_database(decDB, output_file):
    with open(output_file, "wb") as f:
        f.write(decDB)

요로코롬 짜면 EDB가 복호화 된다는거지? 바로 돌려본다. 그럼 두둥

무려 SQLite format 3 라는 헤더를 볼수 있다. 이 매직헤더를 보는게 얼마나 반가운지 모르겠다. 거의 삽질만 이틀했기 때문에....

그럼 정상적인 데이터베이스로써 DB Browser for SQLIte로 열어볼수 있다! 근데 얘네는 데이터베이스 암호화는 하면서 왜 메시지 암호화는 안하는거지..?

암튼 이렇게 EDB를 복호화 할수 있다는 점이 신기하며 아래는 참고한 논문이다.

https://www.dbpia.co.kr/journal/articleDetail?nodeId=NODE11215974

 

윈도우 환경에서 카카오톡 데이터 복호화 및 아티팩트 분석 연구 | DBpia

조민욱, 장남수 | 정보보호학회논문지 | 2023.02

www.dbpia.co.kr

 

궁금한점이나 오탈자는 pental@kakao.com 으로 메일 또는 댓글로 남겨주시면 감사하겠습니다.