可以看到功能还是蛮多的
随便测试一个会员功能:
点击保存后跳转到购买会员的界面:
到这里就要想一想了,为什么点击保存按钮不是执行保存
相关的代码,而是执行跳转到升级Pro
界面的代码
应该是当点击了保存
按钮之后,触发一个判断是不是会员
、或是能否使用该功能
的方法。如果是会员,去执行保存相关的代码;如果不是会员,则执行跳转到升级Pro界面的代码
我们接下来要做的就是定位到这个方法,修改成不管如何都判断我们是会员
- 将 apk 拖进 jadx 反编译(MT管理器的 smali 转 Java 功能就是用的 jadx )
- 启动 frida hook 环境(我这里编写 frida hook 代码用的是 js)
我们刚才看到,点击了 保存 按钮就从当前界面跳转到 升级Pro 的界面。在 java 原生开发中,跳转页面(activity)时执行的代码类似这种:
1
2
3
4
|
// 创建Intent以启动SecondActivity
Intent intent = new Intent(MainActivity.this, SecondActivity.class);
// 跳转界面
startActivity(intent);
|
当执行 startActivity 方法时页面就跳转过去,所以我们从当前页面跳转到 升级Pro 的界面应该也执行了 startActivity 方法,可以编写 hook 脚本来钩住 startActivity 方法,当触发 startActivity 方法打印堆栈,看看 startActivity 方法的调用链
当前 hook 代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
Java.perform(function () {
//调用堆栈的方法
function showStacks() {
console.log(
Java.use("android.util.Log")
.getStackTraceString(
Java.use("java.lang.Throwable").$new()
)
);
}
// 获取Activity类
var Activity = Java.use("android.app.Activity");
// Hook startActivity方法
Activity.startActivity.overload('android.content.Intent').implementation = function (intent) {
console.log("startActivity 方法被调用");
// 打印堆栈
showStacks();
// 调用原始的startActivity方法
this.startActivity(intent);
};
})
|
运行 hook 代码,再去点击 保存 按钮:
当跳转到 升级Pro 界面时:
可以看到 startActivity 方法被调用并打印出堆栈信息,堆栈里的方法是自下而上调用的,我们可以看到
1
|
at com.tianxingjian.supersound.TTSActivity.onClick(TTSActivity.java:10)
|
TTSActivity 类下的 onClick 方法调用了 startActivity 方法,为什么要看 onClick 方法?因为 onClick 方法是按钮被点击后执行的方法
既然定位到点击 保存 按钮后执行的 onClick 方法,我们在 jadx 里静态分析一下:
搜索类名,点进去,再搜索方法名:
找到这个 onClick 方法,TTSActivity 类下有很多方法里还有 onClick 方法,我们要找的是不在任何方法里的 onClick 方法
接下来 hook 这个方法,看看传进来的参数是什么,顺便验证一下当点击 保存 按钮时调用的是不是这个 onClick 方法
当前 hook 代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
Java.perform(function () {
/*//调用堆栈的方法
function showStacks() {
console.log(
Java.use("android.util.Log")
.getStackTraceString(
Java.use("java.lang.Throwable").$new()
)
);
}
// 获取Activity类
var Activity = Java.use("android.app.Activity");
// Hook startActivity方法
Activity.startActivity.overload('android.content.Intent').implementation = function (intent) {
console.log("startActivity 方法被调用");
// 打印堆栈
showStacks();
// 调用原始的startActivity方法
this.startActivity(intent);
};*/
let TTSActivity = Java.use("com.tianxingjian.supersound.TTSActivity");
TTSActivity["onClick"].implementation = function (view) {
console.log(`TTSActivity.onClick 方法被调用: view=${view}`);
this["onClick"](view);
};
})
|
运行后点击 保存 按钮测试:
可以看到确实是调用了这个 onClick 方法,并且能看到传进来的按钮 id 值为 tv_sure
我们来看下这段代码:
到这可以判断出 w 方法是判断是不是会员
、或是能否使用该功能
的方法,但我们不知道 if (App.f30946l.w())
里面的代码:
1
2
3
4
5
6
7
8
9
10
11
12
|
t1();
TextToSpeech textToSpeech = this.f31306n;
if (textToSpeech != null) {
textToSpeech.stop();
}
if (this.f31307o != null && new File(this.f31307o).exists()) {
o1();
return;
}
this.f31314v = true;
S0();
return;
|
是执行 保存 功能的,还是 if 外面的 ProfessionalActivity.I0(this, "tts");
这时候就可以 hook 一下 w 方法,看我们不是会员的时候返回的值是 true 还是 false
当前 hook 代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
Java.perform(function () {
/*//调用堆栈的方法
function showStacks() {
console.log(
Java.use("android.util.Log")
.getStackTraceString(
Java.use("java.lang.Throwable").$new()
)
);
}
// 获取Activity类
var Activity = Java.use("android.app.Activity");
// Hook startActivity方法
Activity.startActivity.overload('android.content.Intent').implementation = function (intent) {
console.log("startActivity 方法被调用");
// 打印堆栈
showStacks();
// 调用原始的startActivity方法
this.startActivity(intent);
};
let TTSActivity = Java.use("com.tianxingjian.supersound.TTSActivity");
TTSActivity["onClick"].implementation = function (view) {
console.log(`TTSActivity.onClick 方法被调用: view=${view}`);
this["onClick"](view);
};*/
let App = Java.use("com.tianxingjian.supersound.App");
App["w"].implementation = function () {
console.log(`App.w 方法被调用`);
let result = this["w"]();
console.log(`App.w 方法的返回值=${result}`);
return result;
};
})
|
运行后点击 保存 按钮:
当我们不是会员的时候返回 false ,执行 ProfessionalActivity.I0(this, “tts”);
这句代码最终会使界面跳转到充值界面,我们不想跳过去很简单,把 w 方法的返回值强行改成 ture,这样就一定会判定我们是会员
最终的 hook 代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
Java.perform(function () {
/*//调用堆栈的方法
function showStacks() {
console.log(
Java.use("android.util.Log")
.getStackTraceString(
Java.use("java.lang.Throwable").$new()
)
);
}
// 获取Activity类
var Activity = Java.use("android.app.Activity");
// Hook startActivity方法
Activity.startActivity.overload('android.content.Intent').implementation = function (intent) {
console.log("startActivity 方法被调用");
// 打印堆栈
showStacks();
// 调用原始的startActivity方法
this.startActivity(intent);
};
let TTSActivity = Java.use("com.tianxingjian.supersound.TTSActivity");
TTSActivity["onClick"].implementation = function (view) {
console.log(`TTSActivity.onClick 方法被调用: view=${view}`);
this["onClick"](view);
};*/
let App = Java.use("com.tianxingjian.supersound.App");
App["w"].implementation = function () {
console.log(`App.w 方法被调用`);
let result = this["w"]();
console.log(`原本 App.w 方法的返回值=${result}`);
result = true;//强行让结果是 true
console.log(`将 App.w 方法的返回值强行改成=${result}`);
return result;
};
})
|
运行后点击 保存 按钮:
这样就破解成功了,接下来是修改 apk
搜索类:com.tianxingjian.supersound.App
点这里:
点击 w 方法:
在这里添上 const v0,0x1,然后一路退出保存
结束