【攻防世界】boomshakalaka-3 writeup

在FirstTest中,马上注意到有个“flag” ,它被传入到类“a”。但Base64解密该字符串得到Bazingaa,咿呀,这不就是Sheldon的口头禅,逗你玩的意思。

protected void onCreate(Bundle savedInstanceState){
       super.onCreate(savedInstanceState);
       String flag = "YmF6aW5nYWFhYQ==";
       a haha = new a(this, "flag");
       haha.d(flag);
       a hehe = new a(this, "Cocos2dxPrefsFile");
       hehe.d("N0");
    }

“a”类里的逻辑也很简单,

public class a	// class@000054 from classes.dex
{
    private SharedPreferences editor;

    public void a(Context arg1,String arg2){
       super();
       this.editor = null;
       this.editor = arg1.getSharedPreferences(arg2, 0);
    }
    public void b(){
       this.editor.edit().putString("DATA", "").commit();
    }
    public String c(){
       return this.editor.getString("DATA", "");
    }
    public void d(String arg1){
       this.editor.edit().putString("DATA", String.valueOf(String.valueOf(this.c()))+arg1).commit();
    }
}

它使用了SharedPreference存储数据,SharedPreferences是Android平台上一个轻量级的存储类,主要是保存一些常用的配置比如窗口状态。这意味着我们可以在/data/data/{package名}SharedPrefs中可能会找到需要的数据。

接下来,我以为只要将MGN0ZntDMGNvUzJkX0FuRHJvMWdz99 这个Base64编码的数据解密就行。

