안드로이드 게임 앱 리버싱 문제다.


1945같은 간단한 비행 게임인데 문제 설명란에 최고점수를 얻으라고 나와있다.


대충 점수조작을 하면 될것같다 생각했고 일단 자바코드부터 보면 아래와 같이 SharedPreferences를 통해 앱 데이터 로컬 영역에 임의의 값을 저장하고 있었다.



위 값들은 앱 실행 시 초기 값을 담는 정도였고 자바코드에선 더이상 볼게 없어서 네이티르 코드를 분석해봤다.


점수와 관련된 코드를 찾아보니 updateScore라는 함수가 존재했고 해당 함수 코드는 아래와 같았다.


cocos2d::CCUserDefault *__fastcall ControlLayer::updateScore(cocos2d::CCUserDefault *result, unsigned int a2)

{

  int v2; // r3

  const char *v3; // r4

  int v4; // r7

  cocos2d::CCUserDefault *v5; // r0

  int v6; // r5

  int v7; // r3

  char *v8; // r0

  int v9; // r5

  int v10; // r3

  int v11; // r5

  int v12; // r3

  int v13; // r5

  int v14; // r3

  int v15; // r5

  int v16; // r3

  int v17; // r5

  int v18; // r3

  int v19; // r5

  int v20; // r3

  int v21; // r5

  int v22; // r3

  int v23; // r5

  int v24; // r3

  int v25; // r5

  int v26; // r3

  int v27; // r0

  int v28; // [sp+0h] [bp-68h]

  cocos2d::CCUserDefault *v29; // [sp+4h] [bp-64h]

  int v30; // [sp+8h] [bp-60h]

  char v31; // [sp+Ch] [bp-5Ch]

  char v32; // [sp+10h] [bp-58h]

  char v33; // [sp+14h] [bp-54h]

  char v34; // [sp+18h] [bp-50h]

  char v35; // [sp+1Ch] [bp-4Ch]

  char v36; // [sp+20h] [bp-48h]

  char v37; // [sp+24h] [bp-44h]

  char v38; // [sp+28h] [bp-40h]

  char v39; // [sp+2Ch] [bp-3Ch]

  char v40; // [sp+30h] [bp-38h]

  char v41; // [sp+34h] [bp-34h]

  char v42; // [sp+38h] [bp-30h]

  char v43; // [sp+3Ch] [bp-2Ch]

  int v44; // [sp+40h] [bp-28h]

  char v45; // [sp+44h] [bp-24h]

  int v46; // [sp+48h] [bp-20h]

  char v47; // [sp+4Ch] [bp-1Ch]


  v44 = 1635017060;

  v2 = 0;

  v45 = 0;

  v47 = 0;

  v29 = result;

  v3 = (const char *)a2;

  v46 = 0;

  do

  {

    *((_BYTE *)&v46 + v2) = *((_BYTE *)&v44 + v2) ^ 0x20;

    ++v2;

  }

  while ( v2 != 4 );

  if ( a2 <= 0x3B9ACA00 )

  {

    v4 = cocos2d::CCUserDefault::sharedUserDefault(result);

    sub_3A34D8(&v33, &unk_3F92A0, &v31);

    cocos2d::CCUserDefault::getStringForKey(&v32, v4, &v46, &v33);

    v5 = (cocos2d::CCUserDefault *)sub_3A1DDC(&v33);

    if ( v3 == (const char *)&dword_64 )

    {

      v6 = cocos2d::CCUserDefault::sharedUserDefault(v5);

      std::operator+<char,std::char_traits<char>,std::allocator<char>>(&v34, &v32, "MW");

      cocos2d::CCUserDefault::setStringForKey(

        v6,

        (const char *)&v46,

        (const char **)&v34,

        v7,

        v28,

        (int)v29,

        v30,

        *(int (__fastcall **)(int))&v31);

      v8 = &v34;

    }

    else if ( v3 == (const char *)&stru_254.st_value )

    {

      v9 = cocos2d::CCUserDefault::sharedUserDefault(v5);

      std::operator+<char,std::char_traits<char>,std::allocator<char>>(&v35, &v32, "Rf");

      cocos2d::CCUserDefault::setStringForKey(

        v9,

        (const char *)&v46,

        (const char **)&v35,

        v10,

        v28,

        (int)v29,

        v30,

        *(int (__fastcall **)(int))&v31);

      v8 = &v35;

    }

    else if ( v3 == (const char *)&stru_2B4.st_size )

    {

      v11 = cocos2d::CCUserDefault::sharedUserDefault(v5);

      std::operator+<char,std::char_traits<char>,std::allocator<char>>(&v36, &v32, "Rz");

      cocos2d::CCUserDefault::setStringForKey(

        v11,

        (const char *)&v46,

        (const char **)&v36,

        v12,

        v28,

        (int)v29,

        v30,

        *(int (__fastcall **)(int))&v31);

      v8 = &v36;

    }

    else if ( v3 == (const char *)&stru_BB4.st_value )

    {

      v13 = cocos2d::CCUserDefault::sharedUserDefault(v5);

      std::operator+<char,std::char_traits<char>,std::allocator<char>>(&v37, &v32, "Bt");

      cocos2d::CCUserDefault::setStringForKey(

        v13,

        (const char *)&v46,

        (const char **)&v37,

        v14,

        v28,

        (int)v29,

        v30,

        *(int (__fastcall **)(int))&v31);

      v8 = &v37;

    }

    else if ( v3 == (const char *)&stru_15D4.st_info )

    {

      v15 = cocos2d::CCUserDefault::sharedUserDefault(v5);

      std::operator+<char,std::char_traits<char>,std::allocator<char>>(&v38, &v32, "RV");

      cocos2d::CCUserDefault::setStringForKey(

        v15,

        (const char *)&v46,

        (const char **)&v38,

        v16,

        v28,

        (int)v29,

        v30,

        *(int (__fastcall **)(int))&v31);

      v8 = &v38;

    }

    else if ( v3 == (const char *)&stru_26A4.st_size )

    {

      v17 = cocos2d::CCUserDefault::sharedUserDefault(v5);

      std::operator+<char,std::char_traits<char>,std::allocator<char>>(&v39, &v32, "9Z");

      cocos2d::CCUserDefault::setStringForKey(

        v17,

        (const char *)&v46,

        (const char **)&v39,

        v18,

        v28,

        (int)v29,

        v30,

        *(int (__fastcall **)(int))&v31);

      v8 = &v39;

    }

    else if ( v3 == (const char *)&stru_4644.st_info )

    {

      v19 = cocos2d::CCUserDefault::sharedUserDefault(v5);

      std::operator+<char,std::char_traits<char>,std::allocator<char>>(&v40, &v32, "b1");

      cocos2d::CCUserDefault::setStringForKey(

        v19,

        (const char *)&v46,

        (const char **)&v40,

        v20,

        v28,

        (int)v29,

        v30,

        *(int (__fastcall **)(int))&v31);

      v8 = &v40;

    }

    else if ( v3 == (const char *)&stru_15AD4.st_info )

    {

      v21 = cocos2d::CCUserDefault::sharedUserDefault(v5);

      std::operator+<char,std::char_traits<char>,std::allocator<char>>(&v41, &v32, "Vf");

      cocos2d::CCUserDefault::setStringForKey(

        v21,

        (const char *)&v46,

        (const char **)&v41,

        v22,

        v28,

        (int)v29,

        v30,

        *(int (__fastcall **)(int))&v31);

      v8 = &v41;

    }

    else if ( v3 == (const char *)&stru_18694.st_info )

    {

      v23 = cocos2d::CCUserDefault::sharedUserDefault(v5);

      std::operator+<char,std::char_traits<char>,std::allocator<char>>(&v42, &v32, "S2");

      cocos2d::CCUserDefault::setStringForKey(

        v23,

        (const char *)&v46,

        (const char **)&v42,

        v24,

        v28,

        (int)v29,

        v30,

        *(int (__fastcall **)(int))&v31);

      v8 = &v42;

    }

    else

    {

      if ( v3 != (const char *)1000000000 )

      {

LABEL_25:

        v27 = cocos2d::CCString::createWithFormat((cocos2d::CCString *)"%d", v3);

        (*(void (__fastcall **)(_DWORD, _DWORD))(**((_DWORD **)v29 + 66) + 428))(

          *((_DWORD *)v29 + 66),

          *(_DWORD *)(v27 + 20));

        return (cocos2d::CCUserDefault *)sub_3A1DDC(&v32);

      }

      v25 = cocos2d::CCUserDefault::sharedUserDefault(v5);

      std::operator+<char,std::char_traits<char>,std::allocator<char>>(&v43, &v32, "4w");

      cocos2d::CCUserDefault::setStringForKey(

        v25,

        (const char *)&v46,

        (const char **)&v43,

        v26,

        v28,

        (int)v29,

        v30,

        *(int (__fastcall **)(int))&v31);

      v8 = &v43;

    }

    sub_3A1DDC(v8);

    goto LABEL_25;

  }

  return result;

}


