克拉恋人会员制取证分析

  • Post author:
  • Post category:其他


篇幅有限

完整内容及源码关注公众号:ReverseCode,发送


绕过强制会员

adb install com.caratlover.apk 安装后强制支付会员费才可进主页

image-20210531003110432

脱壳

jadx打开发现代码很少,目测被加固,脱个衣服先。

git clone https://github.com/hluwa/FRIDA-DEXDump.git
./fs1426arm64
pyenv local 3.9.0
python main.py   app保持最前端,开始脱壳

git clone https://github.com/hanbinglengyue/FART.git
adb push frida_fart/lib/fart* /data/local/tmp
adb shell && cp fart* /data/app && chmod 777
frida -U -f com.caratlover -l frida_fart_hook.js --no-pause  使用安卓8和安卓8.1进行脱壳
mv ../*.dex carat &&  adb pull /sdcard/carat

image-20210531002927720


file *

查看文件格式是Dalvik dex file,但是脱完的部分dex文件用010 Editor打开时,报错,说明文件并不标准。

objection -g com.caratlover explore
android hooking list activities
android intent launch_activity com.chanson.business.MainActivity  直接绕过强制会员购买页面

image-20210531093247062

使用jadx1.2.0中同时打开多个dex,查找

com.chanson.business.MainActivity

用12.8.0的frida混淆的爹妈都不认识了,还是用14.2.16版本。

绕过强制会员页面后,编辑资料填写个人详细信息。

image-20210531093539184

搭讪

通过点击发送时,调用hookEvent.js查看触发的类

frida -UF -l hookEvent.js

[Pixel::克拉恋人]-> [WatchEvent] onClick: com.tencent.qcloud.tim.uikit.modules.chat.layout.input.InputLayout

image-20210531093736893

查看InputLayout该类的用例,该UI基本都在

com.chanson.business.message.activity.ChatActivity

中调用

image-20210531230429589

其中

com.chanson.business.message.activity.ChatActivity

有一段代码,判断是否vip

private final void ja() {
    BasicUserInfoBean col1;
    BasicUserInfoBean col12;
    if (Ib.f9521i.m()) {
        MyInfoBean k = Ib.f9521i.k();
        if (k == null || (col12 = k.getCol1()) == null || !col12.isVip()) {
            CheckTalkBean checkTalkBean = this.f10545d;
            if ((checkTalkBean != null ? checkTalkBean.getUnlockTime() : 0) > 0) {
                da();
            } else {
                l(0);
            }
        } else {
            da();
        }
    } else {
        MyInfoBean k2 = Ib.f9521i.k();
        if (k2 == null || (col1 = k2.getCol1()) == null || !col1.isReal()) {
            ConfirmDialogFragment.a aVar = ConfirmDialogFragment.Companion;
            String string = getString(R$string.you_can_chat_after_you_have_certified);
            i.a((Object) string, "getString(R.string.you_c…after_you_have_certified)");
            String string2 = getString(R$string.authentication_now_in_ten_seconds);
            i.a((Object) string2, "getString(R.string.authe…ation_now_in_ten_seconds)");
            FragmentManager supportFragmentManager = getSupportFragmentManager();
            i.a((Object) supportFragmentManager, "supportFragmentManager");
            ConfirmDialogFragment.a.a(aVar, "", string, "", string2, true, supportFragmentManager, true, (kotlin.jvm.a.a) null, false, (kotlin.jvm.a.b) null, (String) null, 0.0f, (kotlin.jvm.a.b) null, 8064, (Object) null).a(new I(this));
            return;
        }
        da();
    }
}

其中的isVip方法来自于

com.chanson.business.model.BasicUserInfoBean

,我们尝试trace下该类,并打印类的每个域的值。

trace

frida -UF -l trace.js -o traceVip.txt 对指定类的所有动静态方法及构造函数进行trace

function inspectObject(obj) {
    Java.perform(function () {

        const obj_class = obj.class;


        // var objClass = Java.use("java.lang.Object").getClass.apply(object);
        // obj_class =Java.use("java.lang.Class").getName.apply(objClass);


        const fields = obj_class.getDeclaredFields();
        const methods = obj_class.getMethods();
        // console.log("Inspecting " + obj.getClass().toString());
        // console.log("Inspecting " + obj.class.toString());
        console.log("\tFields:");
        for (var i in fields) {
            console.log("\t\t" + fields[i].toString());
            var className = obj_class.toString().trim().split(" ")[1];
            // console.log("className is => ",className);
            var fieldName = fields[i].toString().split(className.concat(".")).pop();
            console.log(fieldName + " => ", obj[fieldName].value);
        }
        // console.log("\tMethods:");
        // for (var i in methods)
        //     console.log("\t\t" + methods[i].toString());
    })
}
function uniqBy(array, key)
{
        var seen = {};
        return array.filter(function(item) {
                var k = key(item);
                return seen.hasOwnProperty(k) ? false : (seen[k] = true);
        });
}

// trace a specific Java Method
function traceMethod(targetClassMethod)
{
    var delim = targetClassMethod.lastIndexOf(".");
    if (delim === -1) return;

    var targetClass = targetClassMethod.slice(0, delim)
    var targetMethod = targetClassMethod.slice(delim + 1, targetClassMethod.length)

    var hook = Java.use(targetClass);
    var overloadCount = hook[targetMethod].overloads.length;

    console.log("Tracing " + targetClassMethod + " [" + overloadCount + " overload(s)]");



    for (var i = 0; i < overloadCount; i++) {

        hook[targetMethod].overloads[i].implementation = function() {
            inspectObject(this)
            console.warn("\n*** entered " + targetClassMethod);

            // print backtrace
            // Java.perform(function() {
            //    var bt = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new());
            //    console.log("\nBacktrace:\n" + bt);
            // });

            // print args
            if (arguments.length) console.log();
            for (var j = 0; j < arguments.length; j++) {
                console.log("arg[" + j + "]: " + arguments[j]);

            }

            // print retval
            var retval = this[targetMethod].apply(this, arguments); // rare crash (Frida bug?)
            console.log("\nretval: " + retval);
            console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
            console.warn("\n*** exiting " + targetClassMethod);
            return retval;
        }
    }

}

function traceClass(targetClass)
{
  //Java.use是新建一个对象哈,大家还记得么?
    var hook = Java.use(targetClass);
  //利用反射的方式,拿到当前类的所有方法
    var methods = hook.class.getDeclaredMethods();
    // var methods = hook.class.getMethods();
    console.log("methods => ",methods)
  //建完对象之后记得将对象释放掉哈
    hook.$dispose;
  //将方法名保存到数组中
    var parsedMethods = [];
    methods.forEach(function(method) {
        parsedMethods.push(method.toString().replace(targetClass + ".", "TOKEN").match(/\sTOKEN(.*)\(/)[1]);
    });
  //去掉一些重复的值
    var targets = uniqBy(parsedMethods, JSON.stringify);
    // 只hook构造函数
    //targets = [];
    targets = targets.concat("$init")
    console.log("targets=>",targets)
  //对数组中所有的方法进行hook,traceMethod也就是第一小节的内容
    targets.forEach(function(targetMethod) {
        traceMethod(targetClass + "." + targetMethod);
    });
}




function hook() {
    Java.perform(function () {
        console.log("start")
        Java.enumerateClassLoaders({
            onMatch: function (loader) {
                try {
                    if(loader.findClass("com.ceco.nougat.gravitybox.ModStatusbarColor$1")){
                    // if(loader.findClass("de.robv.android.xposed.XC_MethodHook")){
                    // if(loader.findClass("de.robv.android.xposed.XposedBridge")){
                    //if(loader.findClass("com.android.internal.statusbar.StatusBarIcon")){

                        console.log("Successfully found loader")
                        console.log(loader);
                        Java.classFactory.loader = loader ;
                    }
                }
                catch(error){
                    console.log("find error:" + error)
                }
            },
            onComplete: function () {
                console.log("end1")
            }
        })
        // Java.use("de.robv.android.xposed.XposedBridge").log.overload('java.lang.String').implementation = function (str) {
        //     console.log("entering Xposedbridge.log ",str.toString())
        //     return true
        // }
        //traceClass("com.ceco.nougat.gravitybox.ModStatusbarColor")
        // Java.use("com.roysue.xposed1.HookTest$1").afterHookedMethod.implementation = function (param){
        //     console.log("entering afterHookedMethod param is => ",param);
        //     return this.afterHookedMethod(param);
        // }
        // traceClass("de.robv.android.xposed.XC_MethodHook")
        // Java.use("de.robv.android.xposed.XC_MethodHook$MethodHookParam").setResult.implementation = function(str){
        //     console.log("entersing de.robv.android.xposed.XC_MethodHook$MethodHookParam setResult => ",str)
        //     console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
        //     return this.setResult(str);
        // }

        Java.enumerateLoadedClasses ({
            onMatch:function(className){
                if(className.toString().indexOf("gravitybox")>0 && 
                className.toString().indexOf("$")>0
                ){
                    console.log("found => ",className)
                    // var interFaces = Java.use(className).class.getInterfaces();
                    // if(interFaces.length>0){
                    //     console.log("interface is => ");
                    //     for(var i in interFaces){
                    //         console.log("\t",interFaces[i].toString())
                    //     }
                    // }
                    if(Java.use(className).class.getSuperclass()){
                        var superClass = Java.use(className).class.getSuperclass().getName();
                        // console.log("superClass is => ",superClass);
                        if (superClass.indexOf("XC_MethodHook")>0){
                            console.log("found class is => ",className.toString())
                            traceClass(className);
                        }



                    }

                }
            },onComplete:function(){
                console.log("search completed!")

            }
        })

        console.log("end2")
    })
}
function main(){
    // hook()
    Java.perform(function(){
        traceClass("com.chanson.business.model.BasicUserInfoBean")  
        // traceClass("com.chanson.business.model.MyInfoBean");
    })

}
setImmediate(main)

image-20210531232604816

java.lang.Throwable at com.chanson.business.model.BasicUserInfoBean.isVip(Native Method) at com.chanson.business.message.activity.ChatActivity.na(SourceFile:2) at com.chanson.business.message.activity.ChatActivity.k(SourceFile:1) at com.chanson.business.message.activity.a.run(SourceFile:1) at android.os.Handler.handleCallback(Handler.java:790) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loop(Looper.java:164) at android.app.ActivityThread.main(ActivityThread.java:6494) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807) at de.robv.android.xposed.XposedBridge.main(XposedBridge.java:108)


优化对应关系

frida -UF -l trace.js -o traceVip.txt

function traceMethod(targetClassMethod) {
    var delim = targetClassMethod.lastIndexOf(".");
    if (delim === -1) return;

    var targetClass = targetClassMethod.slice(0, delim)
    var targetMethod = targetClassMethod.slice(delim + 1, targetClassMethod.length)

    var hook = Java.use(targetClass);
    var overloadCount = hook[targetMethod].overloads.length;

    console.log("Tracing " + targetClassMethod + " [" + overloadCount + " overload(s)]");



    for (var i = 0; i < overloadCount; i++) {

        hook[targetMethod].overloads[i].implementation = function () {
            var output = "";
            for(var line=0;line<100;line++){
                output = output.concat("=")
            }
            output = output.concat("\r\n")
            const Class = Java.use("java.lang.Class");
            // const obj_class = Java.cast(this.getClass(), Class);
            const obj_class = this.class;
            const fields = obj_class.getDeclaredFields();

            // output = output.concat("Inspecting " + this.getClass().toString());
            output = output.concat("Inspecting " + this.class);
            output = output.concat("\r\n")
            output = output.concat("\tFields:");
            output = output.concat("\r\n")
            for (var i in fields) {
                // console.log("\t\t" + fields[i].toString());
                var className = obj_class.toString().trim().split(" ")[1];
                // console.log("className is => ",className);
                var fieldName = fields[i].toString().split(className.concat(".")).pop();
                var fieldValue = undefined;
                if(!(this[fieldName]===undefined)){
                    fieldValue = this[fieldName].value ; 
                }
                output = output.concat(fieldName + " => ", fieldValue);
                output = output.concat("\r\n")
            }
            // inspectObject(this);
            output = output.concat("\n*** entered " + targetClassMethod);
            output = output.concat("\r\n")

            // print backtrace
            // Java.perform(function() {
            //    var bt = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new());
            //    console.log("\nBacktrace:\n" + bt);
            // });

            // print args
            if (arguments.length) console.log();
            for (var j = 0; j < arguments.length; j++) {
                output = output.concat("arg[" + j + "]: " + arguments[j] + " => " + JSON.stringify(arguments[j]));
                output = output.concat("\r\n")
            }
            output = output.concat(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
            output = output.concat("\r\n");

            // print retval
            var retval = this[targetMethod].apply(this, arguments); // rare crash (Frida bug?)
            output = output.concat("\nretval: " + retval + " => " + JSON.stringify(retval));
            output = output.concat("\r\n")
            output = output.concat("\n*** exiting " + targetClassMethod);
            output = output.concat("\r\n")
            console.log(output);
            return retval;
        }
    }

}

image-20210601005958949

vip

旧版4.1.0

frida -UF -l hookCaratVip.js

function hookVIP(){
    Java.perform(function(){
        Java.use("com.chanson.business.model.BasicUserInfoBean").isVip.implementation = function(){
            console.log("Calling isVIP ")
            return true;
        }
    })

}
function main(){
    console.log("Start hook")
    hookVIP()
}
setImmediate(main)

新版4.6.0

android hooking watch class com.chanson.business.message.activity.ChatActivity --dump-args --dump-backtrace --dump-return  当我们无法判断什么时候判断vip时,hook整个类,查看调用链,点击发送消息时,弹窗付费

image-20210601171218877

查看jadx中的

com.chanson.business.message.activity.ChatActivity

类,通过aa方法得知只有在被拉黑等情况,返回false则无法发送消息,我们在第一步让Z()返回false,直接进入

return true

private final boolean aa() {
    if (!Z()) {
        return true;
    }
    if (this.f10873d == null) {
        Hb.a(Hb.f11628c, "数据异常", 0, 2, (Object) null);
        return false;
    } else if (ga()) {
        return false;
    } else {
        CheckTalkBean checkTalkBean = this.f10873d;
        if (checkTalkBean == null) {
            i.a();
            throw null;
        } else if (!checkTalkBean.getUnlock()) {
            ChatLayout chatLayout = (ChatLayout) k(R$id.chatLayout);
            i.a((Object) chatLayout, "chatLayout");
            chatLayout.getInputLayout().hideSoftInput();
            x.a(new RunnableC1179a(this), 100);
            return false;
        } else if (checkTalkBean.getStatus() == 3 || checkTalkBean.getStatus() == 2) {
            Hb.a(Hb.f11628c, "你已将对方拉黑,无法发送消息", 0, 2, (Object) null);
            ChatLayout chatLayout2 = (ChatLayout) k(R$id.chatLayout);
            i.a((Object) chatLayout2, "chatLayout");
            InputLayout inputLayout = chatLayout2.getInputLayout();
            i.a((Object) inputLayout, "chatLayout.inputLayout");
            inputLayout.getInputText().setText("");
            return false;
        } else if (checkTalkBean.getStatus() != 1) {
            return true;
        } else {
            Hb.a(Hb.f11628c, "对方已将你拉黑,无法发送消息", 0, 2, (Object) null);
            ChatLayout chatLayout3 = (ChatLayout) k(R$id.chatLayout);
            i.a((Object) chatLayout3, "chatLayout");
            InputLayout inputLayout2 = chatLayout3.getInputLayout();
            i.a((Object) inputLayout2, "chatLayout.inputLayout");
            inputLayout2.getInputText().setText("");
            return false;
        }
    }
}

通过objection判断ChatActivity源码实现

objection -g com.caratlover explore -P ~/.objection/plugins
android hooking search classes ChatActivity
plugin wallbreaker classdump --fullname com.chanson.business.message.activity.ChatActivity
android hooking watch class_method com.chanson.business.message.activity.ChatActivity.Z --dump-args --dump-backtrace --dump-return

image-20210601022535406

每次Z()返回true自然进不了发送消息逻辑,主动调用Z()返回false,破解vip

function hookVIP(){
    Java.perform(function(){
        Java.use("com.chanson.business.message.activity.ChatActivity").Z.implementation = function(){
            console.log("Calling isVIP ")
            return false;
        }
    })

}
function main(){
    console.log("Start hook")
    hookVIP()
}
setImmediate(main)

image-20210601020458519

抓包

Postern配置代理,其中192.168.0.107是charles主机ip,8889是charles的socks

image-20210601021359032

配置规则

image-20210601021407826

遇到8668端口抓不到,报错

SSL:Unsupported or unrecognized SSL message

,修改charles的Proxy Settings

image-20210601021805962

盲猜一波是base64加密

image-20210601021901648

python r0capture.py -U -f com.caratlover -v -w 2 >> capture.txt  抓包发现都被加密,类被混淆的非常厉害,虽然无法识别类的作用,我们可以有通过trace去跟踪调用返回值

找到登录包

/auth/login-check

,其调用栈中

at com.chanson.common.a.j.intercept(SourceFile:45)

image-20210602144812815

通过jadx查看

com.chanson.common.a.j

方法,其中

com.chanson.common.utils.a.b

将传入的jsonObject转成string后调用c方法。

image-20210602151006668

frida -U -f com.caratlover -l trace.js --no-pause -o traffic.txt  修改trace的class
traceClass("com.chanson.common.utils.a.b")

Error: java.lang.ClassNotFoundException: Didn’t find class “com.chanson.common.utils.a.b” 报错是因为app启动还要时间,修改

setTimeout(main, 2000);

trace登录,先打开登录界面,输入密码后

frida -U com.caratlover -l r0tracer.js --no-pause -o traffic.txt

image-20210602154917590

大量的加密字段类似base64,尝试trace Base64。修改

traceClass("android.util.Base64")

,开启trace,

frida -U com.caratlover -l r0tracer.js --no-pause -o base64.txt

追查调用栈

image-20210602160016918

通过jadx查看

com.chanson.common.a.d

,其中

String a2 = a.a(string, "f87210e0ed3079d8");

的a方法跳转到实现发现是一个完整的标准aes加密。

image-20210602160255397

全局搜索还有AESUtils,完全自己开发的非标准的AES加密,

7z x com.caratlover.apk

查看lib/armeabi-v7a下存在

alicomphonenumberauthsdk-log-online-standard-release_alijtca_plus.so

image-20210602160511071

strings查看该so中的字符串,

traceClass("com.mobile.auth.gatewayauth.utils.security.CheckRoot")

image-20210602161050938

对抗更新

adb connect 172.20.103.172  启动wifiadb
adb install com.caratlover4.1.0.apk
frida -UF -l hookEvent.js  点击马上更新按钮,触发点击时间,打印点击类

image-20210603160106003

打开jadx逐个查看脱完壳后的dex文件,新版本的jadx对加密后的dex反编译结果会rename

image-20210603162543390

查看ConfirmDialogFragment类,其中有

public /* synthetic */ void onDestroyView() {
    super.onDestroyView();
    g();
}

