안드로이드 해킹 실습 (1)

2013년 9월 8일 일요일

이 문서는 어디까지나 스터디의 목적으로 작성되었습니다. 악용하였을때 받는 불이익 및 피해는 책임지지 않습니다.

앞서 준비과정을 통해 익혀본 내용을 실제 안드로이드앱에 적용하며 배워나가보자.

일단 무엇을 할지는 모르겠으나.. 일단은 기본적으로 소스를 훑어보도록한다.

소스파악

  1. apk를 압축풀어 소스를 확인한다.

    실제 소스는 거의 없고 unity3d로 제작된 게임임을 확인. 그래도 일단 jd로 소스를 훑어보자.

    카톡연동과 각종 결제소스들 확인.

  2. assets/bin/Data 에서 unity3d 데이터확인

    Managed/Assembly-CSharp.dll 을 .net reflector로 열어서 unity3d 내부를 훑어보자.

다음으로 패킷을 파악해보자. 뭐.. 평소 실행되는 모습를 보면 실시간으로 통신하기보다는 어떤 수행을 완료했을때 한번에 데이터를 전송하는 방법같다. 이 점을 생각하며 파악해보자.

패킷파악

패킷을 어떻게 파악해볼까.. 일단 pc프로그램같은 경우에는 각종 패킷분석툴로 쉽게 패킷을 스니핑할 수 있겠으나 핸드폰의 경우는 조금 생각을 해봐야 할 것 같다. 가능한 방법을 생각해보자.

  1. 내부(휴대폰)에서 직접 패킷스니핑 : 앱도 존재하고 직접 콘솔툴을 설치해서도 가능하다. 하지만 루팅을 해야하고 (귀찮귀찮) 패킷을 바로 분석하기도 쉽지 않다.. 결국 패킷 데이터를 일반 pc로 가져와서 분석을 해야하는데.. ..귀찮겠다.
  2. 외부에서 패킷스니핑 : dhcp 서버를 구성해서 휴대폰을 붙여 분석한다. 패킷조작도 쉽다. 쉽고 간단하고 강력해보인다. ..만 휴대폰에 유선랜을 꽂지 않은이상 그냥은 어려워보인다. usb 무선랜카드가 있다면 쉽게 구성 할 수 있겠다. 근데 난 usb무선랜이 없다.
  3. 외부에서 패킷스니핑2 : arp스푸핑을 이용한 내부 네트워크 패킷스니핑이다. 2번의 장점은 그대로이나 무선랜카드 필요없다. arp스푸핑툴을 구해서 사용방법을 익혀야하는 애로사항이있겠다. 관련툴 cain & abel 이 방법이 가장 편할거 같다.

어떤 방법이든 패킷을 캡쳐해서 패킷을 분석해보자. 패킷분석툴은 여러가지가 있으나 요즘엔 대부분 와이어샤크를 이용하는거 같다. 나도 많이 다뤄본게 아니라서 그런지 다른툴보다 어떤게 좋은지 잘모르나 그냥 쓰고있다.

구현할 기능

소스도 준비되었고 패킷도 준비되었다. 이제 뭘 해킹할건지 정해야겠다. 하고자하는걸 나열해보자.

  1. 자동사냥 : 애초에 자동사냥이 있지만.. 무의미하게도 다시 시작하기위해 몇번의 클릭질이 필요하다. 이런 의미없는 클릭질의 생략.
  2. 아이템 확률 : 보물상자 세개중에 하나를 선택하지만 매번 겆이같은 것만 나온다. 아무리봐도 33.3%의 확률이 아니다. 체감상 0.1% / 19.9% / 80% 의 느낌이다. 이건 사기다. 인간적으로 100%는 아니더라도 33%는 마춰주자.
  3. 열쇠대기시간 : 10분에 한번씩 게임을 할 수있는 열쇠 하나가 재생된다. 거기다 최대치는 5개이다.. 게임이란게 할때하고 안할땐 안하는데 너무 째째한거 같다. 그렇다고 무한정쓰겠다는건 아니고 적어도 받은건 계속 모아뒀다 쓸수 있게 5개제한은 없애보자.

이 정도면 되겠다. 솔직히 게임머니를 왕창 벌수 있다던지 그런게 더 쉽게 구현가능 할 수 도 있을지도..

소스/패킷 자세히 파악

