Frida解CTF:攻防世界-mobile 进阶 boomshakalaka-3

前言

作者:poilzero
博客:http://poilzero.cn
image.png

主逻辑

同一个package中的a类需要进一步分析

其中使用到了SharedPreferences,这是android lib层提供的一个边界的存储库

  • 使用xml语法保存内容,并对其操作函数封装
  • 对应的数据保存路径在:/data/data/包名/shared_prefs/xx.xml

注释如下

package com.example.plane;

import android.os.Bundle;
import org.cocos2dx.lib.Cocos2dxActivity;
import org.cocos2dx.lib.Cocos2dxGLSurfaceView;

public class FirstTest extends Cocos2dxActivity {
    static {
        System.loadLibrary("cocos2dcpp");
    }

    @Override  // org.cocos2dx.lib.Cocos2dxActivity
    protected void onCreate(Bundle arg5) {
        super.onCreate(arg5);
        new a(this, "flag").d("YmF6aW5nYWFhYQ==");//创建flag.xml附加保存YmF6aW5nYWFhYQ==
        new a(this, "Cocos2dxPrefsFile").d("N0");//创造Cocos2dxPrefsFile.xml附加保存NO
    }

    @Override  // org.cocos2dx.lib.Cocos2dxActivity
    public Cocos2dxGLSurfaceView onCreateView() {
        Cocos2dxGLSurfaceView glSurfaceView = new Cocos2dxGLSurfaceView(this);
        new a(this, "Cocos2dxPrefsFile").d("MG");
        glSurfaceView.setEGLConfigChooser(5, 6, 5, 0, 16, 8);
        return glSurfaceView;
    }
}
package com.example.plane;

import android.content.Context;
import android.content.SharedPreferences;

public class a {
    private SharedPreferences editor;//SharedPreferences

    public a(Context arg1, String arg2) {
        this.editor = null;
        this.editor = arg1.getSharedPreferences(arg2, 0); //获取sharedPreferences对象,文件不存在的时候创建空文件
    }

    public void b() {//设置文件DATA的值为空(初始化)
        this.editor.edit().putString("DATA", "").commit();//
    }

    public String c() {//得到文件DATA的值中
        return this.editor.getString("DATA", "");
    }

    public void d(String arg1) {//保存 DATA原来的的内容+arg1 到文件DATA键值中
        this.editor.edit().putString("DATA", String.valueOf(String.valueOf(this.c())) + arg1).commit();
    }
}

游戏逻辑:分数修改

查看文件后发现flag.xml是假flag,真正的flag是根据分数动态保存到Cocos2dxPrefsFile.xml中,因为cocos2dx的游戏逻辑主要在so文件中,对其进行分析,通过score关键字查找分数相关的函数。

发现可疑函数:updateScore(其对Cocos2dxPrefsFile.xml文件进行了写入操作)

