超级音乐编辑器_2.7.9.apk 逆向思路

超级音乐编辑器_2.7.9.apk 逆向思路

image-20240527235251737

可以看到功能还是蛮多的

随便测试一个会员功能:

image-20240527235657232

点击保存后跳转到购买会员的界面:

image-20240527235740328

到这里就要想一想了,为什么点击保存按钮不是执行保存相关的代码,而是执行跳转到升级Pro界面的代码

应该是当点击了保存按钮之后,触发一个判断是不是会员、或是能否使用该功能的方法。如果是会员,去执行保存相关的代码;如果不是会员,则执行跳转到升级Pro界面的代码

我们接下来要做的就是定位到这个方法,修改成不管如何都判断我们是会员

  1. 将 apk 拖进 jadx 反编译(MT管理器的 smali 转 Java 功能就是用的 jadx )
  2. 启动 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 代码,再去点击 保存 按钮:

image-20240528002113560

当跳转到 升级Pro 界面时:

http://cdn.wutongliran.top/img/image-20240528002230600.png

可以看到 startActivity 方法被调用并打印出堆栈信息,堆栈里的方法是自下而上调用的,我们可以看到

1
at com.tianxingjian.supersound.TTSActivity.onClick(TTSActivity.java:10)

TTSActivity 类下的 onClick 方法调用了 startActivity 方法,为什么要看 onClick 方法?因为 onClick 方法是按钮被点击后执行的方法

既然定位到点击 保存 按钮后执行的 onClick 方法,我们在 jadx 里静态分析一下:

http://cdn.wutongliran.top/img/image-20240528002736904.png

搜索类名,点进去,再搜索方法名:

http://cdn.wutongliran.top/img/image-20240528002855945.png

找到这个 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);
    };
})

运行后点击 保存 按钮测试:

http://cdn.wutongliran.top/img/image-20240528003415596.png

可以看到确实是调用了这个 onClick 方法,并且能看到传进来的按钮 id 值为 tv_sure

我们来看下这段代码:

http://cdn.wutongliran.top/img/image-20240528004125269.png

到这可以判断出 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;
    };
})

运行后点击 保存 按钮:

http://cdn.wutongliran.top/img/image-20240528004740568.png

当我们不是会员的时候返回 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;
    };
})

运行后点击 保存 按钮:

http://cdn.wutongliran.top/img/image-20240528005233573.png

这样就破解成功了,接下来是修改 apk

http://cdn.wutongliran.top/img/image-20240528005425145.png

http://cdn.wutongliran.top/img/image-20240528005438211.png

http://cdn.wutongliran.top/img/image-20240528005500839.png

http://cdn.wutongliran.top/img/image-20240528005518788.png

http://cdn.wutongliran.top/img/image-20240528005543843.png

http://cdn.wutongliran.top/img/image-20240528005557794.png

搜索类:com.tianxingjian.supersound.App

http://cdn.wutongliran.top/img/image-20240528005841296.png

点这里:

http://cdn.wutongliran.top/img/image-20240528005916819.png

点击 w 方法:

http://cdn.wutongliran.top/img/image-20240528005958606.png

在这里添上 const v0,0x1,然后一路退出保存

http://cdn.wutongliran.top/img/image-20240528010116880.png

结束