首页
社区
课程
招聘
[原创]记一次某四字跳绳软件的逆向过程
发表于: 2025-1-2 01:37 2465

[原创]记一次某四字跳绳软件的逆向过程

2025-1-2 01:37
2465
1
2
写在前面:要放假了,但是天杀的学校要求用某跳绳软件进行每天打卡 :(  因此决定逆向试试,看能不能以逸待劳qwq
环境:mumu模拟器,frida,window,jadx

解压该软件,拖到jadx里面,发现包很多,并且实在晦涩难懂。根据惯例,去com下,可以看到com的一系列包,不过一时也看不出什么有用信息
图片描述
先看看软件布局,以便找到合适的切入点
图片描述

图片描述
可以看到在跳绳选项里,有四种可供选择的模式,根据需求来看,我们主要是关心这个自由模式。现在要找程序的主体部分,在cmd里输入命令:
frida-ps -Ua
可以看到程序的包名 com.gkid.crazyrope 图片描述
我们进入这个包,可以发现一些特征性的东西
图片描述
在这个包里翻一翻,可以发现这里设置了上图中关于四种跳绳模式的内容。
很显然下一步我们要去找到“开始”按钮的触发事件,因为我们需要修改的是结束后的成绩。可惜的是,本人逆向功底比较差,这里卡了很久,都没能找到对应的触发事件0_o 因此决定换一种方法
手动进行了几次自由跳绳后发现,在获取摄像机权限进行录像时,界面是起了很多服务的。而在结算后,会有一个暂时性的成绩页面以及进入报告的选项,我们的重点只是修改成绩,如果我们能这个过程中被调用的函数,自由就能知道成绩和报告是怎么生成,因此想到使用frida进行插桩。
启动frida服务:

1
2
3
4
5
6
7
8
9
10
cmd1:
adb connect 127.0.0.1:7555(mumu模拟器默认端口)
adb shell
su
cd /data/local/tmp
chmod 777 frida-server
./frida-server
cmd2:
adb forward tcp:27042 tcp:27042
adb forward tcp:27043 tcp:27043

一般的按键触发都是通过View.setOnClickListener,因此我们之间hook这个函数,并且去打印它的调用堆栈

1
2
3
4
5
6
7
8
9
10
11
12
Java.perform(function () {
    // Hook View.setOnClickListener()
    let View = Java.use("android.view.View");
    View.setOnClickListener.implementation = function (listener) {
        // 打印调用堆栈
        console.log("[*] setOnClickListener called");
        console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
 
        // 调用原始方法
        return this.setOnClickListener(listener);
    };
});

注入脚本:frida -U -n 软件名 -l 脚本
点击开始按钮,会看到一系列的函数堆栈,我们直接标记最后一个
图片描述
随便找个跳绳视频,等他跳一会儿。
点击退出按钮,可以看到一系列函数调用。
图片描述
经过前面分析我们知道,这里面的函数肯定是包含了如何生成报告打印成绩页面的,由于打印的函数链不多,我们可以人工分析一下,可以得到以下的关键函数:

1
2
3
4
5
com.gkid.gsport.widget.BaseDrillEndView.setCloseClickListener(BaseDrillEndView.java:5)
 
com.gkid.gsport.ui.base.v0.showDrillEndView(DrillCtrlActivity.java:242)
 
com.gkid.gsport.widget.BaseDrillEndView.fillViewData(BaseDrillEndView.java:455)

进到DrillEndView这个类中的fillViewData函数
图片描述
发现设置了成绩页面中的 经验值、能量豆、卡砖
进到BaseDrillEndView中的fillViewData函数
图片描述
发现设置了 卡路里、评分,那么剩余的分数和时间在哪里设置的呢
注意到图中可疑函数setText(getLeftValue(record))setText(getRightValue(record)),不妨看看他们的实现
图片描述
图片描述
分析一下第一个,不难发现,record对象的metric_type字段记录了度量值类型,最后一句代码getScore恰好和我们要的比较符合。而第二部分的getTake_time也是同样。为了验证猜想,我们可以hook一下这两个函数,修改返回值,看是不是和我们预期一样

1
2
3
4
5
6
7
8
9
10
11
12
Java.perform(function () {
    let BaseDrillEndView = Java.use("com.gkid.gsport.widget.BaseDrillEndView");
    BaseDrillEndView.getLeftValue.implementation = function (record) {
        console.log(`BaseDrillEndView.getLeftValue is called`);
        return "5000";
    };
 
    BaseDrillEndView.getRightValue.implementation = function (record) {
        console.log(`BaseDrillEndView.getRightValue is called`);
        return "10:00"; // 确保返回值类型与原始方法一致
    };
});

注入后,运行一下
图片描述
可以看到,结果的确是我们预期那样。我们进入getscore,这个函数是record类的公有函数,返回了一个私有的成员变量score,第一反应自然是修改它的get函数的返回值,这样任何获取score的地方都只能得到我们预设的值。
但是 :( 十分遗憾的是,hook了getScore后,我们并没有得到预期那样的报告
图片描述
可以看到成绩还是正常值79。不难猜想,生成报告时所用到的并不是这个字段。但是根据上面的分析我们可以知道,record类存储了一次运动的各种数据,我们可以打印出来它的所有字段进行观察分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Java.perform(function () {
    let BaseDrillEndView = Java.use("com.gkid.gsport.widget.BaseDrillEndView");
    BaseDrillEndView.getLeftValue.implementation = function (record) {
     
    if (record) {
            var recordClass = record.getClass(); // 获取 record 的类
            var fields = recordClass.getDeclaredFields(); // 获取所有字段
 
            for (var i = 0; i < fields.length; i++) {
                fields[i].setAccessible(true); // 设置字段可访问
                var fieldName = fields[i].getName(); // 获取字段名
                var fieldValue = fields[i].get(record); // 获取字段值
                console.log("record." + fieldName + ": " + fieldValue);
            }
        }
    };
});

图片描述
发现了score值相同的两个字段succ_count、metric_amount,我们并不需要知道哪一个才是正确的,直接把两个都修改掉就好,和上文修改score一样,这一次我们发现,报告正确显示了我们设置的值,但是提示数据异常
这也正常(bushi 谁家大学生一分钟一万多个?
很显然我们的速度太快了。那么速度是怎么来的,经验告诉我们,s/t = v
因此当务之急是修改过小的时间,回到前文关于显示时间的部分,有一个take_timed的字段——这个字段刚好等于end_time - begin_time,以及对应的get函数。我们尝试像修改score一样修改这个值,不出意外的失败了
图片描述
这里又尝试修改它的set函数,发现set函数没被调用。那么就回到代码中找找take_time在哪里被修改了
图片描述
在record中发现了一处对take_time和end_time的修改,hook掉这个函数,在它原本的值后面加上3000000(因为是微秒),ok,这一次发现时间被修改过来了,并且没有数据异常的提示
图片描述

完工,因此我们知道了,只需要修改这几个对应字段就能使生成的报告成为我们的形状:),甚至我们可以修改end_time来修改报告的生成时间,使其显示未来的时间点

最终exp如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Java.perform(function() {
    let Record = Java.use("com.gkid.gsport.model.record.Record");
    Record["getSucc_count"].implementation = function () {
        return 5000;
    };
    Record["getMetric_amount"].implementation = function () {
        return 5000;
    };
    Record.setEnd_time.implementation = function (j) {
        var result = this.setEnd_time(j);
    //this.end_time.value = this.end_time.value + 3000000;
        this.take_time.value = this.take_time.value + 3000000;
    return result;
    };
});

总结:本人re新手一只,第一次做这种实践,比较生疏。不过熬夜几天完整完成后,成就感很不错,而且确实觉得获得了很多逆向程序以及使用frida的经验qwq


[招生]科锐逆向工程师培训(2025年3月11日实地,远程教学同时开班, 第52期)!

最后于 2025-1-3 13:08 被t0rch编辑 ,原因:
收藏
免费 6
支持
分享
最新回复 (2)
雪    币: 206
活跃值: (1305)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
实践出真知,不错,学习了
2025-1-5 18:08
0
雪    币: 21
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
3
frida 反调试咋破
2025-2-3 08:32
0
游客
登录 | 注册 方可回帖
返回