[KakaoTalkMac] 카카오톡 맥버전 DB 복호화

KakaoTalk Mac Version

KakaoTalk Mac Version

 

아기다리고기다리던 맥용 카카오톡 앱이 나왔습니다 :)

PC버전과 마찬가지로 모바일 카톡의 UI를 크게 벗어나지 않는 한에서 제작되었네요.
아직 베타 태그를 달고 있고(?) 이번이 첫 공개 릴리즈인만큼.. 앞으로 더 좋아질 일만 남았겠죠?
(사용하면서 개인적으로 문제는 발견되지 않았는데, 종종 불평하시는 분들이 계시니 뭔가 버그가 있긴 한가봅니다.)

각설하고.

새로운 카카오톡 클라이언트가 나왔으니, 그에 걸맞는 환영인사를 해주는것이 예의라고 생각하여..
분석은 해보고 싶은데.. 사실상 코어 프로토콜은 다 알려진 상태라 다시하는건 의미가 없을듯 싶어 무얼할까 고민하다가..
왠지 DB를 암호화 해놓았을거 같아서 바로 체크해보았습니다.

역시나 암호화가 되어있군요.

이는 임의의 사용자나 해커가 피해자 컴퓨터에 접근하여 DB파일을 복사해갔을 경우 대화내용 등의 분석을 쉽게 할 수 없도록 하기위한 조치입니다.

그럼 이 DB파일은 어떻게 암호화가 되어있을까요?
뭐.. 카카오톡 앱이 데이터를 읽고 쓰고 해야하니 앱을 분석하면 나오겠군요.

자~ 그럼 분석 시작!
글의 흐름은 ‘DB 복호화를 하자’라고 마음 먹은 시점부터 실제로 일어난 상황 그대로 순서를 반영한 것입니다.
(본문의 내용은 편의상 반말을 사용하도록 하겠습니다.)

PS: 글을 작성하는 동안 업데이트 버젼인 0.9.1 버젼이 나왔네요. 아래 글과 설명등은 모두 0.9.0  버젼에 해당합니다.

 

Step 1. Disassemble It >:-)

4.3MB의 사이즈에…. 64-bit Mach-O 바이너리다.. =_=

원래 보통 맥용 앱은 32-bit와 64-bit가 같이 묶여있는 Fat binary 혹은 Universal binary라고 불리는 바이너리로 배포가되는데.. (그 이전에는 PPC와 x86용이 묶여있는 바이너리로도 사용됨) 그래서 분석할때 더 편리한 32-bit 바이너리 부분을 떼어내서 분석하곤 한다. 하지만 최근들어 거의 대부분의 기기들이 x64를 지원을 하기 때문에 64-bit 바이너리만 배포를 시작하는 경우가 늘어나고 있다. (들은바로는 App Store에 올리려면 64비트 빌드가 필수라고 한다.)

initWithDatabaseAtPath Function

initWithDatabaseAtPath Function

일단 Database와 관련된 함수들을 정렬해놓고 본 결과, 이 함수가 눈에 띄었다.
이름으로 보건데, 주어진 path에 있는 db 파일을 열고 initialize하는 부분이다.

정확한 함수 signature는 다음과 같다:
NTObjectContext -initWithDatabaseAtPath: secureKey: schemaBuilder:

총 세개의 인자를 받는데, 첫번째는 당연 Path이고 두번째가 secureKey라는 NSString 데이터, 마지막으로 schemaBuilder이다. 여기서 당연히 우리가 흥미를 두는 인자는 secureKey가 되겠다 :)

함수의 나머지 코드를 대충 살펴보면 그다지 예상에서 벗어나는 특이한 부분은 없다.

Getting FMDatabase object pointer

Getting FMDatabase object pointer

 

Step 2. Code Review

카카오톡 맥버젼의 오픈소스 라이센스에도 명시되어 있듯이, 내부적으로 데이터베이스는 SQLite3을 쓰고 데이터 베이스 작업을 하기 위해서 FMDatabase라는 SQLite3의 Objective-C wrapper를 사용한다. 그러므로, FMDatabase 관련 코드는 굳이 리버싱을 할 필요가 없다는 뜻이다 — 오픈소스이므로 가서 코드 리딩을 하면 됨 ^_^!

