[KakaoTalk+] LOCO 프로토콜 분석 (3)

LOCO 프로토콜 분석 두번째 시리즈에 이은 세번재 시리즈입니다.

Disclaimer: 두번째 시리즈가 포스팅 된 이후로 몇몇 카카오팀 관계자 분들께서 연락을 주셨습니다. 저는 지극히 개인적인 취미 활동으로 분석을 하고 알게된 내용을 공유할 뿐, 특정 또는 불특정의 스팸업자들에게 득을 주고자 이 글들을 작성하는것이 아니라는 점을 밝힙니다. 제 글들은 카카오톡을 PC에서 사용하고자 하시는 분들 그리고 분석 방법이나 과정 자체에 관심이 있으신 분들을 초점으로 작성하는 글이라는 점을 명시합니다. 여기서 얻은 정보로 스팸을 하시는 분들이 받게되는 법적 처벌이나 불이익에 대해서 저는 전적으로 책임이 없음을 밝힙니다. 정보는 사회에 기여하기 위해 공유하는것이지 피해를 주기 위함이 아니라는 것을 알아주시면 감사하겠습니다. 친절하게 의견을 전달해주신 카카오팀 관계자분들께 감사의 말씀을 드립니다 _ _)

 

4. LOCO 프로토콜 – LOGIN 커맨드

BUY 커맨드 패킷을 이용하여 로코서버에게 우리의 존재(?)를 알려주고 어느 서버와 포트에 접속하여서 다음 커맨드들을 보내야할지에 대한 정보를 얻었습니다. 그 다음에 보내지는 패킷은 LOGIN 커맨드 패킷입니다. 사실 애초에 ‘계정’ 이라는 개념이 모호했던 카카오톡 시스템에서 ‘로그인’이라고 함은 처음에 폰번호 인증을 할때 생성된 ‘세션키’를 통한 authentication에 가깝습니다. 여기서 한가지 짚고 넘어갈 것은, 작년 포스팅에서도 언급했듯이 이 ‘세션키’라고 불리는 녀석은 앱을 지우거나 회원탈퇴를 하기전까지 유지되는 반영구적인 세션키입니다 — 사실 세션이라는 이름이 어울리지 않는 놈이죠.

어찌되었든, LOGIN 커맨드 보내질 때 어떤 코드가 실행되는지 알아보도록 하죠!

우선 LocoPacket 구조대로 헤더를 작성한 후, appVer, duuid, lang, MCCMNC, ntype, opt, os, prtVer, sKey 와 같은 정보들이 채워지고 BSON화 되어 Body에 저장됩니다.
그런 후에, LocoClientAgent.Send 메소드가 호출되고 현재 SecureMode가 활성화 된 상태이고 LOGIN 커맨드 이므로, LocoSecureHandShakePacket을 생성하게 됩니다 (두번째 시리즈 참조).

여기서 LocoSecureHandShakePacket의 내용이 정확히 어떤식으로 채워지는지 알아보도록 하겠습니다.

앞서 설명한대로 일단 handshake 패킷은 기본적으로 RSA를 이용하여 AES shared key를 암호화합니다. 그 암호화된 AES key를 ‘Encrypted Data’ 부분에 첨부하는것이죠.
1024-bit RSA encryption에 사용되는 public key인 (N,E)는 다음과 같습니다:

Padding scheme은 기본인 PKCS#1 v1.5을 사용합니다.

암호화되는 내용인 AES키는 LOGIN 커맨드를 보낼 때마다 새롭게 생성됩니다.
정확히는 다음과 같은 코드를 통하여 생성됩니다.

서버에 전송되지는 않지만, 양측이 전제하고 있는 AES encryption에 사용되는 IV (initialization vector)‘locoforever\x00\x00\x00\x00\x00’ (128 bit)으로 초기화 됩니다.
카카오팀의 귀여움(?)이 표출되는 IV군요..ㅋㅋ…

어쨋든, 이전 포스트에 설명한대로 기본값들이 셋팅되고 패킷에 담겨져 handshake 패킷이 완성됩니다.