대충보니 점수가 1000000000점이면 될 것 같아서 디버깅을 통해 해당 함수의 인자값을 1000000000점으로 바꿔보니 게임이 클리어되면서 잠겨있는 xml파일을 보라고 했다.


shared_prefs 폴더 내에서 Cocos2dxPrefsFile.xml 파일을 확인해보니 base64 인코딩된 값이 존재해서 이걸 디코딩해봤는데 중간의 값들이 깨져있었다. 


0ctf{C0coS2d_AnDro1gs��g�36�3&E��G&�w?}


단순히 게임 클리어만 하는 방식이 아닌 것 같아서 파일 내에 저장되는 base64 인코딩 값이  어떻게 생성되는지 코드를 좀 더 자세히 봤다.


코드 내 분기문들을 보니 게임 클리어과정에서 아래와 같은 형태로 특정 점수들을 지나갈때마다 최종 base64 인코딩 값의 일부 값들이 저장되고 있었다.


if ( v3 == (const char *)&dword_64 )

    {

      v6 = cocos2d::CCUserDefault::sharedUserDefault(v5);

      std::operator+<char,std::char_traits<char>,std::allocator<char>>(&v34, &v32, "MW");

      cocos2d::CCUserDefault::setStringForKey(

        v6,

        (const char *)&v46,

        (const char **)&v34,

        v7,

        v28,

        (int)v29,

        v30,

        *(int (__fastcall **)(int))&v31);

      v8 = &v34;

    }


