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编辑
,原因: