篇幅有限

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

绕过强制会员

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

脱壳

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

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

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

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

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

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

搭讪

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

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

查看InputLayout该类的用例,该UI基本都在com.chanson.business.message.activity.ChatActivity中调用

其中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 argsif (arguments.length) console.log();for (var j = 0; j < arguments.length; j++) {console.log("arg[" + j + "]: " + arguments[j]);}// print retvalvar 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)

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 argsif (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 retvalvar 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

旧版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整个类,查看调用链,点击发送消息时,弹窗付费

查看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

每次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)

抓包

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

配置规则

遇到8668端口抓不到,报错SSL:Unsupported or unrecognized SSL message,修改charles的Proxy Settings

盲猜一波是base64加密

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)

通过jadx查看com.chanson.common.a.j方法,其中com.chanson.common.utils.a.b将传入的jsonObject转成string后调用c方法。

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

大量的加密字段类似base64,尝试trace Base64。修改traceClass("android.util.Base64"),开启trace,frida -U com.caratlover -l r0tracer.js --no-pause -o base64.txt追查调用栈

通过jadx查看com.chanson.common.a.d,其中String a2 = a.a(string, "f87210e0ed3079d8");的a方法跳转到实现发现是一个完整的标准aes加密。

全局搜索还有AESUtils,完全自己开发的非标准的AES加密,7z x com.caratlover.apk 查看lib/armeabi-v7a下存在alicomphonenumberauthsdk-log-online-standard-release_alijtca_plus.so

strings查看该so中的字符串,traceClass("com.mobile.auth.gatewayauth.utils.security.CheckRoot")

对抗更新

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

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

查看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)

不过页面无法操作,尝试直接跳到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构造函数,点击马上更新

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

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

找到com.chanson.business.login.presenter.PhoneLoginPresenter$a.a实现方法

找到a方法的调用处,在switch的baseResponse.getErrorCode()的判断时调用PhoneLoginPresenter.f10498a.a,其中renamed from: com.chanson.business.g.s正是我们trace得到的类

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

尝试tracecom.chanson.common.base.BaseResponse查看getErrorCode的结果,返回10002,正巧会调用PhoneLoginPresenter.f10498a.a((Update) rVar.a(rVar.a(baseResponse.getUpdate()), Update.class));

使用新版本的apk启动时重新tracecom.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-pausehook 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] = 50console.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的选项,发送消息,查看调用栈

在jadx中找到InputLayout的onClick方法

尝试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

通过jadx找到com.tencent.qcloud.tim.uikit.modules.message.MessageInfoUtil的buildTextMessage方法

想办法获取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)

主要逻辑在this.mCurrentConversation.sendMessage,进入sendMessage方法

进入conversation.sendMessage方法

具体流程在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().getConversationsendMessage发送消息了

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用户idvar 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中完整的发送消息的流程

调用批量发送

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")}})})
}

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