主动调用去除弹窗

frida -UF -l disableUPDATE.js 再destory

function disableUPDATE(){
    Java.perform(function(){
        Java.choose("com.chanson.business.widget.ConfirmDialogFragment",{
            onMatch:function(ins){
                // 动态方法choose onMatch找到实例进行调用
                console.log("found ins => ",ins);
                // smali或objection看真实方法名
                ins.onDestroyView()
            },
            onComplete:function(){
                console.log("Search completed!")
            }
        })
    })
}
function main(){
    console.log("Start hook")
    disableUPDATE()
}
setImmediate(main)

GIF 2021-6-3 16-37-29

不过页面无法操作,尝试直接跳到MainActivity

objection -g com.caratlover explore -P ~/.objection/plugins
android intent launch_activity com.chanson.business.MainActivity

trace

frida -U -f com.caratlover -l r0trace.js –runtime=v8 –no-pause -o trace.txt 在traceClass中添加

targets = [];

只hook构造函数,点击马上更新

image-20210604213755118

traceClass("com.chanson.business.widget.ConfirmDialogFragment")
setTimeout(main, 1000);

setImmediate是立即执行函数,setTimeout是等待毫秒后延迟执行函数 二者在attach模式下没有区别 在spawn模式下,hook系统API时如javax.crypto.Cipher建议使用setImmediate立即执行,不需要延时 在spawn模式下,hook应用自己的函数或含壳时,建议使用setImmediate并给出适当的延时(500~5000)

