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