克拉恋人会员制取证分析相关推荐

  1. 安全技术大系iOS取证分析

    <安全技术大系iOS取证分析> 基本信息 作者: (美)莫里西(Morrissey,S.) [作译者介绍] 译者: 郭永健 韩晟 钟琳 出版社:电子工业出版社 ISBN:978712117 ...

  2. DEFCON 20 CTF 磁盘取证分析题目

    这是一道取证分析题目,主要考察取证分析能力,包括磁盘文件恢复.图片文件修复.数据分析.图片隐写信息提取等. 本次实验题目地址:<DEFCON 20 CTF Quals Forensic 200& ...

  3. 使用EventLog Analyzer进行日志取证分析

    一.构建犯罪现场以寻找安全漏洞的根源 大多数情况下,公司无法追查发起网络违规的网络入侵者.尽管采取了最好的预防措施来防止发生攻击,但不可能保护您的网络免受任何攻击.所有的攻击者都会留下痕迹,并且您的事 ...

  4. 西数DES数据库取证分析大师系统

    西数DES数据库取证分析大师系统  软件界面             功能介绍: 支持勒索病毒加密的oracle数据库 可从数据文件 DBF,dmp文件恢复表数据,存储过程,触发器等所有数据.. (1 ...

  5. 对Apple Watch的取证分析(续)

    在过去几年中,智能可穿戴设备的使用显著增加.2018年智能手表销量达1.41亿部,智能可穿戴设备销量同比增长近一倍.在激烈的市场竞争中,Apple Watch占据了主导地位,2018年可穿戴设备销量超 ...

  6. 利用Volatility进行Windows内存取证分析(一):初体验

    简介 承接上文,上文中使用cuckoo沙箱的时候提到过,分析恶意代码的时候,首先利用沙箱做粗略分析,然后可以目标程序进行动态分析(OD,Windbg调试)或者静态分析(IDA静态反汇编).如果嫌每次逆 ...

  7. 计算机取证工具常用工具,X-Ways Forensics,全球知名的计算机综合取证分析工具...

    X-Ways Forensics,全球知名的计算机综合取证分析工具. X-Ways Forensics 20.0 SR-5(最新) X-Ways Forensics,全球知名的计算机综合取证分析工具, ...

  8. 使用Allegro进行数据包取证分析

    文章目录 简介 便携式取证分析解决方案 内联旁路+带外监控解决方案 数据中心流量聚合 设备方案优势 关注我们 简介 数据包是网络中传输的最小数据单位.网络管理员需要这个粒度单元来收集全面的网络分析,但 ...

  9. 如何对Windows剪切板里的内容进行取证分析 Windows剪切板取证

    前言 无论是在现实中对设备进行取证分析,还是在ctf中做取证类的题目,剪切板里的内容都需要去查看,以免遗漏什么重要信息 剪切板位置 剪切板是计算机操作系统提供的一个临时存储区域,用于在不同应用程序之间 ...

最新文章

  1. 在Ubuntu 14.04 64bit上使用dig
  2. 从新手到入门,如何进入协议分析的世界
  3. debian linux 硬盘,[Debian] 硬盘安装Debian,
  4. 大数据 -- Hadoop集群环境搭建
  5. Spark架构与作业执行流程简介
  6. python切片原理_深度解析Python切片
  7. (hdu 简单题 128道)平方和与立方和(求一个区间的立方和和平方和)
  8. unity中脚本编辑器UnIDE
  9. Oracle解析XML 节点信息并转换String 类型
  10. 【AI自学】 完备的 AI 学习路线,最详细的资源整理!
  11. oracle20g,GaussDB 100 OLTP: 缩减创建数据库的20G空间需求及GS-00714
  12. 题解 P2610 【[ZJOI2012]旅游】
  13. 计算机打印机节支措施,“节支降耗,从我做起 ”倡导篇 ——节约纸张
  14. 如何实现数字化转型?麦肯锡:数字化转型四步法
  15. MySQL基础 增删改查练习
  16. 小葵花妈妈课堂开课了:《Handler Looper Message 浅析》
  17. Exchange Rate Difference
  18. 移动端调试神器(TCon)-- 基于 try catch 捕获所有异常的web移动端控制台(调试工具、console)
  19. c++基础学习:输入cin、输出cout、换行endl
  20. Paradigm Shifts in Kernel Programming 内核编程的范式转移

热门文章

  1. Android项目:基于Android的课程表系统app设计(计算机毕业设计)
  2. CFA/FRM/CAIA刷题名单 必刷题库
  3. 服务器又宕机了,怎么办?
  4. redis防表单重复提交
  5. 计算机内部的线路能承受多高的温度,揭秘:CPU到了120°会不会坏?它到底能承受多高的温度...
  6. 三个经典故事改变人生
  7. 计算机电池的性能参数,基于51单片机的蓄电池性能测试仪的设计.doc
  8. 中国科技大学计算机博士毕业条件,信息科学与工程学院博士研究生学位授予标准...
  9. 区块链软件:谈溯源问题
  10. SDFormat-sensor详解