image-20210607212455683

找到

com.chanson.business.login.presenter.PhoneLoginPresenter$a.a

实现方法

image-20210607212852510

找到a方法的调用处,在switch的

baseResponse.getErrorCode()

的判断时调用

PhoneLoginPresenter.f10498a.a

,其中

renamed from: com.chanson.business.g.s

正是我们trace得到的类

image-20210607213046818

traceClass("com.chanson.common.base.BaseResponse") 
setTimeout(main, 1000);

尝试trace

com.chanson.common.base.BaseResponse

查看getErrorCode的结果,返回10002,正巧会调用

PhoneLoginPresenter.f10498a.a((Update) rVar.a(rVar.a(baseResponse.getUpdate()), Update.class));

image-20210607213548788

使用新版本的apk启动时重新trace

com.chanson.common.base.BaseResponse

查看正常情况下case返回的值为10001。

Java.use("com.chanson.common.base.BaseResponse").getErrorCode.implementation = function(){
    console.log("Calling getErrorCode ")
    return 10001;
}
setTimeout(main,2000)  // 壳的切换需要时间


frida -U -f com.caratlover -l disableUPDATE.js --no-pause

hook getErrorCode直接返回10001,发现正常进入登录,登录时发现

我们检测到你的账号存在异常数据,为确保你的账号安全,请重新登录