구현할 기능도 생각했으니 이제 다시 패킷과 소스를 살펴보자. 패킷을 분석해보면 대부분 http프로토콜로 통신하는 것을 알 수 있다. ㄷㄷ 그리고 예상대로 게임중에 통신하기보다는 완료시점에 한번에 등록하는 형식이다. ㄷㄷㄷ 로그인부터 게임완료까지 전체적인 흐름을 대략적으로 살펴보자.

카톡 연동 통신

/SR/notice : 공지사항

/SR/login : 로그인

각종정보수신

/SR/getmissionlist : 미션리스트

/SR/startgame : 게임시작

/SR/gameover: 스테이지클리어

/SR/getdropitem : 아이템선택

/SR/getdungeoncleartime: 클리어정보수신

/SR/gameoverfail: 스테이지실패

이런식으로 진행된다. action명이 워낙 친절(?)해서 굳이 하나하나 파악안해도 되겟다. 뭔가 쉬워보인다.. 패킷을 수정하는것이 소스 수정보다 훨씬 쉬울 것 같은 느낌이 온다. /SR/startgame 부터 /SR/getdungeoncleartime 까지 패킷만 재전송만 시켜도 왠지 구현하고자하는 기능 1번은 그냥 해결될 것 같다..

이제 패킷 내용을 살펴보자.

//REQUEST
POST /SR/startgame HTTP/1.1
Content-Type: application/x-www-form-urlencoded
User-Agent: Dalvik/1.6.0 (Linux; U; Android 4.1.2; SHV-E210S Build/JZO54K)
Host: monster-server.netmarble.net
Connection: Keep-Alive
Accept-Encoding: gzip
Content-Length: 224

epp=b866624f640f84434f7c2ff046b91d90cgtSVxlREWx1dy9dBFoGbxQiHRRlX3UIAzViAwRYMRkJBQQ9Wxx%2fAhQ9RWx%2fAxgJclURbykAMFd4SgZObhEwfX8TE1trAUpCeWAFdGxYHRpFD01sf3YqHlI0Q3keYkY8PilSBV5RBAsIDUw3dXEtW1hdA2AbIBZJNwt7WgJhNVIGWDIdXQ%3d%3d

//RESPONSE
HTTP/1.1 200 OK
Date: Sun, 08 Sep 2013 07:59:54 GMT
Server: Jetty(8.1.10.v20130312)
Content-Type: text/html;charset=UTF-8
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Content-Length: 322
Set-Cookie: JSESSIONID=1ihc73ufh0g3m1oxk9stb1fqdk;Path=/SR
Connection: close


ekBSDx5ZHGNuejdIWElTOVcbTB5oA25UUXsjERAfcBUcAAkEAEMsVgAse29uHBAJb0ZHJiMBfQYiHQJJcAE2OX9KUFtvGwhRdmZIYmoOEh4bXAItdTF0RgdnFSxNYB9vYiwTTCMPXxkZURphEywlGUkdDAx+aA0daAspTFtjLUALAnFeUFBWbFsdeB1ScBAnOF1LSitGHnhmTSxJY1lSDT1PDCNzQkEmMFZUUSA/AiVuBx4JF1xZNX4gJB9cNBcuEGILKT1uWAVvBhRQBE0ZY2BiKhhUWlgzUCxGAV4dIhpDbTZSGEEkGRgTR2FTVg==

request(클라이언트->서버)와 response(클라이언트<-서버)다. 뭔가 암호화된 것 같다. 일단 형태보니 base64인코딩은 되어있는거 같으니 각각 풀어주자. response 내용을 디코딩해보면

z@RYcnz7HMIFO-I#W}^Mul2u -GH/A) 0K=x,R3Bkd}#&g�,< aRJ?c1uUgI{Z>N{nTw=UO?`b?RK4P*
K5]~_W`6UIOr    2GW,T) 
{Wk"�1ScpK(i6Fay#$b7]OLGI#m`gF
g%f:n0d]0nz*QLS{(Jra%r
$FHQKkR    a Bn8aV /;OyFKYfcc8HMbo�>1b7^LLHM>}"*HrQo[7Io4qXd_HE#n5?d}F2An4nHc
5AoIy)6XX-�K$#:QxKAlq#sBA[i8? %/UIK@Yx*vxK]U~G=xy>oT^BHP  F8tyz_{!W^##d:h5yYa'Eiv[Nf$
B=>Oy
...

이런식으로 디코딩된다. request는 base64와 아닌값이 섞여있는거같다. 몇번 실행해서 바뀌는값을 살펴보면 32글자뒷부분만 base64로 인코딩되었음을 알수가있다. 앞에 32자는 16진수 hex문자인거 같다. 아마도 해싱값인듯하다. 어쨋든 뒷부분을 디코딩하면

