PC端那位老师已经演示的很清楚了,我就简单的复述一遍,感兴趣的可以看一下视频。
微信的核心功能都是在WeChatwin.dll(windows下)里实现的,信息撤回也不例外。
我们使用ida分析,通过字符串进行定位,试试相关的字符,如:撤回、revoke、withdrawn等关键字
我们逐一hook使用到这些字符串的函数,然后点击微信的撤回,如果有log输出,就代表是关键函数。
测试脚本如下,首先我们获取了dll的加载地址,然后通过ida查看到函数的偏移地址,将dll地址加上该偏移地址,即可得到内存中该函数的地址,那么如何确定哪一个才是撤回相关函数? 就是靠试,使用到msg相关字符串的函数我们都hook一遍,然后触发撤回功能,如果打印出来revokeMsg就说明hook成功。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | import frida,sys # 创建脚本 jsCode = """ //写入js脚本 就和之前一样 //下面的代码获取dll的加载地址 const baseAddr=Module.findBaseAddress('WeChatWin.dll'); console.log("baseAddr:"+baseAddr); //每次需要修改的只有这里 const revokeMsgFunAddr=getRealAddr('0x1823CD710'); console.log("function addr:",revokeMsgFunAddr); //根据地址进行frida注入 Interceptor.attach(revokeMsgFunAddr,{\ //一旦进入hook的函数,该回调函数就会被调用 onEnter(args){ console.log("-----revokeMsg------") } }); //dll地址加上偏移地址,定位到这个函数 function getRealAddr(addr){ //基地址 const idaBase=ptr('0x180000000'); const offset=ptr(addr).sub(idaBase); const result=ptr(baseAddr).add(offset); return result; } """ #使用任务管理器查看微信的PID然后填入 session = frida.attach( 7876 ) # 运行脚本 script = session.create_script(jsCode) script.load() print ( "Successfully attached!!!!" ) # 防止运行完进程直接退出 sys.stdin.read() |
运行测设脚本
当我们测试到字符串SyncMgr::ProcessRevokeMsg所在的函数时,有了revokeMsg的回显
函数逻辑比较复杂,hook点也是比较难找
但是我们知道每次撤回信息,这个函数都要被调用,我们直接查看他的交叉引用,发现这个函数在一个switch语句里被调用
edi为4的时候就会执行撤回相关的函数,修改思路就是当程序运行到这里时修改edi寄存器的值
1 2 3 4 5 6 7 8 | Interceptor.attach(revokeMsgFunAddr,{ / / 一旦进入hook的函数,该回调函数就会被调用 onEnter(args){ console.log( "-----revokeMsg------" ) console.log(this.context.rdi); this.context.rdi = 0 ; } }); |
最终实现的效果:
PC端显示
大致思路也是向PC端演示的那样,但是因为没有参考的教程,所以走了不少弯路
还是直接搜索字符串进行定位,考虑到PC端那里时revoke关键字,所以也是直接搜索revoke
其实也不用逐个尝试,因为有几个特别明显的,hook这几个特别明显的方法测试就行
直接右键方法名选择复制为frida片段即可,最终是定位到了这个RevokeMsgEvent方法,每当我们撤回信息,都会打印 RevokeMsgEvent.$init is called
1 2 3 4 5 6 7 8 | Java.perform( function (){ console.log( "==========begain==========" ) let RevokeMsgEvent = Java.use( "com.tencent.mm.autogen.events.RevokeMsgEvent" ); RevokeMsgEvent[ "$init" ].implementation = function () { console.log(`RevokeMsgEvent.$init is called`); this [ "$init" ](); }; }); |
这个方法其实没啥有价值的东西,尝试了直接置空这个方法,也就是将上面的 this["$init"]();
直接删除,并没有起到什么效果
然后就是查看了这个方法的交叉引用
整个程序只有这一出引用,微信应该做了一些混淆,名字都奇奇怪怪的,可读性很差,一时见不知从何下手。首先尝试了修改RevokeMsgEvent的赋值,它将 变量赋值为falsethis.f153351e = false;
,我尝试了一下将该变量赋值为true,注意没有f153351e,这是jadx帮它命名的,实际的变量名是e
1 2 3 4 5 6 7 8 | Java.choose( "com.tencent.mm.autogen.events.RevokeMsgEvent" ,{ / / 遍历内存中的所有对象 onMatch:function(obj){ obj.e.value = true; console.log( "value : " ,obj.e.value); },onComplete:function(){ console.log( "内存对象搜索完毕" ) } }) |
修改成功,但是信息还是被撤回。
这条路行不通就考虑另外的方案,hook掉调用这个方法的方法,和pc端相同的操作
该方法是被s类下的h方法所调用的,复制h方法的frida片段进行hook
1 2 3 4 5 | let s = Java.use( "tj0.s" ); s[ "h" ].implementation = function (str, j15, n0Var, str2, str3, str4) { console.log(`s.h is called: str=${str}, j15=${j15}, n0Var=${n0Var}, str2=${str2}, str3=${str3}, str4=${str4}`); this [ "h" ](str, j15, n0Var, str2, str3, str4); }; |
报错,提示没有找到tj0.s这个类,于是我想这个类可能不是被默认的类加载器所加载的,遍历一下所有的类加载器然后再hook
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | function main() { Java.perform( function () { Java.enumerateClassLoaders({ onMatch: function (loader) { try { var factory = Java.ClassFactory.get(loader); var s = factory.use( "tj0.s" ); s[ "h" ].implementation = function (str, j15, n0Var, str2, str3, str4) { console.log(`s.h is called: str=${str}, j15=${j15}, n0Var=${n0Var}, str2=${str2}, str3=${str3}, str4=${str4}`); this [ "h" ](str, j15, n0Var, str2, str3, str4); }; } catch (e) { console.log( "Error accessing class or method: " + e); } }, onComplete: function () {} }); }); } |
结果遍历了所有的类加载器也没有这个类
而且我使用objection打印加载的类也没有这个tj0.s
最后想到了打印方法的调用堆栈
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | Java.perform( function (){ console.log( "==========begain==========" ) let RevokeMsgEvent = Java.use( "com.tencent.mm.autogen.events.RevokeMsgEvent" ); //hook构造方法 RevokeMsgEvent[ "$init" ].implementation = function () { console.log(`RevokeMsgEvent.$init is called`); showStacks(); this [ "$init" ](); }; function showStacks(){ console.log( Java.use( "android.util.Log" ) //首先找到log类 .getStackTraceString( //调用log类的该方法 Java.use( "java.lang.Throwable" ).$ new () //new一个对象 ) ) } }); |
通过函数的调用堆栈,发现RevokeMsgEvent是被ij0.s.i方法调用的,我们直接将这个方法置空
1 2 3 4 | var sClass=Java.use( "ij0.s" ); sClass.i.implementation= function (){ console.log( "======================" ); }; |
最终实现的效果,左边是pc显示的,右边是手机显示的
使用xposed将上面的代码重写一下即可,frida逻辑如下
1 2 3 4 5 6 7 8 | Java.perform(function(){ var sClass = Java.use( "ij0.s" ); sClass.i.implementation = function(){ console.log( "======================" ); }; }); |
xposed代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | package com.example.xposeddemo; import android.util.Log; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Map; import java.util.Objects; import de.robv.android.xposed.IXposedHookLoadPackage; import de.robv.android.xposed.XC_MethodHook; import de.robv.android.xposed.XC_MethodReplacement; import de.robv.android.xposed.XposedBridge; import de.robv.android.xposed.XposedHelpers; import de.robv.android.xposed.callbacks.XC_LoadPackage; public class Hook implements IXposedHookLoadPackage { @Override public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable { if (!loadPackageParam.packageName.equals( "com.tencent.mm" )) return ; Class<?> clazz=XposedHelpers.findClass( "ij0.s" ,loadPackageParam.classLoader); Log.d( "mxy" , clazz.toString()); XposedHelpers.findAndHookMethod(clazz, "i" , new XC_MethodReplacement() { @Override protected Object replaceHookedMethod(MethodHookParam methodHookParam) throws Throwable { Log.d( "mxy" , "hook successfully" ); return null ; } }); } } |