각 점수별로 저장하는 값들을 다 더해줬고 맨 앞 맨 뒤에 기본적으로 저장되는 값들을 디버깅으로 구해 최종 base64인코딩 값을 만들었다. 이를 디코딩해보면 정상적인 플래그 값이 나온다.


FLAG = 0ctf{C0coS2d_AnDro1d_G0mE_YoU_Kn0w?}






















'CTF > Writeup' 카테고리의 다른 글

Pico CTF 2018 keygen-me-2  (0) 2019.03.22
Tokyo-Westerns-3rd-2017 CTF Rev Rev Rev  (0) 2019.03.15
SSCTF 2016 : Re1  (0) 2019.03.13
Sharif University CTF 2016 : Serial  (0) 2019.03.11
35C3 CTF 2018 COREBOT  (0) 2019.02.26
블로그 이미지

JeonYoungSin

메모 기록용 공간

,

SSCTF 2016 : Re1

CTF/Writeup 2019. 3. 13. 16:53

안드로이드 리버싱 문제다.


앱을 실행해보면 유저명,비밀번호를 입력받아 중국어로된 문구를 띄어준다.


앱을 까서보면 인풋을 네이티브 함수의 인자로 사용하는데 해당 함수 리턴값이 True이면 된다.


ida로 jni 함수를 확인해보면 아래와 같이 상당히 복잡해보이는 코드가 나온다.


bool __fastcall getpl(const char *a1, const char *a2)

