안드로이드 게임 앱 리버싱 문제다.
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?}