0ctf{C0coS2d_AnDro1gs÷

明显结果是不对的,或是不完整的。

在我完了几盘游戏,发现Cocos2dxPrefsFile.xml发生变化

<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
    <string name="DATA">MGN0ZntDMGNvUzJkX0FuRHJvMWdz99MGN0ZntDMGNvUzJkX0FuRHJvdz99ZntDMGNvUzJkX0FuRHJvMWdz99MGN0ZntDMGNvUzJkX0FuRHJvMWRfdz99</string>
    <boolean name="isHaveSaveFileXml" value="true" />
    <int name="HighestScore" value="4700" />
</map>

它有部分字符串是重复出现的,规律大概是

MGN0ZntDMGNvUzJkX0FuRHJv + (这部分有时候不一样,是MW/Rf)+dz99

可能是每完一次游戏都会更新的,而且更新的内容有部分是不一样的,猜测可能是分数/时间?不一样,所以有变动

用IDA逆向so文件,找到了UpdateScore,印证我的想法。

cocos2d::CCUserDefault *__fastcall ControlLayer::updateScore(cocos2d::CCUserDefault *result, const char *a2)
{
  int v2; // r3
  int v4; // r7
  cocos2d::CCUserDefault *v5; // r0
  int v6; // r5
  char *v7; // r0
  int v8; // r5
  int v9; // r5
  int v10; // r5
  int v11; // r5
  int v12; // r5
  int v13; // r5
  int v14; // r5
  int v15; // r5
  int v16; // r5
  int v17; // r0
  cocos2d::CCUserDefault *v18; // [sp+4h] [bp-64h]
  char v19[4]; // [sp+Ch] [bp-5Ch] BYREF
  char v20[4]; // [sp+10h] [bp-58h] BYREF
  char v21[4]; // [sp+14h] [bp-54h] BYREF
  char v22[4]; // [sp+18h] [bp-50h] BYREF
  char v23[4]; // [sp+1Ch] [bp-4Ch] BYREF
  char v24[4]; // [sp+20h] [bp-48h] BYREF
  char v25[4]; // [sp+24h] [bp-44h] BYREF
  char v26[4]; // [sp+28h] [bp-40h] BYREF
  char v27[4]; // [sp+2Ch] [bp-3Ch] BYREF
  char v28[4]; // [sp+30h] [bp-38h] BYREF
  char v29[4]; // [sp+34h] [bp-34h] BYREF
  char v30[4]; // [sp+38h] [bp-30h] BYREF
  char v31[4]; // [sp+3Ch] [bp-2Ch] BYREF
  char v32[8]; // [sp+40h] [bp-28h] BYREF
  int v33; // [sp+48h] [bp-20h] BYREF
  char v34; // [sp+4Ch] [bp-1Ch]

  strcpy(v32, "data");
  v2 = 0;
  v34 = 0;
  v18 = result;
  v33 = 0;
  do
  {
    *((_BYTE *)&v33 + v2) = v32[v2] ^ 0x20;
    ++v2;
  }
  while ( v2 != 4 );
  if ( (unsigned int)a2 <= 0x3B9ACA00 )
  {
    v4 = cocos2d::CCUserDefault::sharedUserDefault(result);
    sub_3A34D8(v21, &byte_3F92A0, v19);
    cocos2d::CCUserDefault::getStringForKey(v20, v4, &v33, v21);
    v5 = (cocos2d::CCUserDefault *)sub_3A1DDC(v21);
    if ( a2 == (const char *)&dword_64 )
    {
      v6 = cocos2d::CCUserDefault::sharedUserDefault(v5);
      std::operator+<char>(v22, v20, "MW");
      cocos2d::CCUserDefault::setStringForKey(v6, (const char *)&v33, (const char **)v22);
      v7 = v22;
    }
    else if ( a2 == (const char *)&stru_254.st_value )
    {
      v8 = cocos2d::CCUserDefault::sharedUserDefault(v5);
      std::operator+<char>(v23, v20, "Rf");
      cocos2d::CCUserDefault::setStringForKey(v8, (const char *)&v33, (const char **)v23);
      v7 = v23;
    }
    else if ( a2 == (const char *)&stru_2B4.st_size )
    {
      v9 = cocos2d::CCUserDefault::sharedUserDefault(v5);
      std::operator+<char>(v24, v20, "Rz");
      cocos2d::CCUserDefault::setStringForKey(v9, (const char *)&v33, (const char **)v24);
      v7 = v24;
    }
    else if ( a2 == (const char *)&stru_BB4.st_value )
    {
      v10 = cocos2d::CCUserDefault::sharedUserDefault(v5);
      std::operator+<char>(v25, v20, "Bt");
      cocos2d::CCUserDefault::setStringForKey(v10, (const char *)&v33, (const char **)v25);
      v7 = v25;
    }
    else if ( a2 == (const char *)&stru_15D4.st_info )
    {
      v11 = cocos2d::CCUserDefault::sharedUserDefault(v5);
      std::operator+<char>(v26, v20, "RV");
      cocos2d::CCUserDefault::setStringForKey(v11, (const char *)&v33, (const char **)v26);
      v7 = v26;
    }
    else if ( a2 == (const char *)&stru_26A4.st_size )
    {
      v12 = cocos2d::CCUserDefault::sharedUserDefault(v5);
      std::operator+<char>(v27, v20, "9Z");
      cocos2d::CCUserDefault::setStringForKey(v12, (const char *)&v33, (const char **)v27);
      v7 = v27;
    }
    else if ( a2 == (const char *)&stru_4644.st_info )
    {
      v13 = cocos2d::CCUserDefault::sharedUserDefault(v5);
      std::operator+<char>(v28, v20, "b1");
      cocos2d::CCUserDefault::setStringForKey(v13, (const char *)&v33, (const char **)v28);
      v7 = v28;
    }
    else if ( a2 == (const char *)&stru_15AD4.st_info )
    {
      v14 = cocos2d::CCUserDefault::sharedUserDefault(v5);
      std::operator+<char>(v29, v20, "Vf");
      cocos2d::CCUserDefault::setStringForKey(v14, (const char *)&v33, (const char **)v29);
      v7 = v29;
    }
    else if ( a2 == (const char *)&stru_18694.st_info )
    {
      v15 = cocos2d::CCUserDefault::sharedUserDefault(v5);
      std::operator+<char>(v30, v20, "S2");
      cocos2d::CCUserDefault::setStringForKey(v15, (const char *)&v33, (const char **)v30);
      v7 = v30;
    }
    else
    {
      if ( a2 != (const char *)1000000000 )
      {
LABEL_25:
        v17 = cocos2d::CCString::createWithFormat((cocos2d::CCString *)"%d", a2);
        (*(void (__fastcall **)(_DWORD, _DWORD))(**((_DWORD **)v18 + 66) + 428))(
          *((_DWORD *)v18 + 66),
          *(_DWORD *)(v17 + 20));
        return (cocos2d::CCUserDefault *)sub_3A1DDC(v20);
      }
      v16 = cocos2d::CCUserDefault::sharedUserDefault(v5);
      std::operator+<char>(v31, v20, "4w");
      cocos2d::CCUserDefault::setStringForKey(v16, (const char *)&v33, (const char **)v31);
      v7 = v31;
    }
    sub_3A1DDC(v7);
    goto LABEL_25;
  }
  return result;
}

其中下面这段代码证明,每次都会获取之前的xml文件中“DATA”的数据,然后添加新的,最后写回到xml文件

do
  {
    *((_BYTE *)&v33 + v2) = v32[v2] ^ 0x20;
    ++v2;
  }
  while ( v2 != 4 );
  if ( (unsigned int)a2 <= 0x3B9ACA00 )
  {
    v4 = cocos2d::CCUserDefault::sharedUserDefault(result);
    sub_3A34D8(v21, &byte_3F92A0, v19);
    cocos2d::CCUserDefault::getStringForKey(v20, v4, &v33, v21);
    v5 = (cocos2d::CCUserDefault *)sub_3A1DDC(v21);

然后下面的代码是个if/else的选择,发现在if分支里有之前的“MW”和“rf”。根据什么进行选择呢?大概是分数的量化级别,

 if ( (unsigned int)a2 <= 0x3B9ACA00 )
  {
    v4 = cocos2d::CCUserDefault::sharedUserDefault(result);
    sub_3A34D8(v21, &byte_3F92A0, v19);
    cocos2d::CCUserDefault::getStringForKey(v20, v4, &v33, v21);
    v5 = (cocos2d::CCUserDefault *)sub_3A1DDC(v21);
    if ( a2 == (const char *)&dword_64 )
    {
      v6 = cocos2d::CCUserDefault::sharedUserDefault(v5);
      std::operator+<char>(v22, v20, "MW");
      cocos2d::CCUserDefault::setStringForKey(v6, (const char *)&v33, (const char **)v22);
      v7 = v22;
    }
    else if ( a2 == (const char *)&stru_254.st_value )
    {
      v8 = cocos2d::CCUserDefault::sharedUserDefault(v5);
      std::operator+<char>(v23, v20, "Rf");
      cocos2d::CCUserDefault::setStringForKey(v8, (const char *)&v33, (const char **)v23);
      v7 = v23;
    }
    else if ( a2 == (const char *)&stru_2B4.st_size )
    {
      v9 = cocos2d::CCUserDefault::sharedUserDefault(v5);
      std::operator+<char>(v24, v20, "Rz");
      cocos2d::CCUserDefault::setStringForKey(v9, (const char *)&v33, (const char **)v24);
      v7 = v24;
    }
    else if ( a2 == (const char *)&stru_BB4.st_value )
    {
      v10 = cocos2d::CCUserDefault::sharedUserDefault(v5);
      std::operator+<char>(v25, v20, "Bt");
      cocos2d::CCUserDefault::setStringForKey(v10, (const char *)&v33, (const char **)v25);
      v7 = v25;
    }
    else if ( a2 == (const char *)&stru_15D4.st_info )
    {
      v11 = cocos2d::CCUserDefault::sharedUserDefault(v5);
      std::operator+<char>(v26, v20, "RV");
      cocos2d::CCUserDefault::setStringForKey(v11, (const char *)&v33, (const char **)v26);
      v7 = v26;
    }
    else if ( a2 == (const char *)&stru_26A4.st_size )
    {
      v12 = cocos2d::CCUserDefault::sharedUserDefault(v5);
      std::operator+<char>(v27, v20, "9Z");
      cocos2d::CCUserDefault::setStringForKey(v12, (const char *)&v33, (const char **)v27);
      v7 = v27;
    }
    else if ( a2 == (const char *)&stru_4644.st_info )
    {
      v13 = cocos2d::CCUserDefault::sharedUserDefault(v5);
      std::operator+<char>(v28, v20, "b1");
      cocos2d::CCUserDefault::setStringForKey(v13, (const char *)&v33, (const char **)v28);
      v7 = v28;
    }
    else if ( a2 == (const char *)&stru_15AD4.st_info )
    {
      v14 = cocos2d::CCUserDefault::sharedUserDefault(v5);
      std::operator+<char>(v29, v20, "Vf");
      cocos2d::CCUserDefault::setStringForKey(v14, (const char *)&v33, (const char **)v29);
      v7 = v29;
    }
    else if ( a2 == (const char *)&stru_18694.st_info )
    {
      v15 = cocos2d::CCUserDefault::sharedUserDefault(v5);
      std::operator+<char>(v30, v20, "S2");
      cocos2d::CCUserDefault::setStringForKey(v15, (const char *)&v33, (const char **)v30);
      v7 = v30;
    }
    else
    {
      if ( a2 != (const char *)1000000000 )
      {
LABEL_25:
        v17 = cocos2d::CCString::createWithFormat((cocos2d::CCString *)"%d", a2);
        (*(void (__fastcall **)(_DWORD, _DWORD))(**((_DWORD **)v18 + 66) + 428))(
          *((_DWORD *)v18 + 66),
          *(_DWORD *)(v17 + 20));
        return (cocos2d::CCUserDefault *)sub_3A1DDC(v20);
      }
      v16 = cocos2d::CCUserDefault::sharedUserDefault(v5);
      std::operator+<char>(v31, v20, "4w");
      cocos2d::CCUserDefault::setStringForKey(v16, (const char *)&v33, (const char **)v31);
      v7 = v31;
    }
    sub_3A1DDC(v7);
    goto LABEL_25;
  }

如果把所有的分数级别都玩过,是不是就当作是完整通过了呢,把所有这些串在一起得到:MWRfRzBtRV9Zb1VfS24w,加上之前的前缀MGN0ZntDMGNvUzJkX0FuRHJv和后缀dz99,得到:

MGN0ZntDMGNvUzJkX0FuRHJvMWRfRzBtRV9Zb1VfS24wdz99

解码该Base64,得到正确的flag。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值