木鱼_1.2.18.apk 逆向思路

木鱼_1.2.18.apk 逆向思路

所用工具:

  1. 静态分析:jadx(将apk反编译成java代码,MT管理器的smali转java功能正是用jadx)
  2. 动态分析:frida(hook框架)

随便点击一个皮肤,皮肤图标右上角有个“LOCK”,意思是被锁住了(因为我们不是会员) :

image-20240530192006348

点击之后会跳转到登陆界面:

image-20240530192229193

在Android开发中,页面切换和跳转通常会使用Intent,但并不是所有的页面切换和跳转都必须使用Intent

以下是一个Activity之间的跳转示例(并不唯一):

1
2
Intent intent = new Intent(CurrentActivity.this, TargetActivity.class);
startActivity(intent);

可以尝试hook Intent或者startActivity(我这里hook Intent方法,因为hook startActivity方法没效果不知道什么情况,我太菜了),在Intent方法被调用时打印当前堆栈信息来定位上层方法

编写hook Intent所有重载代码,这里用的是js代码来编写:

 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
Java.perform(function () {
    //打印堆栈信息的方法
    function showStacks() {
        console.log(
            Java.use("android.util.Log")
                .getStackTraceString(
                    Java.use("java.lang.Throwable").$new()
                )
        );
    }

    var Intent = Java.use("android.content.Intent");

    Intent.$init.overload('android.content.Context', 'java.lang.Class').implementation = function(context, cls) {
        console.log("Intent(context, cls) called with context: " + context + " and class: " + cls);
        showStacks();
        return this.$init(context, cls);
    };

    Intent.$init.overload('java.lang.String').implementation = function(action) {
        console.log("Intent(action) called with action: " + action);
        showStacks();
        return this.$init(action);
    };

    Intent.$init.overload('java.lang.String', 'android.net.Uri').implementation = function(action, uri) {
        console.log("Intent(action, uri) called with action: " + action + " and uri: " + uri);
        showStacks();
        return this.$init(action, uri);
    };
    console.log("Hook 成功!");
});

运行后点击一个皮肤:

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

打印出堆栈,由下而上调用链

搜索com.ahzy.fish.vm.SharedViewModel.goToPayOrLogIn方法

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

MuYuSpUtil.INSTANCE.isLogin()判断是否登录,已登录返回true、未登录返回false

hook 该方法,将返回值改成固定为true可绕过登录:

1
2
3
4
5
let MuYuSpUtil = Java.use("com.ahzy.fish.utils.MuYuSpUtil");
    MuYuSpUtil["isLogin"].implementation = function () {
        console.log(`MuYuSpUtil.isLogin is called`);
        return true;
    };

运行后点击一个皮肤:

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

这次没有跳转到登录界面,而是跳转到购买会员界面,来看下堆栈信息:

goToPayOrLogIn方法顾名思义,应该是“去购买界面或登录界面”,多半是因为判断后发现我们不是会员,该功能被锁住而调用这个方法

搜索类名:com.ahzy.fish.act.SettingAct$bindSkin$1$2,找到invoke方法:

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

muYuBgBean.isLock()判断该皮肤是否被锁住,锁住返回true,没锁住返回false

hook 该方法,将返回值改成固定为false可绕过皮肤限制:

1
2
3
4
5
let MuYuBgBean = Java.use("com.ahzy.fish.bean.MuYuBgBean");
    MuYuBgBean["isLock"].implementation = function () {
        console.log(`MuYuBgBean.isLock is called`);
        return false;
    };

运行后重新点进这个设置界面,皮肤的“LOCK”已经不见了:

image-20240530204226501

在这个界面定位到上一个invoke方法:

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

muYuBean.isLock()判断该音色是否被锁住,锁住返回true,没锁住返回false

hook 该方法,将返回值改成固定为false可绕过音色限制:

1
2
3
4
5
let MuYuBean = Java.use("com.ahzy.fish.bean.MuYuBean");
MuYuBean["isLock"].implementation = function () {
    console.log(`MuYuBean.isLock is called`);
    return false;
};