위에서 볼 수 있듯이 처음 4바이트는 Encrypted Data의 길이인 0x80 (128) 바이트를 나타내고, 다음 두 4 바이트들은 각각 handshake type과 encrypt type을 나타내는 값들이 들어 있습니다. 마지막으로 ’17 ac … ‘ 부터 패킷 마지막까지는 encrypt된 AES key가 들어있습니다. 위의 패킷을 만들때 사용한 임의의 AES key는 ‘0123456789abcdef’ 였습니다 — 큰 의미는 없음.

Handshake 패킷이 생성된 다음에는 LocoSecureNormalPacket안에 이전에 만들어둔 LOGIN 커맨드 패킷을 encapsulate 시킵니다. 쉽게 말하면, LOGIN 커맨드 패킷을 AES encrypt한 후 다음과 같은 스펙으로 새로운 패킷을 만든다는 것이죠.

마지막으로 해야할 일은 Handshake 패킷과 Secure-화 된 LOGIN 커맨드 패킷을 이어 붙인 후 보내는 것입니다 :D

완성 되었군요!!

각 패킷의 값들이 valid하다는 가정하에 이 패킷을 보내게 되어 성공적으로 처리되면, 서버에서 response가 돌아옵니다.

스크린샷에서 빨간 박스안에 표시된 부분이 리스폰스 패킷의 첫 4바이트 입니다. 앞으로 몇바이트나 읽어야하는지 길이를 나타냅니다. 이 예제에서는 0x00000040이니 64바이트이군요. 그리고 바로 딸려오는 64바이트의 데이터는 방금전에 서버에게 Handshake를 이용해 전달한 AES Key로 encrypt 된 서버의 LOGIN response packet입니다. 복호화 해보면 (IV와 key 를 알고 있기 때문에 복호화를 할 수 있습니다) 별 내용은 없습니다:

성공적으로 로그인 했다는걸 알려주는 status 코드 0과 해당 세션키와 duuid (device uuid)로 등록되어있는 현재 유져의 고유 userId를 돌려주는군요.

사실 이것으로 LOGIN 커맨드는 끝입니다. 이 이후부터는 로코서버 캐시 (세션)가 expire되기 전까지는 각 커맨드 스펙에 맞춰서 loco 패킷을 보내주면 됩니다. 실제 앱에서는 LOGIN 커맨드가 끝나자마자 NCHATLIST 라는 커맨드 패킷을 보내줍니다 (위 스크린샷에서 아래 두 블럭). 현재 참여중인 채팅방 리스트를 가져오는 커맨드 입니다. 이 부분에 대해서는 다음 시리즈에 공개할 Proof-of-concept를 참조하시기 바랍니다.

모든 패킷의 타입과 구성방식을 보았으므로, 실질적으로 모든 loco 커맨드를 각 커맨드 패킷의 스펙만 안다면 모두 사용할 수 있게 되었습니다. 중요 커맨드 스펙은 역시 다음 시리즈에 공개하도록 하겠습니다.

 

그럼 이것으로 이 포스팅은 끝이느냐?! 그건 아닙니다 :p

별로 재미있는 내용을 커버 안했잖아요? 오늘의 하이라이트(?)는 사실 좀전에 거의 모든 분들이 이상함을 느끼지 못하고 넘기셨을 한줄의 코드입니다. 다시 한번 봐 볼까요?

<카카오톡 윈폰 앱 취약점 찾기 time!>

어느 부분이 문제일까요?

바로 Guid.NewGuid().ToString() 부분입니다. 랜덤한 스트링을 생성하기 위해서 사용된 코드이지만 몇가지 치명적인 단점이 존재합니다. MSDN 페이지에 따르면, Guid의 포맷은 다음과 같습니다.

The value of this Guid, formatted by using the “D” format specifier as follows:
xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
where the value of the GUID is represented as a series of lowercase hexadecimal digits in groups of 8, 4, 4, 4, and 12 digits and separated by hyphens. An example of a return value is “382c74c3-721d-4f34-80e5-57657b6cbc27”.

AES 키 길이를 맞춰주기 위해 16 바이트를 자르면서 재앙은 더 극대화됩니다.

