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

LOCO 프로토콜 분석 첫번째 시리즈에 이어 바로 시작합니다.

 

3. LOCO 프로토콜 – 기본 패킷 타입 및 BUY 커맨드

LOCO 프로토콜은 카카오팀에서 자체적으로  디자인 및 구현한 TCP/IP를 바탕으로 작동하는 request and response 프로토콜입니다. 앱 내부적으로 _Request_Packet_Map 이라는 Dictionary 데이터 스트럭쳐를 통하여 <packet_id, request_packet>식으로 패킷을 관리하고, 성공과 실패시에 따른 이벤트 핸들러 등록으로 결과를 처리합니다.

LocoClientAgent 클래스 오브젝트가 전반적인 LOCO 패킷전송시에 필요한 정보 관리 및 TCP 소켓 관련 이벤트를 처리하고 로코 서버 접속 및 끊기, 패킷 전송하기 등을 담당하고있습니다. 잠시후에 더 자세히 보겠지만, 이 클래스에서 가장 흥미로운 부분은 Send 메소드입니다. 크게 Send 메소드가 무엇을 하는지 보면 이렇습니다.

    • 로코 서버에 대한 TCP 소켓이 아직 살아있는지 (연결되어있는지) 확인
      • 연결되어있지 않다면, 재접속 시도
    • 해당 커맨드 패킷에 구현되어있는 (가상 메소드) FillBuffer 메소드 호출 — 좀 더 자세한 내용은 아래에.
    • Secure mode인지 아닌지 판단
      • True => LOGIN 커맨드 패킷이라면 handshake 패킷 생성한 뒤, 원래 패킷의 secure 버전 패킷 생성한 것의 앞에 붙이고 (prepend) 전송; LOGIN 패킷이 아니라면, 원래 패킷의 secure 버전 패킷 생성 한 후 전송
      • False => 원래 패킷을 그대로 전송

하지만, LOCO 패킷이 어떤식으로 구성되어있는지 모르는 상태로 위의 메소드를 본다면 사실 뭐가 뭔지 알기 힘듭니다. 그래서 잠깐 LOCO 프로토콜에 존재하는 패킷 타입들과 각 타입에 대한 구성요소들을 살펴보도록 하겠습니다. LOCO 프로토콜에는 총 3가지 타입의 패킷이 존재합니다. 아무런 암호화가 적용되지 않는 non-secure버전인 LocoPacket, AES encryption을 적용하는 secure 버전의 LocoSecureNormalPacket, 그리고 마지막으로 LOGIN 커맨드 패킷에 사용되는 특별한 handshake 패킷인 LocoSecureHandShakePacket 입니다. 각 클래스마다 중요한 메소드는 FillBuffer 이므로, 이를 중점적으로 설명합니다.

세가지 타입 모두 PacketBase라는 class를 extend하여서 구현합니다.

먼저 제일 기본이 되는 LocoPacket을 살펴보도록 하겠습니다. 사실 왠만한 개발자라면 예상할 수 있을 정도의 패킷 구조를 가지고 있고, 패킷경량화가 목표였던 만큼 최소로 필요한 데이터만 추가한듯 싶습니다. 정확히는 다음과 같은 패킷 구조를 가지고 있습니다.

 

맨 처음 4 바이트는 현재 커맨드 패킷의 패킷 아이디를 담고 있고,

바로 다음 2 바이트는 현재 커맨드/패킷의 상태코드를
(커맨드를 보낼 시에는 항상 0),

그 다음 11바이트는 Method, 즉 커맨드 이름을 담고 있는데 보통 11바이트보다 짧으며 null 패딩을 합니다.
(Method 예제: LOGIN, ADDMEM, BLOCK, NCHATLIST 등등)

다음에 있는 1바이트 Body Type 역시 커맨드를 보낼 시에는 항상 0의 값을 가지고 있고,

그 다음 4바이트는 이 후에 딸려올 body 메세지의 길이를 담고 있습니다.

마지막으로, body 내용이 덧붙여집니다.

 

 

다음으로 알아볼 패킷 타입은 LocoSecureNormalPacket 입니다. 매우 간단한 구조를 가집니다 ㅡ_ㅡ;;

 

보시다시피 처음 4 바이트는 encrypted body의 길이를 담고 있고, 바로 encrypted 된 데이터가 덧붙여집니다.

 

 

참…쉽죠…?

 

 

 