运行后重进设置界面:

image-20240530205849006

音色已解锁

刚刚看到两个功能限制都是通过不同类下的isLock()方法来判断,所以大胆猜测其他的功能限制也是用对应类下的isLock()方法来判断(当然不放心可以像前面那样一步步定位到其他功能限制的方法,我已经验证过其他的功能限制方法也是lsLock)

直接搜索方法:isLock()

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

前两已经hook绕过了,把剩下的也全部hook上,将返回值设置为false:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
let BeadThemeBean = Java.use("com.ahzy.fish.dto.BeadThemeBean");
BeadThemeBean["isLock"].implementation = function () {
    console.log(`BeadThemeBean.isLock is called`);
    return false;
};

let DocxDTO = Java.use("com.ahzy.fish.dto.DocxDTO");
DocxDTO["isLock"].implementation = function () {
    console.log(`DocxDTO.isLock is called`);
    return false;
};

let MusicDTO = Java.use("com.ahzy.fish.dto.MusicDTO");
MusicDTO["isLock"].implementation = function () {
    console.log(`MusicDTO.isLock is called`);
    return false;
};

运行hook代码:

解锁佛珠样式限制:

image-20240530210733542 image-20240530210810105

解锁佛经禅音限制:

image-20240530211211415

DocxDTO.isLock不知道是关于哪个功能的,这里的般若文海就算没修改也没有看到限制

还有一个关键函数:isPay 忘说了,使用“在桌面敲木鱼”功能时会调用,赋值true

不用加强版也行:

image-20240531005852679

搜索方法:com.ahzy.fish.utils.MuYuSpUtil.isLogin

image-20240530231614482 image-20240530231641450

如图在箭头指向的位置添加代码 const v0,0x1

image-20240530231724905

搜索方法:com.ahzy.fish.bean.MuYuBean.isLock

image-20240530231103309 image-20240530231130874

如图在箭头指向的位置添加代码 const v0,0x0

image-20240530231217761

以此类推修改以下isLock方法:

  • com.ahzy.fish.bean.MuYuBgBean.isLock
  • com.ahzy.fish.dto.BeadThemeBean.isLock
  • com.ahzy.fish.dto.DocxDTO.isLock
  • com.ahzy.fish.dto.MusicDTO.isLock

搜索方法:isPay

bcff878eae514ec34e8d4249536fbaf3_720

如图位置将0改成1

1c6ce94572382e699f6596739a7cb5e8

(关于广告植入这方面我还未了解,只好去网上找现成的方法,也不知道怎么分析出来的)

用LibChecker查看下广告

image-20240530232453806

快手广告和腾讯广告

搜索方法名:com.qq.e.comm.adevent.ADEvent.getType

a21cac779221fd21eb6133bc07969ef4_720

如图在箭头指向的位置添加代码 const/16 v0,0x65

669b836e0d5f159122728169882c600f

搜索方法名:com.qq.e.comm.managers.b.d

如图添加代码 const v0,0x0

bc9c1f2a42815feea12a117f981005bb

搜索方法名:isResultOk

搜到三个结果如图修改:

9b87dbc0234a2b7be0ff90f1870dd524_720 d0e19d35297864d9275abcbf00bbe9b1_720 e8a413bf7015cd3bd059b5fb2eb425c1_720

点击常量,点击替换:

0797f3edb2f888cd594a0486c81b4b63_720

将qq.e替换为#:

938960f122a15d12cdbf16eec1cc3f58_720

点击应用修改:

2587119e864518f898f9b5625202ce4e_720

再替换:com.kwad.

9090693d171c0efae8f8ad47269254d0_720

记得应用修改

来到assets目录下:

f30c275c91ade634f3e668910ed1b0b1_720

删除gdt_plugin目录:

63888a99f26ff2f5004fa0ab6f36f617_720

反编译AndroidManifest.xml:

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

搜索:

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

把包含qq.e的activity全删了:

image-20240530234439654

结束