,r0capture抓包发现对版本号进行了校验,接下来将SSLOutputStream的入参改成新版本

Java.use("com.android.org.conscrypt.ConscryptFileDescriptorSocket$SSLOutputStream").write.overload('[B', 'int', 'int').implementation = function (bytearry, int1, int2) {
    for(var i = 0; i < bytearry.length; ++i){
        // Memory.writeS8(ptr.add(i), array[i]);
        if(bytearry[i]=='0x34'){
            console.log("found 4");
            if(bytearry.length - i > 4){
                if(bytearry[i+1] == '0x2e' && bytearry[i+2] == '0x31' &&  bytearry[i+3] == '0x2e' &&  bytearry[i+4] == '0x30' ){
                    bytearry[i+2] = 50
                    console.log("finally change to 4.2.0!")
                }
            }
            // 4.1.0 字符串转16进制转 0x34 0x2e 0x31 0x2e 0x30
        }
    }
    var result = this.write(bytearry, int1, int2);
    jhexdump(bytearry)

    // var trafficstring = StringClass.$new(bytearry).replace(StringClass.$new("4.1.0"),StringClass.$new("4.2.0"))
    // console.log("write => ",trafficstring)
    // Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()).toString();
    // var result = this.write(trafficstring.getBytes(), int1, int2);
    return result;
}