마지막이자 가장 중요한 녀석은 LocoSecureHandShakePacket 입니다. LOCO 커맨드를 사용하기 위해서는 먼저 LOCO ‘서버’와 통신하여 세션을 열어야하는데, 그걸 하는 방법이 LOGIN 커맨드를 서버에 날리는 것입니다. 그리고 위에서 잠깐 설명했듯이 LOGIN 패킷을 보낼때는 아래의 handshake 패킷을 앞에 붙여서 보내게 됩니다. 용도는 SSL에서의 handshake 프로세스와 마찬가지로 AES 암호화에 사용될 shared key (symmetric key)를 서버에 전달하기 위해서 입니다. 그리고 이후의 secure 타입 패킷들은 모두 여기서 생성되어 서버와 공유한 AES 키를 사용하여 암호화합니다.

 

첫 4바이트는 Encrypted Data 암호화에 사용된 Data block 사이즈를 담고 있고,

다음 4바이트는 Handshake Type을 나타내는데, 총 2가지 타입이 있습니다. (RSA = 1, DH = 2) 기본적으로 RSA를 사용합니다.

다음엔 앞으로의 세션에서 어떤 Encryption 알고리즘을 사용할지에 대한 정보를 담고 있습니다. 총 4가지 타입이 있고 (AES_CBC=1, AES_CFB128=2, AES_OFB128=3, RC4=4), 기본적으로 AES_CBC 모드를 사용합니다.

마지막으로 붙여지는 Encrypted Data는 앞으로 사용할 AES key가 카톡측의 public key로 RSA 암호화된 채로 담겨있습니다.

 

 

이제 각 패킷이 어떤 구성인지 알았으니, 이 패킷들이 어떤식으로 채워지고 어디에서 사용되는지 알아볼 차례입니다. 각 패킷별로 나눠서 설명해도 되겠지만, 그러는것 보다는 카카오톡 앱을 열어서부터 메세지를 전송하기까지 무슨 패킷들이 생성되어 전송되는지, 그리고 각 스텝에서 어떤 코드들이 실행되는지 등을 설명하도록 하겠습니다 :)

카카오톡 앱을 처음 실행하게 되면 우선적으로 LocoInitCommand가 실행되게 됩니다.

간단히 말해서, 로코서버에 이미 연결한적이 있었고 그 세션이 아직 expire되지 않았다면 재사용하고 그게 아니라면 BUY 커맨드를 만들어서 날리게 됩니다.
처음엔 BUY 커맨드의 이름만 보고 앱내 컨텐츠 구매 관련 커맨드인줄 알았는데.. 실제로는 로코 서버 정보를 받아오는 커맨드입니다;;

그럼 이제 자연스럽게 BUY 커맨드에 대해서 알아봐야겠군요!
일단 BUY 커맨드의 Execute 메소드를 살펴보면, 알파/샌드박스/베타/리얼 용 로코 서버 주소와 포트로 나뉘어 있습니다. 실제 앱 시장에 푸시하기 전에 테스팅하는 용도로 사용되겠죠?

    • Alpha: 192.168.77.33:5555
    • Sandbox: 110.76.140.115:9290
    • Beta: 110.76.140.165:9282
    • Real: loco.kakao.com

실제로 deploy할때 사용되는 Real 모드에서는 아이피도 DNS를 통해 동적으로 부여되고 (앱을 업데이트 하지 않고도 접속되는 아이피를 바꿀 수 있기 때문에 load balancing등에 유용함) 포트 또한 3G 연결상태와 WiFi 연결상태에 따라서 여러개의 옵션이 있습니다.

    • 3G시 사용 가능 포트: 9282, 8080, 5223, 5242, 10009
    • WiFi시 사용 가능 포트: 80, 8080, 5223, 5242, 10009

개인적인 생각이지만, 아마 회사내 인터넷 등에서 쉽사리 막기 힘든 포트들을 사용하는 듯 해 보이는군요.
80은 http, 8080은 http-alt, 5223은 apple push notification 등으로 널리 알려지고 자주 사용되는 포트들을 사용할 수 있게 해 두었습니다. 카카오톡이 유니크한 포트 하나를 골라서 사용한다면 네트워크 관리자가 회사내 인터넷에서 카톡을 막기 쉽기 때문이죠.

이 부분이 사실 분석함에 있어서도 꽤 중요한데요, 왜냐하면 wireshark등으로 패킷캡쳐를 하고 카카오톡 관련 패킷을 찾을때 필터링을 단지 특정 포트 하나에만 걸어버리면 놓치는 패킷들이 생길 수 있기 때문입니다. 보여야할 패킷이 보이지 않는다면 다른 가능한 포트들을 상대로 찾아보는 방법도 있겠죠 :)