실제로 코드를 작성하여 실행해보면, MSDN에 명시된 포맷으로 나오고, 앞 16자리를 자르게 됩니다.
이 16바이트는 ascii 값 그대로 AES 키 byte들이 됩니다. 그럼 일단 총 16바이트의 key중에서 2바이트는 고정된 장소인 9번째와 14번째 바이트에 고정된 값인 ‘-‘ (hyphen)이 항상 들어가게 됩니다. 그리고 남은 14바이트에도 문제가 있습니다. 바로 ‘lowercase hexadecimal digits’이기 때문이죠. 다른 말로 하자면, 각 바이트당 총 16가지의 경우의 수밖에 존재하지 않는다는 것입니다. 종합적으로 이 루틴으로 인해 생성될 수 있는 AES key space를 계산해보면 16^14 = 72057594037927936 가 나옵니다.

매우 큰 숫자인지라, AMD Opteron 8354 2.2 GHz 프로세서에서 Crypto++ 5.6 으로 벤치마크한 결과를 이용하여 계산하면, 가능한 key space 모두를 소진하려면 싱글코어로 돌렸을 경우 약 800년 정도가 필요합니다. 하지만, 쿼드코어라고 가정하면 200년으로 줄어들고.. 국가나 기업차원의 컴퓨팅 리소스를 감안하여 200개의 쿼드코어가 있다고 한다면 약 1년정도면 가능해집니다. 그리고 Crypto++가 AES key scheduling 및 decryption에 optimized되있지 않고 인텔의 새로운 AES instruction set을 사용하지 않는 다는 점을 생각하면 최신 기술력으로는 꽤 현실성있는 시간 안에 특정 세션에 오고간 (LOGIN 패킷으로 초기화된 AES키가 expire 되기 전까지) 패킷들의 내용을 decrypt 할 수 있게 됩니다.

카카오톡의 특징 상 sKey라고 불리는 ‘세션키’가 유출되는 순간 해당 세션키 주인을 impersonate할 수 있게 됩니다. 즉, 그 사람을 가장하여 메세지를 보내거나 받는게 가능해지는것이죠. 앞서 말했듯이 이 세션키는 반 영구적이기 때문에 사용자가 계정삭제를 하지 않는 한 동일하게 유지됩니다.

다행히도 이 취약점(?)은 윈도우폰용 카카오톡 앱에만 존재하기 때문에 다른 OS용 카카오톡 앱을 사용하시는 분들은 위와 같은 공격을 당하기는 어렵다고 보시면 됩니다.

현재 이 문제에 대해서는 카카오팀에 리포팅 한 상태이고, 현재 아직 패치되지 않았습니다. 하지만, 개인입장에서 쉽게 공격을 시작할 수 없는 점을 감안하여 패치가 되기 전에 미리 블로그에 취약점을 공개합니다. 여기서 배울 수 있는 교훈은 crypto를 할때는 항상 assumption이 무엇인지 확실히 알고 지켜지는지 확인해야한다는 점입니다. 그냥 프로토콜 분석을 하다가 우연찮게 발견하게 되어 적어둡니다.

요번 포스팅은 여기까지 하도록 하겠습니다. 다음 포스팅에서는 ‘5. LOCO 프로토콜 – CWRITE 커맨드’를 통하여서 목표 중 하나였던 메세지 보내기 기능을 구현하고 그에 대한 Proof-of-concept (POC) 코드를 제공하는것으로 LOCO 프로토콜 분석 시리즈를 마치도록 하겠습니다.

You may also like...

5 Responses

  1. Cai says:

    다 쓰고 나서 생각해보니… 세션키 얻는 방법에 대해서 설명을 안했군요.. ㅋㅋ… 다음 포스팅에 하도록 하겠습니다

  2. Seungjoo Kim says:

    좋은글 잘 읽었습니다! 그런데 RSA 암호화시 사용하는 공개키 e값이 너무 작아 (Low public exponent) 위험할수 있네요. Padding을 제대로 하지 않으면 Coppersmith’s attack에 취약할수도..

    • Cai says:

      네, 저도 그 생각이 들긴했지만 cryptanalysis가 주된 목표가 아니었기에 살포시 넘어가버렸습니다 ^^;;
      실제로 공격이 가능한지 연구해보실 분들이 있다면 재미있을것 같네요!

  3. 난빨라 says:

    좋은 게시글 보고 갑니다~ (+_+)b

Leave a Reply

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