批量撩妹

jadx-gui查看新版本依旧加壳

./fs14216arm64
pyenv local 3.9.0
git clone https://github.com/hanbinglengyue/FART.git
adb push frida_fart/lib/fart* /data/local/tmp
adb shell && cp fart* /data/app && chmod 777
frida -U -f com.caratlover -l frida_fart_hook.js --no-pause  使用安卓8和安卓8.1进行脱壳
mv ../*.dex carat &&  adb pull /sdcard/carat

开启内存漫游

pyenv local 3.8.0
./fs128arm64
objection -g com.caratlover explore
android intent launch_activity com.chanson.business.MainActivity  直接绕过强制会员购买页面

将破解vip添加在r0trace的main中执行一次,实现trace某一个类时执行单次hook

function main() {
    Java.perform(function () {
        console.warn("r0tracer begin ... !")
        Java.perform(function(){
            Java.use("com.chanson.business.message.activity.ChatActivity").Z.implementation = function(){
                console.log("Calling isVIP ")
                return false;
            }
        })

    })
}

frida -UF -l hookEvent.js 点击发送消息,触发com.tencent.qcloud.tim.uikit.modules.chat.layout.input.InputLayout`,并弹窗要求付费,我们尝试trace该类的同时并破解vip

function main() {
    Java.perform(function () {
        console.warn("r0tracer begin ... !")
        traceClass("com.tencent.qcloud.tim.uikit.modules.chat.layout.input.InputLayout");
        Java.perform(function(){
            Java.use("com.chanson.business.message.activity.ChatActivity").Z.implementation = function(){
                console.log("Calling isVIP ")
                return false;
            }
        })

    })
}

frida -UF -l r0tracer.js –no-pause > chat.txt 开启trace,只有frida12 没有runtime=v8的选项,发送消息,查看调用栈

image-20210607232416746

在jadx中找到InputLayout的onClick方法

image-20210607234715239

尝试

traceClass("com.tencent.qcloud.tim.uikit.modules.message.MessageInfoUtil")

function main() {
    Java.perform(function () {
        console.warn("r0tracer begin ... !")
        traceClass("com.tencent.qcloud.tim.uikit.modules.message.MessageInfoUtil")

        Java.perform(function(){
            Java.use("com.chanson.business.message.activity.ChatActivity").Z.implementation = function(){
                console.log("Calling isVIP ")
                return false;
            }
        })

    })
}

frida -UF -l r0tracer.js –no-pause > chat.txt 开启trace,再次发送消息,搜索我们发送的ccccdddd

image-20210607235129408

通过jadx找到

com.tencent.qcloud.tim.uikit.modules.message.MessageInfoUtil

的buildTextMessage方法

image-20210607235326550

想办法获取MessageInfo返回值的内容

function main() {
    Java.perform(function () {
        console.warn("r0tracer begin ... !")
        traceClass("com.tencent.qcloud.tim.uikit.modules.message.MessageInfo")

        Java.perform(function(){
            Java.use("com.chanson.business.message.activity.ChatActivity").Z.implementation = function(){
                console.log("Calling isVIP ")
                return false;
            }
        })

    })
}

frida -UF -l r0tracer.js –no-pause > chat.txt 开启trace,再次发送消息tttttttt,搜索tttttttt

Inspecting Fields: => true => class com.tencent.qcloud.tim.uikit.modules.message.MessageInfo com.tencent.imsdk.TIMMessage TIMMessage => TIMMessage{ ConverstaionType:Invalid ConversationId: MsgId:2148258574 MsgSeq:32779 Rand:2148258574 time:1614087810 isSelf:true Status:Sending Sender:klover1_server_550179 elements:[ {Type:Text, Content:

tttttttt

} ] } => “<instance: com.tencent.imsdk.TIMMessage>” java.lang.String dataPath => null => null android.net.Uri dataUri => null => null com.tencent.imsdk.TIMElem element => com.tencent.imsdk.TIMTextElem@7d67029 => “<instance: com.tencent.imsdk.TIMElem, $className: com.tencent.imsdk.TIMTextElem>” java.lang.Object extra => tttttttt => “<instance: java.lang.Object, $className: java.lang.String>” java.lang.String fromUser => klover1_server_550179 => “klover1_server_550179” boolean group => false => false java.lang.String groupNameCard => null => null java.lang.String id => 70b42de0-097a-4b9c-927d-13e660ce86a6 => “70b42de0-097a-4b9c-927d-13e660ce86a6” int imgHeight => 0 => 0 int imgWidth => 0 => 0 long msgTime => 1614087810 => “1614087810” int msgType => 0 => 0 boolean peerRead => false => false boolean read => true => true boolean self => true => true int status => 1 => 1 long uniqueId => 0 => “0” int MSG_STATUS_DELETE => 274 => 274 int MSG_STATUS_DOWNLOADED => 6 => 6 int MSG_STATUS_DOWNLOADING => 4 => 4 int MSG_STATUS_NORMAL => 0 => 0 int MSG_STATUS_READ => 273 => 273 int MSG_STATUS_REVOKE => 275 => 275 int MSG_STATUS_SENDING => 1 => 1 int MSG_STATUS_SEND_FAIL => 3 => 3 int MSG_STATUS_SEND_SUCCESS => 2 => 2 int MSG_STATUS_UN_DOWNLOAD => 5 => 5 int MSG_TYPE_AUDIO => 48 => 48 int MSG_TYPE_CUSTOM => 128 => 128 int MSG_TYPE_CUSTOM_FACE => 112 => 112 int MSG_TYPE_FILE => 80 => 80 int MSG_TYPE_GROUP_CREATE => 257 => 257 int MSG_TYPE_GROUP_DELETE => 258 => 258 int MSG_TYPE_GROUP_JOIN => 259 => 259 int MSG_TYPE_GROUP_KICK => 261 => 261 int MSG_TYPE_GROUP_MODIFY_NAME => 262 => 262 int MSG_TYPE_GROUP_MODIFY_NOTICE => 263 => 263 int MSG_TYPE_GROUP_QUITE => 260 => 260 int MSG_TYPE_IMAGE => 32 => 32 int MSG_TYPE_LOCATION => 96 => 96 int MSG_TYPE_MIME => 1 => 1 int MSG_TYPE_TEXT => 0 => 0 int MSG_TYPE_TIPS => 256 => 256 int MSG_TYPE_VIDEO => 64 => 64 [native function h() { [native code] } => undefined => undefined

entered com.tencent.qcloud.tim.uikit.modules.message.MessageInfo.getTIMMessage java.lang.Throwable at com.tencent.qcloud.tim.uikit.modules.message.MessageInfo.getTIMMessage(Native Method) at com.tencent.qcloud.tim.uikit.modules.chat.base.ChatManagerKit.

sendMessage

(SourceFile:11)

image-20210608000342744

主要逻辑在

this.mCurrentConversation.sendMessage

,进入sendMessage方法

image-20210608000653377

进入

conversation.sendMessage

方法

image-20210608000723461

具体流程在native层,使用的是

腾讯云sdk

,很难抓到包,不过可以在

com.tencent.qcloud.tim.uikit.modules.message.MessageInfoUtil.buildTextMessage

构造消息体

android heap search instances com.tencent.imsdk.TIMManager 
android hooking list class_methods com.tencent.imsdk.TIMManager
android heap execute 227890024 getLoginUser  根据堆中的实例主动调用方法
android heap execute 227890024 getVersion
android hooking search classes TIMConversation
android hooking list class_methods com.tencent.imsdk.TIMConversation

trace单个函数在r0trace中添加

if(targetMethod.toString().indexOf("getConversation") < 0){
    return
}

查看腾讯云官方文档

文档中心


>


即时通信 IM


>


SDK 文档


>


旧版 API 教程


>


消息收发


>


消息收发(Android)

,获取会话由

TIMManager

中的

getConversation

实现。

function TIMManager() {
    Java.perform(function () {
        Java.choose("com.tencent.imsdk.TIMManager", {
            onMatch: function (ins) {
                console.log("found ins => ", ins)
                console.log("found ins.getNetworkStatus() => ", ins.getNetworkStatus())
                console.log("found ins.getSdkConfig() => ", ins.getSdkConfig())
                console.log("found ins.getUserConfig() => ", ins.getUserConfig())  //看不到内容可以通过r0trace的inspectObject单独看
                var output = "";
                output = inspectObject(ins.getUserConfig(), output);
                console.log(output)
    }, onComplete: function () {
        console.log("search compeled")
    }
        })
    })
}

尝试trace腾讯云sdk,

frida -UF -l r0tracer.js --no-pause -o chat.txt

,重新进入聊天界面获取log中的peer,即用户id

function main() {
    Java.perform(function () {
        console.warn("r0tracer begin ... !")
        traceClass("com.tencent.imsdk.TIMManager") 
        Java.perform(function(){
            Java.use("com.chanson.business.message.activity.ChatActivity").Z.implementation = function(){
                console.log("Calling isVIP ")
                return false;
            }
        })

    })
}

有了peer就可以调用

TIMManager.getInstance().getConversation



sendMessage

发送消息了

image-20210608004924503

function TIMManager() {
    Java.perform(function () {
        Java.choose("com.tencent.imsdk.TIMManager", {
            onMatch: function (ins) {
                console.log("found ins => ", ins)
                console.log("found ins.getNetworkStatus() => ", ins.getNetworkStatus())
                console.log("found ins.getSdkConfig() => ", ins.getSdkConfig())
                // console.log("found ins.getUserConfig() => ", ins.getUserConfig())  看不到内容可以通过r0trace的inspectObject单独看
                // var output = "";
                // output = inspectObject(ins.getUserConfig(), output);
                // console.log(output)

                 var peer = Java.use('java.lang.String').$new("klover1_server_190249");  // 这就是peer用户id
                 var conversation = ins.getConversation(Java.use("com.tencent.imsdk.TIMConversationType").C2C.value, peer);

                 var msg = Java.use("com.tencent.imsdk.TIMMessage").$new();
                 //添加文本内容
                 var elem = Java.use("com.tencent.imsdk.TIMTextElem").$new();
                 elem.setText(Java.use("java.lang.String").$new("cpdd"));
                 msg.addElement(elem)

                 const callback = Java.registerClass({  // new 一个接口
                     name: 'callback',
                     implements: [Java.use("com.tencent.imsdk.TIMValueCallBack")],
                     methods: {
                         onError(code, desc) {
                             console.log("send message failed. code: " + code + " errmsg: " + desc);
                         },
                         onSuccess(msg) {//发送消息成功
                             console.log("SendMsg ok" + msg);
                         },
                     }
                 });
                 conversation.sendMessage(msg, callback.$new())

    }, onComplete: function () {
        console.log("search compeled")
    }
        })
    })
}

以上实现了sdk中完整的发送消息的流程

image-20210608005229195

调用批量发送

function TIMManager() {
    Java.perform(function () {
        Java.choose("com.tencent.imsdk.TIMManager", {
            onMatch: function (ins) {
                console.log("found ins => ", ins)
                console.log("found ins.getNetworkStatus() => ", ins.getNetworkStatus())
                console.log("found ins.getSdkConfig() => ", ins.getSdkConfig())
                // console.log("found ins.getUserConfig() => ", ins.getUserConfig())  看不到内容可以通过r0trace的inspectObject单独看
                // var output = "";
                // output = inspectObject(ins.getUserConfig(), output);
                // console.log(output)
                console.log("found ins.getConversationList() => ", ins.getConversationList())
                console.log("found ins.getConversationList() => ", ins.getConversationList().toString())
                console.log("found ins.getConversationList() => ", JSON.stringify(ins.getConversationList()))

                var iter = ins.getConversationList().listIterator();
                while (iter.hasNext()) {
                    console.log(iter.next());
                    if (iter.next() != null) {
                        var TIMConversation = Java.cast(iter.next(), Java.use("com.tencent.imsdk.TIMConversation"))
                        console.log(TIMConversation.getPeer());
                        // if (TIMConversation.getPeer().toString().indexOf("209509") >= 0) {
                        console.log("try send message...")

                        //构造一条消息
                        var msg = Java.use("com.tencent.imsdk.TIMMessage").$new();
                        //添加文本内容
                        var elem = Java.use("com.tencent.imsdk.TIMTextElem").$new();
                        elem.setText("cpdd 你是唯一 问我是谁 codewj");
                        //将elem添加到消息
                        msg.addElement(elem)

                        const callback = Java.registerClass({
                            name: 'com.tencent.imsdk.TIMValueCallBackCallback',
                            implements: [Java.use("com.tencent.imsdk.TIMValueCallBack")],
                            methods: {
                                onError(i, str) { console.log("send message failed. code: " + i + " errmsg: " + str) },
                                onSuccess(msg) { console.log("SendMsg ok", +msg) }
                            }
                        });
                        //发送消息
                        TIMConversation.sendMessage(msg, callback.$new())
                    }
                }

    }, onComplete: function () {
        console.log("search compeled")
    }
        })
    })
}

微信图片_20210608202426

本文由博客群发一文多发等运营工具平台

OpenWrite

发布



版权声明:本文为welggy原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。