여기서 한가지를 더 짚고 넘어가자면, 라이센스에 역시 명시되어있듯이 FMDatabase에서 지원하는 SQLite DB 암호화 extension인 SQLCipher를 사용한다.
이 역시 오픈소스이며, 코드는 여기서 확인할 수 있다.

위의 코드에서 볼 수 있듯이, FMDatabase 오브젝트의 open 을 호출한 후 정상적으로 열린것이 확인되면 인자로 받아두었던 secureKey를 인자로 하여 setKey 함수를 호출한다.

그럼 setKey 가 무엇을 하는 함수인지, 인자를 받아서 어떻게 써먹는지 보자.

그냥 key를 받아다가 NSData 바이트로 변형 시킨 후, setKeyWithData  함수를 호출한다.

음… 별거 없고 그냥 현재 db 포인터와 key 바이트들을 sqlite3_key 에 넘긴다. 이 함수는 SQLCipher 코드에서 찾아볼 수 있다.

이런 써글..ㅋㅋ 이렇게 함수만 타고내려가다는 끝이 없겠다.

결론적으로 encryption을 지원하니 decryption 하는 방법도 당연히 있을터.
그리고 AES를 사용하므로 symmetric key일테니 그냥 key만 얻어내서 decrypt하면 된다 -_-)/

그럼 이 SQLCipher 라이브러리를 써서 decrypt 하는법을 찾아야하는데.. 혹시해서 tool을 찾아봤다.

알고보니 SQLCipher 프로젝트 자체가 원래 SQLite 코드베이스에 extension개념으로 얹은거였고.. 빌드하면 sqlite3 CLI와 거의 동일한 sqlcipher 바이너리를 얻게 된다.

문서를 좀 더 찾아보니 여기 아래쪽에 sqlcipher_export()에 대한 예제로 “Example 2: Decrypt a SQLCipher database to a Plaintext Database” 가 떡하니 있지 않은가. ㄳㄳ.

 

Step 3. Build SQLCipher

갑자기 일이 쉬워졌다….
내가 굳이 proof-of-concept (PoC) 코드를 안짜도 되는 상황이 되어버린것이다.. 두둥.

살포시 SQLCipher 리포를 git clone 해주고 빌드하도록 한다.

그럼 같은 디렉토리에 sqlicipher 바이너리가 생긴것을 볼 수 있다 :)

이제 카카오톡 DB 파일을 복사하거나 경로를 주어 열어보자.

DB 파일이 있는 경로는 ~/Library/Containers/com.kakao.KakaoTalkMac/Data/Library/Application Support/com.kakao.KakaoTalkMac 이다.

어…. 근데.. 복호화에 필요한 secureKey는 어떻게 알아내지..?

처음에 테스트 할 때는 프로그램을 후킹하여 그냥 메모리에서 추출했지만..
이 key가 어떻게 생성되는것인지 궁금한것이 당연지사!

또한, 예리하신 분들은 알아챘겠지만.. 개개인 유져의 데이터베이스 파일명도 hash로 되어있다.

그럼 일단 이 데이터들이 어떻게 생성되는지 들여다보도록 하자.

 

Step 4. Generate secureKey + DB file name

Key와 DB 파일이름 hash를 생성하는 부분을 찾는덴 그리 오래 걸리지 않았다.

우선 우리가 있던 initWithDatabaseAtPath 함수부터 거꾸로 올라가도록 한다.
인자로 secureKey를 넘겼는데, 그렇다면 어디선가 secureKey를 가져왔어야했을 것이고.. 자연스럽게 생성지점까지 올라갈 수 있을것이기 때문이다.

initWithDatabaseAtPath 함수는 openDatabaseAtPath 함수에서 호출된다.
인자 값들은 동일하다 (path, secureKey, schemaBuilder).

openDataBaseAtPath 함수가 호출되는 곳을 보면 NTDataStore 클래스의 objectContext이다.

여기에 path, databaseName, secureKey 등의 프로퍼티들이 getter를 통해 액세스된다.

그럼 이 값들은 언제 set 되는가?
프로퍼티에 따라 다르지만, secureKey와 databaseName 같은 경우는 LPLoginController 클래스의 didMaldiveLogin 함수에서 setSecureKey와 setDatabaseName을 통해 값이 들어간다.

databaseName 생성 과정을 보면.. 다음과 같은 코드를 통해 생성된다.