cocos2d::CCUserDefault *__fastcall ControlLayer::updateScore(cocos2d::CCUserDefault *this, int a2)
{
    //define ...
    strcpy(v32, "data");
    v2 = 0;
    v34 = 0;
    v18 = this;
    v33 = 0;
    do
    {
        *((_BYTE *)&v33 + v2) = v32[v2] ^ 0x20;
        ++v2;
    }
    while ( v2 != 4 );
    if ( (unsigned int)a2 <= 0x3B9ACA00 )
    {
        v4 = cocos2d::CCUserDefault::sharedUserDefault(this);
        sub_3A34D8(v21, &byte_3F92A0, v19);
        cocos2d::CCUserDefault::getStringForKey(v20, v4, &v33, v21);
        v5 = (cocos2d::CCUserDefault *)sub_3A1DDC(v21);
        switch ( a2 )
        {
            case 100:
                v6 = cocos2d::CCUserDefault::sharedUserDefault(v5);
                std::operator+<char>(v22, v20, "MW");
                cocos2d::CCUserDefault::setStringForKey(v6, &v33, v22);
                v7 = v22;
                break;
            case 600:
                v8 = cocos2d::CCUserDefault::sharedUserDefault(v5);
                std::operator+<char>(v23, v20, "Rf");
                cocos2d::CCUserDefault::setStringForKey(v8, &v33, v23);
                v7 = v23;
                break;
            case 700:
                v9 = cocos2d::CCUserDefault::sharedUserDefault(v5);
                std::operator+<char>(v24, v20, "Rz");
                cocos2d::CCUserDefault::setStringForKey(v9, &v33, v24);
                v7 = v24;
                break;
            default:
                if ( (unsigned __int32 *)a2 == &stru_BB4.st_value )
                {
                    v10 = cocos2d::CCUserDefault::sharedUserDefault(v5);
                    std::operator+<char>(v25, v20, "Bt");
                    cocos2d::CCUserDefault::setStringForKey(v10, &v33, v25);
                    v7 = v25;
                }
                else if ( a2 == 5600 )
                {
                    v11 = cocos2d::CCUserDefault::sharedUserDefault(v5);
                    std::operator+<char>(v26, v20, "RV");
                    cocos2d::CCUserDefault::setStringForKey(v11, &v33, v26);
                    v7 = v26;
                }
                else if ( (unsigned __int32 *)a2 == &stru_26A4.st_size )
                {
                    v12 = cocos2d::CCUserDefault::sharedUserDefault(v5);
                    std::operator+<char>(v27, v20, "9Z");
                    cocos2d::CCUserDefault::setStringForKey(v12, &v33, v27);
                    v7 = v27;
                }
                else if ( (unsigned __int8 *)a2 == &stru_4644.st_info )
                {
                    v13 = cocos2d::CCUserDefault::sharedUserDefault(v5);
                    std::operator+<char>(v28, v20, "b1");
                    cocos2d::CCUserDefault::setStringForKey(v13, &v33, v28);
                    v7 = v28;
                }
                else if ( (unsigned __int8 *)a2 == &stru_15AD4.st_info )
                {
                    v14 = cocos2d::CCUserDefault::sharedUserDefault(v5);
                    std::operator+<char>(v29, v20, "Vf");
                    cocos2d::CCUserDefault::setStringForKey(v14, &v33, v29);
                    v7 = v29;
                }
                else if ( (unsigned __int8 *)a2 == &stru_18694.st_info )
                {
                    v15 = cocos2d::CCUserDefault::sharedUserDefault(v5);
                    std::operator+<char>(v30, v20, "S2");
                    cocos2d::CCUserDefault::setStringForKey(v15, &v33, v30);
                    v7 = v30;
                }
                else
                {
                    if ( a2 != 1000000000 )
                    {
                        LABEL_25:
                        v17 = cocos2d::CCString::createWithFormat((cocos2d::CCString *)"%d", (const char *)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, &v33, v31);
                    v7 = v31;
                }
                break;
        }
        sub_3A1DDC(v7);
        goto LABEL_25;
    }
    return this;
}

exp

游戏启动状态下暂停游戏

使用frida主动调用updateScore函数每次增加一百,因为不可能全部穷举一遍,特大的单独调用一次就好。

之所以调用从第二次开始,因为如果从第一次开始,第一次的分数是100,我主动调用也有100,会重复添加字符

要是从第一次开始主动调用从200开始,当我主动调用结束,还会添加一个分数是100的字符

(游戏结束才会添加末尾的剩余字符到文件中)

  • exp_frida_inject.py
  • exp_frida_inject.js
# exp_frida_inject.py
import frida
import sys

device = frida.get_usb_device()
pid = device.spawn(["com.example.plane"])
session = device.attach(pid)
device.resume(pid)

import time
time.sleep(0.1)

def on_message(message, data):
    # assert None==message.get('payload', None)
    print(message)

with open('exp_frida_inject.js', 'rt', encoding='utf8') as fs:
    script = fs.read()
script = session.create_script(script)
script.on("message", on_message)
script.load()
sys.stdin.read()
//exp_frida_inject.js
var str_name_so = "libcocos2dcpp.so";    //要hook的so名
var n_addr_func_offset = 0x015C9E4;    //0x0DC8;

//加载到内存后 函数地址 = so地址 + 1 + 函数偏移
var n_addr_so = Module.findBaseAddress(str_name_so);
var n_addr_func = n_addr_so.add(n_addr_func_offset + 1);
var ptr_func = new NativePointer(n_addr_func);

var ptr_ccud;
var cnt = 0;
function updata_score(score){
    var cocos_updata_score = new NativeFunction(ptr_func, 'pointer', ['pointer', 'int'], ['fastcall']);
    return cocos_updata_score(ptr_ccud, score)
}
Interceptor.attach(ptr_func, {
    onEnter:
        function(args){
            cnt++;
            //args[0] this
            args[1] score
            console.log("call "+cnt+" times. value: "+args[1]+". address: "+args[0]);

            ptr_ccud = args[0]; 
            if(cnt==2){
                for(var i=200; i<0xACA00; i+=100){//遍历到最大时间不够,特大数单独处理了
                    var ret_ptr = updata_score(i);
                }
                updata_score(1000000000);//so中一个特别大的需要写入的分数值,遍历不到
                console.log("success call");
            }
        },
    onLeave:
        function(retval){
       }
});

最后读取文件debase后得到:0ctf{C0coS2d_AnDro1d_G0mE_YoU_Kn0w?}

cancro:/data/data/com.example.plane/shared_prefs # cat Cocos2dxPrefsFile.xml
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
    <int name="HighestScore" value="400" />
    <string name="DATA">MGN0ZntDMGNvUzJkX0FuRHJvMWRfRzBtRV9Zb1VfS24wdz99</string>
    <boolean name="isHaveSaveFileXml" value="true" />
</map>
Last modification:October 30, 2021
如果觉得我的文章对你有用,请随意赞赏。咖啡(12RMB)进度+100%,一块巧克力(1RMB)进度+6%。
(赞赏请备注你的名称哦!后台记录中来自理工小菜狗)