Arcaea 的一些破解记录

 

免责声明:本博文相关内容已涉及 lowiro 的实际利益。本博文内容仅用于个人软件安全研究与学习。请勿将博文内容用于商业或者非法用途,如果阁下愿意继续阅读,请您承诺将为自己的全部行为负责。

出处

本文基于Arcaea 3.7.0c(及以前的)(或许还会有以后的)版本,arm64-v8a

工具

IDA

一些功能&快捷键:

  • 数据库快照:存放数据库的历史更改Ctrl+Shift+T
  • F5:快速切换至伪代码

插件

Bindiff

Keypatch

Frida

gdb

众所周知,gdb是一个功能强大的调试器,我们可以用它来调试native层的程序

安装

首先,最好不要用Windows,坑太多了(如果你成功了请教教我)

我们可以用WSL+Ubuntu,以下内容皆基于此基础展开`

由于目标是aarch64架构,所以需要多架构的gdb(这也就是不用Windows的原因)。执行sudo apt install gdb-multiarch,随后执行gdb-multiarch以确定安装成功

gdbserver可以在Android NDKprebuilt/android-arm64/gdbserver/目录下找到,也可以用我上传的版本(gdbserver 7.11 for arm64,来自NDK 19c for linux)

把gdbserver丢到/data/local/tmp/目录下,赋予777权限

连接

手机端打开arc、usb调试开关

(adb可以用Windows上的)使用adb连接手机(有线/无线),输入adb forward tcp:23946 tcp:23946

进入shell(adb shell),切换root(su)

执行pid=`ps | grep moe | awk '{print $2}'`;echo $pid,得到的数字即为arc的PID(在一些设备上,ps要改为ps -A

执行cat /proc/$pid/maps | grep cocos | grep '[0-9a-f]*' -o | sed -n '1,1p',得到一个16进制数(形如7f68dcd000),把他丢python里,记为base_addr,这就是so文件的基址[需要验证]

执行/data/local/tmp/gdbserver :23946 --attach $pid,开启gdb服务端

一行版,用于开始程序后立刻attach:pkg="moe.low.arc";killall $pkg;am start -W $pkg/low.moe.AppActivity>/dev/null;pid=`pidof $pkg`;cat /proc/$pid/maps | grep cocos | grep '[0-9a-f]*' -o | sed -n '1,1p';/data/local/tmp/gdbserver :23946 --attach $pid

(注意:上述命令可能因环境不同而变化,请按需更改!)

打开gdb-multiarch,输入target remot localhost:23946,等待gdb加载(中途的分页按c以继续)

加载完成后,光标停留在(gdb)后面,此时可以输入指令

使用

进程暂停时输入c以恢复进程运行,进程运行时输入Ctrl+C以暂停进程

在IDA找到你需要下断点的位置(形如0x1F3FF),记作offset,进入python,执行base_addr+offset,得到一个十进制数(形如547220276223),回到gdb输入b *547220276223(得到的十进制数)下断点

输入i reg可以查看寄存器的值,如果想查看含向量寄存器和浮点寄存器的值,则需要使用i all-reg

ni替代n,用si替代s来步进

更多用法可参考其它教程,比如这篇

核心破解

需要Xposed框架(作用域:Android系统),用于绕过签名、版本校验(可以直接安装签名错误或没有签名的apk)

下载地址:

NS相关

hactool

nx2elf & elf2nso

内容

.so相关

ScoreState结构体

警告:本表可能存在问题,如发现问题请提Issue

(原版来自西城飘雪的博客

struct ScoreState {
    pointer vtable;    // 0 0 指向vtable的指针 占8字节
    unsigned int _referenceCount;//8 8 cocos2d内部使用
    //skip
    int noteCount;    // 16    10
    int maxCombo;    // 20    14
    int score;        // 24    18
    int score_check;    // 28    1c        score / 3
    float hp;        // 32  20
    float unk36      //36 24
    float unk40         //40 28
    BYTE unk44         //44 2c
    BYTE unk45         //45 2d
    float unk48         // 48 30 貌似恒为0
    float unk52         //52 34 仅风暴
    float unk56         //56 38 仅风暴/Ether Strike异象
    float unk60         //60 3C 仅风暴 回忆系数*物量
    float unk64         //64 40 仅风暴 初始化:100-unk60 if unk60<100 else 0;更新:继承上次更新的v16
    float hit_hps         //68 44 仅风暴 将在击中note时增加应增加的回忆度(pure:2*回忆系数 far:1*回忆系数)
    float nonhit_hps         //72 48 仅风暴 将在未击中note时减少应扣除的回忆度(同一般Hard血条一致)
    float unk76         //76 4c 仅风暴
    int combo;        // 80  50
    int shiny_pure; // 84  54
    int pure;         // 88  58
    int far;         // 92  5c
    int lost;         // 96  60
    int shiny_pure_check    // 100 64          shiny_pure/6
    int pure_check    // 104 68          pure/5
    int far_check    // 108 6c          far*2
    int lost_check     // 112 70        lost*6
    int far_late         //116 74 far的late数
    int far_early         //120 78 far的early数
    int pure_late         //124 7c 小p的late数
    int pure_early         //128 80 小p的early数
    bool(?) unk132        //132 84
    float hp_base_value // 136 88 回忆系数(风暴时为回忆系数*2)
    int unk92       //13a 92
    int skill_type; // 152    98        //0普通,1简单,2困难,5过载,6风暴
    int unk156      //156 9c
    LogicChart* lc   //160 A0 占8字节
    GameResult* gr   //168 a8
    int lasttime     //176  b0 上次调用ScoreState::update的时间(毫秒)
    bool unk180          //180 b4
    int unk184           //184 b8
    int unk188           //188 bc
    CharacterAbility* ca //192 C0 占8字节
    bool unk200          //200 c8
} ScoreState;
//铺面等级:*(int *)(*(__ *)(TheLogicChart_ptr + 24) + 136LL)

GameResult结构体

struct GameResult {
    pointer vtable;    // 0 0 指向vtable的指针 占8字节
    //skip
    int unk12//12 xx
    int score;//16 10
    int shiny_pure//20
    int pure//24
    int far//28
    int unk32//32
    int unk36//36
    bool unk56//56 xx
    int unk80//80 xx
} GameResult;

去除Tempestissimo的硬编码

已过时,删除

Frida相关

gdb相关

貌似没啥x