{

  int v2; // r7

  int v3; // r0

  const char *i; // r3

  int j; // r7

  int k; // r4

  int v7; // r0

  signed int v8; // r4

  int v9; // r0

  int l; // r6

  int v11; // r0

  _BOOL4 v12; // r3

  const char *v14; // [sp+0h] [bp-B90h]

  signed int v15; // [sp+0h] [bp-B90h]

  const char *v16; // [sp+4h] [bp-B8Ch]

  char v17[12]; // [sp+14h] [bp-B7Ch]

  int v18; // [sp+20h] [bp-B70h]

  int v19; // [sp+2Ch] [bp-B64h]

  int v20; // [sp+30h] [bp-B60h]

  const char *v21; // [sp+50h] [bp-B40h]

  const char *v22; // [sp+54h] [bp-B3Ch]

  const char *v23; // [sp+58h] [bp-B38h]

  const char *v24; // [sp+5Ch] [bp-B34h]

  const char *v25; // [sp+60h] [bp-B30h]

  const char *v26; // [sp+64h] [bp-B2Ch]

  const char *v27; // [sp+68h] [bp-B28h]

  const char *v28; // [sp+6Ch] [bp-B24h]

  const char *v29; // [sp+70h] [bp-B20h]

  const char *v30; // [sp+74h] [bp-B1Ch]

  const char *v31; // [sp+78h] [bp-B18h]

  const char *v32; // [sp+7Ch] [bp-B14h]

  const char *v33; // [sp+80h] [bp-B10h]

  const char *v34; // [sp+84h] [bp-B0Ch]

  const char *v35; // [sp+88h] [bp-B08h]

  const char *v36; // [sp+8Ch] [bp-B04h]

  const char *v37; // [sp+90h] [bp-B00h]

  const char *v38; // [sp+94h] [bp-AFCh]

  const char *v39; // [sp+98h] [bp-AF8h]

  const char *v40; // [sp+9Ch] [bp-AF4h]

  const char *v41; // [sp+A0h] [bp-AF0h]

  const char *v42; // [sp+A4h] [bp-AECh]

  const char *v43; // [sp+A8h] [bp-AE8h]

  const char *v44; // [sp+ACh] [bp-AE4h]

  char v45; // [sp+118h] [bp-A78h]

  int v46; // [sp+ADCh] [bp-B4h]

  int v47; // [sp+AE8h] [bp-A8h]

  int v48; // [sp+AF4h] [bp-9Ch]

  int v49; // [sp+B34h] [bp-5Ch]


  v16 = a2;

  v14 = a1;

  j_memset(&v21, 0, 200);

  v21 = "103066";

  v22 = "78yn";

  v2 = 0;

  v23 = "91";

  v24 = "jk@O";

  v25 = "w0%3";

  v26 = "okJni";

  v27 = "32tP";

  v28 = "pYn";

  v29 = "oty3e";

  v30 = "ss";

  v31 = "whw3";

  v32 = "Gh$";

  v33 = "ju3t";

  v34 = "g986";

  v35 = "rTp0";

  v36 = "J)g";

  v37 = "a";

  v38 = "pLoKm7";

  v39 = "0o7";

  v40 = "******";

  v41 = "plokm7";

  v42 = "SSCFlg2016";

  v43 = "aP$";

  v44 = "kEy";

  j_memcpy(v17, off_B309F004, 60);

  j_memset(&v48, 0, 64);

  j_memset(&v46, 0, 10);

  j_memset(&v47, 0, 10);

  j_memset(&v49, 0, 64);

  byte_B309F109 = 0;

  v3 = j_strlen(v14);

  for ( i = v14; i - v14 < v3; ++i )

  {

    if ( (unsigned int)*(unsigned __int8 *)i - 33 <= 0x5D )

      *((_BYTE *)&v47 + v2++) = *i;

  }

  for ( j = 0; *(&v21)[j] != 42; ++j )

    ;

  j_qsort(&v21, j, 4, cmp_string);

  for ( k = 0; k < j; ++k )

  {

    j_strcpy(&v45 + 50 * k, (&v21)[k]);

    v7 = j_strlen(&v45 + 50 * k);

    j_qsort(&v45 + 50 * k, v7, 1, cmp_char);

  }

  v8 = 0;

  do

  {

    while ( 1 )

    {

      j_strcpy(&v46, *(_DWORD *)&v17[4 * v8]);

      v9 = j_strlen(&v46);

      j_qsort(&v46, v9, 1, cmp_char);

      v15 = 0;

      for ( l = 0; l < j; ++l )

      {

        if ( !j_strcmp(&v45 + 50 * l, &v46) )

        {

          j_strcat(&v48, (&v21)[l]);

          v15 = 1;

        }

      }

      if ( v15 )

        break;

      switch ( v8 )

      {

        case 3:

          v8 = 4;

          j_strcat(&v48, v43);

          break;

        case 6:

          j_sprintf(&v49, "%c", 105);

          v8 = 7;

          j_strcat(&v48, &v49);

          break;

        case 11:

          v8 = 12;

          j_strcat(&v48, v20);

          break;

        default:

          goto LABEL_25;

      }

    }

LABEL_25:

    ++v8;

  }

  while ( v8 != 15 );

  j_strcat(&v48, &v47);

  j_sprintf(&v49, "%c", 125);

  j_strcat(&v48, &v49);

  j_sprintf(&v49, "%c", 123);

  j_strcat(&v49, &v48);

  j_strcat(&pl, v19);

  j_strcat(&pl, v18);

  j_strcat(&pl, &v49);

  if ( !v16 )

    return 0;

  v11 = j_strlen(v16);

  v12 = 0;

  if ( v11 == 0x27 )

    v12 = (unsigned int)j_strncmp(&pl, v16) <= 0;

  return v12;

}