Generating DB File Name

Generating DB File Name

결론적으로 DB 파일이름은:
J|유져고유번호|O|디바이스UUID|SH 라는 스트링을 SHA512 해쉬한 값인것이다. (중간에 O는 영문 대문자 O 이다)

마찬가지로, secureKey는:
KY|유져고유번호|디바이스UUID 라는 스트링을 SHA512 해쉬한 값이다.

유져 고유 번호는 카카오톡 한 유져당 한개씩 배당되는 64비트 정수 값이다.
하지만 내가 알기로는 새로운 유져가 생길 때마다 incremental 하게 올라가는 값으로.. 현재 할당된 양이 그리 크지 않다.

디바이스 UUID는 해당 맥에서 다음 커맨드를 터미널에 치면 알 수 있다:
ioreg -rd1 -c IOPlatformExpertDevice | awk '/IOPlatformUUID/ { split($0, line, "\""); printf("%s\n", line[4]); }'

예를들어 데이터베이스 주인의 유져 고유번호가 12345678이고 디바이스 UUID가 AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE 라고 가정하면 다음과 같이 databaseName과 secureKey를 생성할 수 있다.

 

Step 5. Decrypt the database!

이제 secureKey 만드는 법을 알았으니.. 다시 sqlcipher 인터페이스로 돌아가서 DB를 복호화 해보자.

암호화된 DB를 복호화 된 DB 파일로 export한 뒤 확인하니 제대로 복호화된 것을 볼 수 있다.

오오. 테이블도 보이고, 메세지들도 보인다 :)

Encrypted DB vs Decrypted DB

Encrypted DB vs Decrypted DB

여기까지 잘 따라했다면 카카오톡 Mac 버젼 (현재 0.9.1)에서 사용하는 암호화된 DB파일 복호화를 할 수 있다.

앞으로 얼마나 어떻게 바뀔지는 모르겠지만.. 분석 (30분정도밖에 시간투자를 안했으니.. 분석이라하기에도 민망하지만.. -_-) 하는 동안에 든 몇가지 생각을 짚어보자면..

1. 일단 출시때부터 DB 경로 암호화 및 DB 파일 내용 암호화는 카카오톡 개발자들이 보안에 신경쓰고 있음이 드러나는 부분이다. 칭찬하자!
2. 위의 글에서는 secureKey를 생성하는 방법을 별도로 적어놨기 때문에 언급하진 않았지만, 메모리에서 추출할때 한가지 발견한것은 런타임 모니터링 코드가 어느정도 들어가 있다는 점이다. 이 역시 어느정도 자가 보호를 하려는 노력이 보이므로 칭찬한다. (물론 커맨드 2-3줄로 우회가 가능하다)
3. 이게 엄청 취약해보일 수 있긴 한데.. 사실 로그인을 해야만 알 수 있는 유져 고유 번호를 사용하여 해쉬를 하기 때문에 예를들어 멀웨어가 컴퓨터에서 이 파일만 빼갔다고 한들 (디바이스 UUID는 해당머신에서 쉽게 구할 수 있지만) 해쉬를 생성하기가 쉽진 않다. 하지만 고유 번호를 사용하기 때문에 어떻게보면 server-seeded random 데이터와 같은것을 이용하는것보다 취약하긴하다. 왜냐하면 언급한대로 유져수에 따라 증가하는 값이고.. 그 범위가 크지 않아 bruteforce를 통해 고유번호를 알아낼 수 있기 때문이다 (결론적으로 secureKey를 계산할 수 있게 된다). 이는 아래의 Bonus 섹션에서 다루었으니 참고하도록 하자.
4. 하아… 나도 osx GUI 앱을 좀 이쁘게 잘 만들 수 있었으면 좋겠다 ㅠㅠ

 

Bonus! Cracking the hash (not really) :p

만약 내가 멀웨어 author라면 -_-? 후후후.

일단 시나리오를 써보자면..

맥을 감염시키고 다음과 같은 정보 및 파일들을 얻었다:
1. ~/Library/Containers/com.kakao.KakaoTalkMac/Data/Library/Application Support/com.kakao.KakaoTalkMac 에 있는 모든 파일들 (즉, 암호화된 DB 파일들)
2. 감염된 머신의 디바이스 UUID (AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE 라고 가정하자)

