Analysis of making membership card

Posted by project-nz on Mon, 13 Dec 2021 07:27:00 +0100

Limited space

Full content and source code: official account: ReverseCode, send punch

Bypass mandatory membership

adb install com. caratlover. After APK is installed, you can enter the home page only after paying the membership fee

Shelling

jadx opened and found that there was little code. It was reinforced by visual inspection. Take off your clothes first.

git clone https://github.com/hluwa/FRIDA-DEXDump.git
./fs1426arm64
pyenv local 3.9.0
python main.py   app Keep the front end,Start shelling

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  Using Android 8 and Android 8.1 Shelling
mv ../*.dex carat &&  adb pull /sdcard/carat

File * view that the file format is Dalvik dex file, but an error is reported when the stripped part of the dex file is opened with 010 Editor, indicating that the file is not standard.

objection -g com.caratlover explore
android hooking list activities
android intent launch_activity com.chanson.business.MainActivity  Directly bypass the mandatory member purchase page

Use jadx1 Open multiple dex at the same time in 2.0 to find com chanson. business. MainActivity

Use 12.8 0 frida confused parents don't know, still use 14.2 Version 16.

After bypassing the mandatory member page, edit the profile and fill in the personal details.

Accost

When you click send, call hookevent JS view the triggered class Frida - UF - L hookevent js

[Pixel::Clara lover]-> [WatchEvent] onClick: com.tencent.qcloud.tim.uikit.modules.chat.layout.input.InputLayout

View the use cases of InputLayout class. The UI is basically on COM chanson. business. message. activity. Call in ChatActivity

Including com chanson. business. message. activity. Chatactivity has a piece of code to judge whether it is 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();
    }
}

The isVip method comes from com chanson. business. model. Basic userinfobean, we try to trace the class and print the value of each field of the class.

trace

frida -UF -l trace.js -o traceVip.txt trace all dynamic and static methods and constructors of the specified class

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 is a new object, remember?
    var hook = Java.use(targetClass);
  //Use reflection to get all the methods of the current class
    var methods = hook.class.getDeclaredMethods();
    // var methods = hook.class.getMethods();
    console.log("methods => ",methods)
  //Remember to release the object after the object is created
    hook.$dispose;
  //Save method name to array
    var parsedMethods = [];
    methods.forEach(function(method) {
        parsedMethods.push(method.toString().replace(targetClass + ".", "TOKEN").match(/\sTOKEN(.*)\(/)[1]);
    });
  //Remove some duplicate values
    var targets = uniqBy(parsedMethods, JSON.stringify);
    // Only hook constructors
    //targets = [];
    targets = targets.concat("$init")
    console.log("targets=>",targets)
  //hook all the methods in the array. traceMethod is the content of the first section
    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)

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)

Optimize correspondence

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;
        }
    }

}

vip

Old version 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)

New version 4.6 0

android hooking watch class com.chanson.business.message.activity.ChatActivity --dump-args --dump-backtrace --dump-return  When we can't judge when to judge vip When, hook The whole class, view the call chain, click send message, and pay by pop-up window

Check out com.com in jadx chanson. business. message. activity. The chatactivity class knows through the aa method that only when it is pulled black, if it returns false, it cannot send a message. In the first step, we let Z() return false and directly enter return true

private final boolean aa() {
    if (!Z()) {
        return true;
    }
    if (this.f10873d == null) {
        Hb.a(Hb.f11628c, "Data exception", 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, "You have hacked the other party and can't send messages", 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, "The other party has hacked you and can't send messages", 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;
        }
    }
}

Determine the source code implementation of ChatActivity through object

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

Every time Z() returns true, it will not enter the message sending logic. Actively call Z() to return false to crack the 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)

Grab bag

Postern configures the agent, where 192.168 0.107 is charles' host ip and 8889 is charles' socks

Configuration rules

When port 8668 cannot be caught, an error SSL is reported: Unsupported or unrecognized SSL message. Modify charles' Proxy Settings

Blind guess that the first wave is base64 encryption

python r0capture.py -U -f com.caratlover -v -w 2 >> capture.txt  The packet capture discovery is encrypted, and the class is very confused. Although we can't recognize the role of the class, we can pass it trace To trace the return value of the call

Find the login package / auth / login check, which calls at com in the stack chanson. common. a.j.intercept(SourceFile:45)

View com.com through jadx chanson. common. a. J method, where com chanson. common. utils. A.B calls the c method after transforming the incoming jsonObject into string.

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

Error: java.lang.ClassNotFoundException: Didn't find class "com.chanson.common.utils.a.b" the error is because the app needs time to start. Modify setTimeout(main, 2000);

trace login, first open the login interface, enter the password, and then Frida - U com caratlover -l r0tracer. js --no-pause -o traffic. txt

A large number of encrypted fields are similar to Base64. Try trace Base64. Modify traceClass("android.util.Base64") and open trace, Frida - U com caratlover -l r0tracer. js --no-pause -o base64. Txt trace call stack

View com.com through jadx chanson. common. a. D, where String a2 = a.a(string, "f87210e0ed3079d8"); A method jump to implementation discovery is a complete standard aes encryption.

Global search also has AESUtils, a completely self-developed non-standard AES encryption, 7z x com caratlover. Apk check that alicomphonenumberauthsdk log online standard release exists in lib/armeabi-v7a_ alijtca_ plus. so

strings view the string in the so, traceClass("com.mobile.auth.gatewayauth.utils.security.CheckRoot")

Confrontation update

adb connect 172.20.103.172  start-up wifiadb
adb install com.caratlover4.1.0.apk
frida -UF -l hookEvent.js  Click the update now button to trigger the click time and print the click class

Open jadx to view the dex files after shelling one by one. The decompiled result of encrypted dex will be rename d in the new version of jadx

View the ConfirmDialogFragment class, where

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

Active call to remove pop-up window

frida -UF -l disableUPDATE.js and then destroy

function disableUPDATE(){
    Java.perform(function(){
        Java.choose("com.chanson.business.widget.ConfirmDialogFragment",{
            onMatch:function(ins){
                // The dynamic method choose onMatch finds an instance to call
                console.log("found ins => ",ins);
                // See the real method name from smali or object
                ins.onDestroyView()
            },
            onComplete:function(){
                console.log("Search completed!")
            }
        })
    })
}
function main(){
    console.log("Start hook")
    disableUPDATE()
}
setImmediate(main)

However, the page cannot be operated. Try to jump directly to 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 add targets = [] in traceClass; Just hook constructor, click update now

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

setImmediate is the immediate execution function and setTimeout is the delayed execution function after waiting for milliseconds. There is no difference between the two in the attach mode. In the spawn mode, the hook system API, such as javax crypto. Cipher recommends using setImmediate to execute immediately without delay. In the spawn mode, when hook applies its own function or contains a shell, it is recommended to use setImmediate and give an appropriate delay (500 ~ 5000)

Find com chanson. business. login. presenter. Phoneloginpresenter $A.A implementation method

Find the calling place of the a method in the baseresponse Call phoneloginpresenter when determining geterrorcode() f10498a. a. Among them, renamed from: com chanson. business. g. S is the class we get from trace

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

Try tracecom chanson. common. base. Baseresponse checks the result of getErrorCode and returns 10002, which happens to call phoneloginpresenter f10498a. a((Update) rVar. a(rVar.a(baseResponse.getUpdate()), Update. class));

Restart tracecom.com when starting with the new version of apk chanson. common. base. Baseresponse: under normal conditions, the returned value of case is 10001.

Java.use("com.chanson.common.base.BaseResponse").getErrorCode.implementation = function(){
    console.log("Calling getErrorCode ")
    return 10001;
}
setTimeout(main,2000)  // Shell switching takes time

frida -U -f com. caratlover -l disableUPDATE. JS -- no pausehook geterrorcode directly returns 10001. It is found that the login is normal. When logging in, we found that there is abnormal data in your account. To ensure the security of your account, please log in again. r0capture packet capture found that the version number has been verified. Next, change the input parameter of SSLOutputStream to a new version

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 string to hex 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;
}

Batch flirting

The new version of jadx GUI is still shelled

./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  Using Android 8 and Android 8.1 Shelling
mv ../*.dex carat &&  adb pull /sdcard/carat

Enable memory roaming

pyenv local 3.8.0
./fs128arm64
objection -g com.caratlover explore
android intent launch_activity com.chanson.business.MainActivity  Directly bypass the mandatory member purchase page

Add the crack vip to the main of r0trace and execute it once. When a class of trace is implemented, execute hook once

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 click send message to trigger com tencent. qcloud. tim. uikit. modules. chat. layout. input. Inputlayout 'and pop-up to ask for payment. We try to trace this class and crack the vip at the same time

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 enable trace. Only frida12 does not have the option of runtime=v8. Send a message and view the call stack

Find the onClick method of InputLayout in jadx

Try 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 start trace, send the message again, and search the ccccdddd we sent

Find com.com through jadx tencent. qcloud. tim. uikit. modules. message. buildTextMessage method of messageinfoutil

Find a way to get the content of the returned value of 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 start trace, send the message tttttt again, and search tttttt

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)

The main logic is this mCurrentConversation. sendMessage, enter the sendMessage method

Enter conversation SendMessage method

The specific process is in the native layer, using Tencent cloud sdk , it's hard to catch the bag, but it can be found on COM tencent. qcloud. tim. uikit. modules. message. MessageInfoUtil. Buildtextmessage constructs the message body

android heap search instances com.tencent.imsdk.TIMManager 
android hooking list class_methods com.tencent.imsdk.TIMManager
android heap execute 227890024 getLoginUser  Actively invoke methods based on instances in the heap
android heap execute 227890024 getVersion
android hooking search classes TIMConversation
android hooking list class_methods com.tencent.imsdk.TIMConversation

Trace single function added in r0trace

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

View Tencent cloud official documents Document Center > Instant messaging IM > SDK documentation > Legacy API tutorial > Messaging > Messaging (Android) , the get session is implemented by getConversation in TIMManager.

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())  //The invisible content can be viewed separately through r0trace's inspectObject
                var output = "";
                output = inspectObject(ins.getUserConfig(), output);
                console.log(output)
    }, onComplete: function () {
        console.log("search compeled")
    }
        })
    })
}

Try trace, Tencent cloud SDK, Frida - UF - L r0tracer js --no-pause -o chat. Txt, re-enter the chat interface to obtain the peer in the log, that is, the user 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;
            }
        })

    })
}

With peer, you can call timmanager getInstance(). The sendMessage of getconversation sent a message

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()) can't see the content. You can see it separately through r0trace's inspectObject
                // var output = "";
                // output = inspectObject(ins.getUserConfig(), output);
                // console.log(output)

                 var peer = Java.use('java.lang.String').$new("klover1_server_190249");  // This is the peer user id
                 var conversation = ins.getConversation(Java.use("com.tencent.imsdk.TIMConversationType").C2C.value, peer);

                 var msg = Java.use("com.tencent.imsdk.TIMMessage").$new();
                 //Add text content
                 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 an interface
                     name: 'callback',
                     implements: [Java.use("com.tencent.imsdk.TIMValueCallBack")],
                     methods: {
                         onError(code, desc) {
                             console.log("send message failed. code: " + code + " errmsg: " + desc);
                         },
                         onSuccess(msg) {//Message sent successfully
                             console.log("SendMsg ok" + msg);
                         },
                     }
                 });
                 conversation.sendMessage(msg, callback.$new())

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

The above implements the complete process of sending messages in sdk

Call batch send

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()) can't see the content. You can see it separately through r0trace's 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...")

                        //Construct a message
                        var msg = Java.use("com.tencent.imsdk.TIMMessage").$new();
                        //Add text content
                        var elem = Java.use("com.tencent.imsdk.TIMTextElem").$new();
                        elem.setText("cpdd You're the only one asking who I am codewj");
                        //Add elem to message
                        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) }
                            }
                        });
                        //send message
                        TIMConversation.sendMessage(msg, callback.$new())
                    }
                }

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

This paper is based on the operation tool platform of blog group sending one article and multiple sending OpenWrite release

Topics: frida