근데 코드를 쭉 분석해보면 사실 인풋으로 들어온 유저명, 패스워드를 코드 내에서 전혀 사용하지 않고 있다가 마지막에 패스워드 길이 구하고 특정 값이랑 비교할때만 사용하고 있다.


즉, 저위의 복잡해보이는 코드를 분석할 필요가 없다는 거고 그냥 마지막에 strncmp에서 들어온 값만 확인해주면 된다.


디버깅을 통해 해당 값을 확인해봤고 플래그가 있었다.



FLAG = SSCTF{oty3eaP$g986iwhw32j%OJ)g0o7J.CG:}

'CTF > Writeup' 카테고리의 다른 글

Tokyo-Westerns-3rd-2017 CTF Rev Rev Rev  (0) 2019.03.15
0CTF 2016 Quals : boomshakalaka  (0) 2019.03.14
Sharif University CTF 2016 : Serial  (0) 2019.03.11
35C3 CTF 2018 COREBOT  (0) 2019.02.26
Evlz CTF 2019 Smol-Big  (0) 2019.02.20
블로그 이미지

JeonYoungSin

메모 기록용 공간

,

해당 바이너리를 ida로 까서보면 안티 디스어셈블리가 적용되어 있어 헥스레이 및 어셈블리 정적분석을 하기가 까다로웠다.


적용되어있던 안티 디스어셈블리 방식은 아래와 같았다.



jz short near ptr loc_4009f3+2


위 명령어를 보면 4009f5 주소를 점프를 하는데 ida에서는 해당 주소부터 디스어셈블하여 명령어를 해석해 주지 않고 있었다.


이를 해결하기 위해 동적분석을 진행했고 jz short near ptr loc_4009f3+2 명령어 실행 후  4009f5 주소로 점프했을때 ida에서 data 값으로 인식된 바이트 코드를 code로 인식하도록 convert해서 정상적으로 디스어셈블 하도록 해줬다.


이런식으로 계속 안티 디스어셈블을 우회해서 동적분석 진행하다보면 시리얼 비교 루틴이 나오는데 비교 로직은 간단했다.


flag_front = "EZ9dmq4c"

flag_end = ""

for i in range(0,len(flag_front)):

    if i==4:

        flag_end += chr(0xb4-ord(flag_front[i]))

    elif i==5:

        flag_end += chr(0xaa - ord(flag_front[i]))

    else:

        flag_end += chr(0x9b-ord(flag_front[i]))


print "FLAG = " + flag_front + flag_end[::-1]


FLAG = EZ9dmq4c8g9G7bAV













'CTF > Writeup' 카테고리의 다른 글

0CTF 2016 Quals : boomshakalaka  (0) 2019.03.14
SSCTF 2016 : Re1  (0) 2019.03.13
35C3 CTF 2018 COREBOT  (0) 2019.02.26
Evlz CTF 2019 Smol-Big  (0) 2019.02.20
Codegate CTF 2019 KingMaker  (0) 2019.02.16
블로그 이미지

JeonYoungSin

메모 기록용 공간

,

ROOT-ME ELF - Anti debug

2019. 3. 8. 16:37

보호되어 있는 글입니다.
내용을 보시려면 비밀번호를 입력하세요.

ROOT-ME APK - Anti-debug

2019. 3. 8. 15:06

보호되어 있는 글입니다.
내용을 보시려면 비밀번호를 입력하세요.

ROOT-ME PE - SEHVEH

2019. 3. 8. 13:34

보호되어 있는 글입니다.
내용을 보시려면 비밀번호를 입력하세요.

ROOT-ME Crackme automating

2019. 3. 7. 14:06

보호되어 있는 글입니다.
내용을 보시려면 비밀번호를 입력하세요.

Ida arm Debugging(Qemu)

2019. 3. 6. 13:10

보호되어 있는 글입니다.
내용을 보시려면 비밀번호를 입력하세요.

ROOT-ME ELF ARM - crackme 1337

2019. 3. 6. 11:49

보호되어 있는 글입니다.
내용을 보시려면 비밀번호를 입력하세요.

ROOT-ME ELF - Random Crackme

2019. 3. 6. 10:38

보호되어 있는 글입니다.
내용을 보시려면 비밀번호를 입력하세요.