하지만 이것만으로는 추출해온 DB들을 복호화하기 불가능하다.
secureKey가 필요하기 때문인데, 이는 MITM 또는 메모리 리딩을 하지 않는 이상 구하기 힘들다.

그럼 dead-end인가? 그건 아니다.

일단 secureKey를 만들어내는 (해쉬) 알고리즘이 1 round of SHA512 이므로.. computationally expensive한 녀석이 아니라는데서 힌트를 얻어 그냥 bruteforce하면 된다.

방법은 간단한데,  echo -ne "J|?????????|O|AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE|SH" | sha512sum 에서 ????? 부분을 순차적으로 늘리며 시도해서 우리가 얻은 DB 이름의 해쉬와 매칭되는게 있는지 보면 되는것이다. 매칭되는 해쉬를 생성한 유져 고유번호가 감염된 유져의 카카오톡 유져 고유번호인 것이고, 이걸 이용해 secureKey를 생성하여 DB를 복호화할 수 있다.

그래서.. 이게 꽤나 trivial하게 가능하다는걸 보이기 위해서 5분정도 들여 간단한 poc를 만들었다:

테스트 결과, 수 초 안에 데이터베이스를 복호화할 수 있는 secureKey를 생성해 낼 수 있는것을 확인하였다.

 

뭐.. 이게 얼마나 누구에게 유용할지는 모르겠지만..

분석을 시작한 목표를 달성했으므로.. 여기서 끝  >_<

 

——

긴 글 읽어주셔서 감사합니다.
질문이나 피드백은 아래 코멘트를 달아주시거나 이메일로 주시면 됩니다.

 

You may also like...

10 Responses

  1. anon says:

    KakaoTalk auth 과정에서 xvc 라는 토큰이 추가되었다는 부분을 확인했습니다.
    혹시 이 부분 관련해서는 분석하실 계획은 없으신가요?

  2. Root says:

    예전 PC버전 인증서 교체로 분석을 잘했었으나,
    이제 버전체크까지 하면서 업데이트를 강요하는바람에 더이상 사용할 수 없게됬습니다.
    최근에 PC 몇분간 응답없으면, 모바일로 푸시가 되도록 변경된것 같던데,
    해당부분을 맥용으로 분석하려했으나, binary내부파일 내부 문자를 바꾼다던가
    hosts파일을 수정해 카톡서버대신에 프록시를 통해 하려하는 것을 해봤지만 잘안되네요.
    맥용분석 한번 부탁드릴께요!

    • Cai says:

      안녕하세요. 말씀하신 몇분간 응답없을 시 모바일푸시는 서버단에서 해결하는게 아닐까하고 조심히 생각해봅니다. 아직 분석하진 않아서 확실하진 않지만.. 메세지 읽음 콜백을 못받으면 모바일로 푸시하는 루틴이 서버에 있지 않을까 싶습니다. 나중에 분석하게 되면 한번 알아보도록 하겠습니다.

  3. n0fate says:

    글을 이제서야 정주행했네요! UUID가 시스템에서 가장 유니크한 값이다보니 악성코드가 좀비맥(?)을 관리할때도 UUID|MAC주소 형태를 해시해서 호스트디비를 유지하더라구요 :-)
    전에 sqlite 데이터베이스 암호화 기능 알아보면서 봤던 SQLCipher를 카톡이 사용했었군요 o_O
    좋은 글 감사합니다!

  4. brieve says:

    항상 감사합니다. 시스템을 이해하고 프로그램을 이해하는데 많은 도움을 주시는 것 같아서요.
    개발자로서는 비루한 실력이지만 글을 읽을때는 개발이 재밌다는 생각을 하게 해 주시네요.

    한글 포스팅 앞으로도 계속 부탁드리겠습니다.

  5. 찬빈사랑 says:

    비슷한 사례의 패킷을 분석하고 있는데요. 조언좀 부탁 드려도 될까요?

    카카오톡 아이디는 jkham99 입니다.

    수고하세요

  6. Tommy Lee says:

    이번글도 너무 재미있게잘 봤습니다.

  7. 병하 says:

    유저 고유번호는 알수있는 방법이 없나요? 개인 아이디인데 알수있을거같기도 하고…

Leave a Reply to 병하 Cancel reply

Your email address will not be published. Required fields are marked *