tSH7tr
�nqH9[x^G>l:tW    /b!    {*^X/
"+>S?LTU7Q|;rPef;=tuM<NT={rvWdi{d"vHK:B;(p|bg[uA^b0'�;MDSQ}G,P&Vf'D]1M-l0SzF^Fia,)E5UW(f&:[O@[k|+pA3Az`Eon}Re=W_[^6|u{YRgs8|[57QV    ?R

이런식으로 디코딩된다. request response 둘다 비슷한 형태로 출력된다. base64 말고 또다른 암호화가 되어있는거 같다. 문자열이 많이 섞여있는거보니 아마도 xor처리된거같다. 복호화하려면 xor한 키값을 알아야된다. 소스에서 암복호화하는 부분을 찾아보자.

디컴파일이 워낙에 잘되놔서 어떻게든 뒤적거리면 찾겠으나 좀더 쉽게 찾으려면 c#에서 base64 처리하는 Convert.ToBase64String 같은걸 검색해서 찾으면 쉽게 찾을 수 있을 것이다. 프로텍터로 소스를 꼬아놨어도 이방법이면 쉽게 찾을 수 있을것이다. ...만 여긴 그런게 없으니 대충 뒤적거려도 나온다.

...
    xor_table = new uint[] {
    1, 0x62, 0x36, 0x6a, 0x6a, 0x38, 0x75, 15, 0x4c, 0x40, 0x4c, 0x6a, 0x3d, 0x3f, 0x36, 0x57,
    0x23, 0x44, 0x2f, 0x71, 1, 0x6d, 0x4c, 110, 0x61, 0x57, 1, 0x62, 0x65, 0x6d, 6, 0x7c,
    0x6a, 0x61, 0x65, 0x5b, 0x63, 0x2b, 0x4d, 0x24, 0x67, 0x49, 0x24, 11, 0x1a, 0x3e, 0x2a, 0x39,
    0x43, 100, 0x24, 0x49, 0x4a, 0x6f, 0x5f, 60, 0x11, 0x2f, 0x3b, 0x7b, 0x5c, 0x23, 0x53, 0x4f,
    0x1a, 0x24, 0x24, 4, 10, 0x63, 120, 0x73, 0x4c, 0x56, 100, 0x40, 13, 0x6f, 0x7f, 0x7b,
    0x70, 0x39, 0x7b, 15, 0x4f, 0x13, 0x13, 0x27, 0x6a, 2, 0x27, 0x1f, 0x29, 0x56, 0x27, 11,
    0x5b, 0x1c, 0x31, 0x60
     };
...

private string Encode(string param)
{
    param = param + "&cookie=" + AndroidManager.instance.GetXigncodeCookie();
    StringBuilder builder = new StringBuilder(param.Length);
    char[] chArray = param.ToCharArray();
    for (int i = 0; i < chArray.Length; i++)
    {
    uint num2 = xor_table[i % xor_table.Length];
    char ch = (char) (chArray[i] ^ num2);
    builder.Append(ch);
    }
    return (CommonUtil.MD5Sum(param) + Convert.ToBase64String(Encoding.UTF8.GetBytes(builder.ToString())));
}

...

private string Decode(string param)
{
    byte[] buffer = Convert.FromBase64String(param);
    StringBuilder builder = new StringBuilder(0x100);
    for (int i = 0; i < buffer.Length; i++)
    {
    uint num2 = xor_table[i % xor_table.Length];
    char ch = (char) (buffer[i] ^ num2);
    builder.Append(ch);
    }
    return builder.ToString();
}

나왔다. 인코드 디코드. request는 앞부분은 인증용 md5 + 인코딩데이터로 되어있다.

이걸 적용해서 각각 디코드해보자.

request

sid=sidc97c79e087f2ed29fbbcaa57ecdaf872&stage=20115&cookie=522c2e77_ab2156a4a7ba566c0e9986df74a7e5ce_32ab59891a1eb578d986f74c640c54a7

response

{
  "detail": {
    "event_coin": 0,
    "survival_charge_dt": 0,
    "coin": 3292,
    "event_exp": 0,
    "gamekey": "xxxxxxx",
    "mission_list": [],
    "life": 4,
    "now": 1378659594,
    "cash": 1,
    "survival_life": 5,
    "lifecharge": 1378660194,
    "friend": null,
    "friendship_pnt": 70
  },
  "err": 0
}

디코딩해보니 response쪽은 무려 json!! ...친절하다.