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

By | 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!! …친절하다.

15 thoughts on “안드로이드 해킹 실습 (1)

  1. Pingback: ray ban rb8301

    1. cook Post author

      헠 보시는 분이 있을줄이야.. ㄷㄷㄷ 조..조만간 시간이 날때 쓸꺼같아여

      Reply
  2. AGKeiz

    잘봤습니다~!

    한가지 의문사항이 있어서 여쭤봅니다.

    request의 앞부분은 인증용 md5 + 인코딩데이터로 되어있다는 말이 이해가 가질않습니다.

    md5를 디코딩하기 위해선 해쉬가 필요하다고 알고있는데 필요한 해쉬는 어디있나요?

    리버싱 초보가 배우고싶은게 많습니다. 답변부탁드립니다ㅎ

    좋은하루되세요~!

    Reply
    1. AGKeiz

      중복댓글달아 죄송합니다.

      글을 이해갈때까지 몇번 정독해보니 해쉬가 무엇이고 암호화된 코드가 뭔지도 알겠습니다.

      근데 이해가 가질않는게,

      아래 코드로 디코딩을 하는데, xor테이블을 이용하여 어떻게 디코딩을 하셨는지 궁금합니다..

      Reply
  3. AGKeiz

    정확히 의문점을 정리하자면,

    디코딩 순서가

    epp=을 제외한 앞 32자리 해싱값을 제외한 나머지 코드로 base64디코딩 -> xor디코딩이 맞는가요?

    뒷부분 암호문가지고 base64디코딩을 우선 하니 뒷 상당부분이 날라가버리네요.(디코딩후 다시 인코딩하면 뒷부분 소실)

    아래는 c언어로 짜본 xor디코더 소스 입니다.

    void main(void)
    {
    int xor_table[] = {
    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}; //xor디코딩을 위한 키 배열

    int i=0; //for문 돌리기위한 i선언.
    char data[1000] = “/*이곳에 암호문이 들어갑니다*/”; //암호화된 원문을 받아들이기 위한 data변수 선언.

    for (i = 0; i < strlen(data); i++){ //받아들인 암호문의 길이에따라 for문 반복.
    unsigned int num2 = xor_table[i % (sizeof(xor_table)/sizeof(xor_table[0]))]; //xor암호화/복호화에 사용되는 num2변수 지정)
    char ch = (char) (data[i] ^ num2); //복호화된 결과를 ch에 저장
    printf("%c",ch); //ch 출력하고 for문 처음으로 돌아가서 다음문자 복호화.
    }
    }

    리버싱이 이렇게 재미있을 줄 몰랐습니다.

    답변기다리겠습니다. 감사합니다~!

    Reply
    1. cook Post author

      epp= 제외하고 나머지 base64 디코딩 맞습니다.

      다만 제가 한가지 언급하지 않아서 뒷부분이 제대로 디코딩 안된거 같습니다;;

      자세히 보시면 %3d 같은 문자열이 섞여있는데요.. 이게 웹에서 POST값으로 값을 던저줄때 url encoding이 되서 특수문자가 저런식으로 넘겨져서 그렇습니다.

      먼저 url decoding을 하신후에 base64로 디코딩을 하시면 정상적으로 변활될거 같네요~

      Reply
      1. AGKeiz

        url decoding이 빠져있었군요.

        감사합니다. 해당디코딩을 먼저 하게끔 프로그램을 보완했더니 정상적으로 출력하는군요~

        연재하시는 글 잘 보고 배우고 있습니다~!

        Reply
  4. 몬스터 명받어

    솔직히 강화 가 넘 안되서 짜증 나는것은 어쩔수 없네요 …
    위에 내용 정말 솔직히 어떻게 해야 하는지 모르겠습니다 참 평소 보시 힘든 글이네요 ㅠㅠ
    강화 하는것은 안되나요 ???
    ㅠㅠ

    Reply
  5. 게임분석

    안녕하세요.
    현재 안드로이드 게임 분석을 해 볼려고 하고 있습니다.
    악용할 생각으로 분석할려는 것이 아니고,
    다른 목적이 있어 게임 분석을 할려고 합니다.
    블로그의 글을 보니 cook님이 분석을 어느정도 하시는것같아.
    수업료를 지불해서라도 스킬을 좀 배울수 있을까 해서 문의 드립니다.

    메일이나 댓글로 답변 주시면 감사하겠습니다.

    Reply
    1. cook Post author

      저도 취미로 하는거라 잘몰라요;; 누굴 가르칠정도는 절대 아니지영

      그냥 궁금한게 있으면 알려드릴수는 있으니 물어보세요

      Reply
  6. ndkdkd

    잘봤습니다. 한가지 궁금점이있습니다.
    자바가아닌 c로제작된 안드로이드 어플도 가능한가요?

    Reply
    1. ROOT

      틀리셨네요 fun씨 괜히 안되는 영어 하지 마세요
      왜 하는지 이해가 안되네?

      Reply
  7. ㅎㅎ

    Managed 폴더에 Assembly-CSharp.dll이 없으면 어떻게 하나요?? 유니티로 만든건 맞는데 Assembly-CSharp.dll 파일이 없어요

    Reply

댓글 남기기

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다