커넥션이 성공적으로 이루어지게 되면, BUY 커맨드 패킷을 생성하여 보내게 됩니다. 이 커맨드의 특징은 SecureMode가 아니라는 점입니다. 즉, 내용이 encrypt 되지 않고 그대로 보내집니다. BUY 커맨드 request 패킷의 내용은 다음과 같습니다.

 앞에서는 언급하지 않았지만, Loco패킷에 들어가는 ‘데이터’는 bson 형식으로 encoding 되어있습니다. 물론 response 역시 bson 인코드 되어있는 채로 리턴되구요. BSON은 다들 잘 아시는 JSON의 친척(?) 같은 녀석으로 Binary JSON의 약자입니다. 자세한 스펙은 위의 링크에서 확인하시기 바랍니다.

하여튼, 저희가 위에서 알아본 LocoPacket의 구성대로 BUY 커맨드 request 패킷을 컬러코딩해보면,

즉, 이 패킷의 packet_id는 1, status_code는 0, method (커맨드 이름)는 BUY, body_type은 0, body_length는 0x64 (십진수로 100) byte 입니다.
그리고 마지막으로 body에 해당하는 내용이 따라오죠. 중간에 검은색으로 줄이 그어진 곳은 제 userId의 값이 적힌 곳이라 삭제하였으니 이해해주시기 바랍니다.

보기 쉽게 decoding을 해보면, 어떤 정보를 보내는지 알 수 있습니다.
ntype은 네트워크 타입을 나타내고, WiFi가 켜져있다면 0을 그렇지 않다면 3의 값을 가지게 됩니다. countryISO는 쉽게 알 수 있듯이 국가코드를 나타내고, userId는 현재 유져의 유니크아이디를 나타내는 8 byte (long) integer이고… 등등의 정보가 넘겨지게 됩니다. 또한, 윈폰에서는 voip를 아직 지원하지 않으므로 False의 값이 넘어가는걸 보실 수 있구요.

커맨드가 성공적으로 보내졌다면, loco 서버에서 response가 돌아오게 됩니다. 이 역시 같은 LocoPacket의 형태를 하고 있으며, 파싱하여 디코드하면 다음과 같습니다.

성공적인 response시에는 앱에서 참조하는 loco 서버 configuration 값들이 돌아오게 됩니다.
pingItv는 로코서버에게 ‘나 살아있다’ 라고 보내는 ping 패킷을 얼마마다 한번씩 보낼지에 대한 interval 이고, 앞으로 커맨드를 날릴 로코 서버 host와 port 등등이 명시됩니다.

이렇게 까지 하면, 이제 앱에서는 LOCO서버와 통신할 준비가 모두 끝나게 됩니다. 필요한 정보를 다 모은 셈이죠.
이 과정이 끝난 후에는 SecureMode를 true로 변경하여, 앞으로의 통신은 LocoSecureNormalPacket을 사용하게 됩니다.

Loco 서버에 접속을 시도할 때 성공했을때 불리는 Handler를 등록하게 되는데, 정확히는 _LocoClientAgent_ConnectSucceeded 핸들러 메소드가 실행됩니다.
여기서는 LOGIN 커맨드 패킷이 생성되고 보내지게 됩니다. 다음 시리즈에서 더 자세히 설명하겠지만, LOGIN 패킷은 handshake 패킷을 사용하는 특별한 녀석입니다. 앞으로 통신에 사용될 패킷 데이터 암호화에 사용될 AES key를 서버에게 전달하는 역할을 하죠 :P

 

일단 오늘은 여기서 마치겠습니다. 다음 화에서 ‘4. LOCO 프로토콜 – LOGIN’ 부터 이어가도록 하겠습니다. 기대해주세요!

 

다음은 각 API의 response 마다 status code가 딸려서 돌아오는데, (윈폰 기준) 각 코드의 뜻입니다:

 

 

You may also like...

5 Responses

  1. anch0vy says:

    이번 글도 잘봤습니다~

  2. oleg says:

    Hi Cai,
    Can you tell which application you used to create beautiful pictures with packet format?

  3. Tommy Lee says:

    분석 정말 잘 하시네요.
    잘 봤습니다.

  4. 이재윤 says:

    안녕하세요 저도 이런거 해보고싶은데 어떤책을 사서 배워야될까요?

Leave a Reply

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