19年年末无聊的时候研究了下微信的机器人,发现并不是很难,当时主要实现了好友、群消息的实时获取,以及从微信本地数据库中拉取朋友圈数据。朋友圈数据的获取并不难,难的是对数据的解析,因为数据都是加密存储的,当时搞了好几天,后来终于搞定了,现将过程分享出来吧。
hook的过程都在手机上完成。如果手机root过,直接安装Xposed,没有root过的话,可以安装VirtualXposed(以下简称VXP),VXP是一个手机虚拟root环境,针对未root手机虚拟一个root的环境,相关应用安装在VXP里面,就可以实现需要root权限的相关操作。VXP大家可自行在GIT上面下载安装,微信安装7.0.5,并登陆你的微信号。
打开studio新建一个项目,并配置Xposed的hook信息,如下:
在src.main下新建一个文件夹:assets,然后在assets下新建一个名为xposed_init的文件,并在这个文件中放入你的hook主目录,我的是这样:
com.android.com.yh.gj.xp.XpMain
这个就是告诉xposed进行hook的入口。
重要是的meta-data里面的类型
引入xposed相关包compileOnly 'de.robv.android.xposed:api:82' compileOnly 'de.robv.android.xposed:api:82:sources'
这样,基本的配置就完成了。
暴力点,直接hook微信入库点,消息收到后都要入本地库,hook到了入库点,就能拿到消息信息。另外,获取库密码,这样就能从库中拉取朋友圈的信息
public void getDbPwdAndPath(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable{
if(this.checkPwdIsNull()){
XposedHelpers.findAndHookMethod(ConfigUtil.hookDbPackage, loadPackageParam.classLoader, ConfigUtil.bdMethod[1], String.class,
byte[].class, loadPackageParam.classLoader.loadClass(ConfigUtil.bdClasz[0]),
loadPackageParam.classLoader.loadClass(ConfigUtil.bdClasz[1]), int.class,
loadPackageParam.classLoader.loadClass(ConfigUtil.bdClasz[2]), int.class,
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable{
String password = new String((byte[]) param.args[1], "UTF-8");
String path = param.args[0].toString();
if(path.contains(ConfigUtil.wxDbName)){
FileUtil.writeStringToFile(password+"_"+path,ConfigUtil.pdFilePath);
context = AndroidAppHelper.currentApplication().getApplicationContext();
}
}
});
}
}
上面的代码可以直接拿到微信的本地数据库的密码及库文件路径,密码拿到了一切都好办。
/**
* 消息监控
* @param lpp
*/
public void getWeChatMsg(XC_LoadPackage.LoadPackageParam lpp){
XposedHelpers.findAndHookMethod(ConfigUtil.hookDbPackage,lpp.classLoader, ConfigUtil.bdMethod[0],String.class,String.class, ContentValues.class,new XC_MethodHook() {
@Override
protected void afterHookedMethod(final MethodHookParam param) throws Throwable{
context = AndroidAppHelper.currentApplication().getApplicationContext();
//消息监控
new WxMsgMonitor().MsgClass(param,context);
super.afterHookedMethod(param);
}
});
}
有新消息这里就会调用WxMsgMonitor,WxMsgMonitor的实现如下:
package com.android.com.yh.gj.wx;
import android.content.ContentValues;
import android.content.Context;
import com.alibaba.fastjson.JSON;
import com.android.com.yh.gj.util.ConfigUtil;
import com.android.com.yh.gj.util.FileUtil;
import java.lang.reflect.Method;
import de.robv.android.xposed.XC_MethodHook;
import static de.robv.android.xposed.XposedBridge.log;
public class WxMsgMonitor {
public void MsgClass(XC_MethodHook.MethodHookParam param, Context context) throws Throwable{
String msgType = param.args[0].toString();
log("类型:"+msgType);
log("文本1:"+param.args[1].toString());
log("文本2:"+param.args[2].toString());
if("message".equals(msgType)){
ContentValues values = (ContentValues)param.args[2];//消息信息均在里面,要用什么数据直接在里面获取就行
String type = values.get("type").toString();
//判断是否群消息,如果要区分群消息,请自行利用此段注释代码
// if(talker.contains("@chatroom")){//群消息
// talker = values.get("content").toString().split(":")[0];
// content = values.get("content").toString().split(":")[1];
// }
FileUtil.writeStringToFile("消息:"+param.args[2].toString(),ConfigUtil.dataFile[3]);
if("1".equals(type)){//文本、表情消息处理
}else if("3".equals(type)){//图片
FileUtil.writeStringToFile(values.get("imgPath").toString().split("//")[1]+"hd",ConfigUtil.dataFile[4]);
}else if("34".equals(type)){//语音消息处理
FileUtil.writeStringToFile(values.get("imgPath").toString(),ConfigUtil.dataFile[4]);
}else if("43".equals(type)){//视频消息处理
FileUtil.writeStringToFile(values.get("imgPath").toString(),ConfigUtil.dataFile[4]);
}else if("49".equals(type)){//文件消息处理
}else if("10000".equals(type)){//同意了加好友的申请处理
}else if("436207665".equals(type)){//红包消息处理
}else if("419430449".equals(type)){//转账消息处理
}else if("570425393".equals(type)){//邀请人加入了群聊消息处理
}
}else if("fmessage_conversation".equals(msgType)){//收到加好友邀请
//信息全部在param.args[2].toString()里面,用哪些数据自己取
}else if("oplog2".equals(msgType)){//删除好友监听到后处理
//信息全部在param.args[2].toString()里面,用哪些数据自己取
}else if("WxFileIndex2".equals(msgType)){
ContentValues values = (ContentValues)param.args[2];
if(values.get("path").toString().contains(FileUtil.readToString(ConfigUtil.dataFile[4])) && !FileUtil.readToString(ConfigUtil.dataFile[3]).contains("路径:")){
FileUtil.writeStringToFileForAppend("\n路径:"+ConfigUtil.localFile[0]+values.get("path").toString(),ConfigUtil.dataFile[3]);
}
}
// else if("MediaDuplication".equals(msgType)){//图片已保存到手机
// ContentValues values = (ContentValues)param.args[2];
// FileUtil.writeStringToFile( values.get("path").toString(),"/storage/emulated/0/filePath.txt");
// }
}
}
好了,上面实时获取消息的过程就算完成了,下面说一下从数据库拉取朋友圈数据
上面已经拿到了微信本地数据库的密码及文件位置了,我们先库文件复制一份出来:
SQLiteDatabase.loadLibs(context);
String uin = FileUtil.readToString(ConfigUtil.pdFilePath).split("_")[1].split("MicroMsg/")[1].split("/")[0];
try{
RootCmdUtil.shellCommand("chmod 777 -R "+ConfigUtil.wxRootPath);
}catch(Exception e){}
FileUtil.copyFile(ConfigUtil.wxRootPath+uin+ConfigUtil.wxSnsDbName, ConfigUtil.copySnsDbPath);
// log("开始获取朋友圈数据");
List list = getSnsList(10,context);//一次获取多少条请自行修改
// log("朋友圈数据获取成功:"+list);
然后再读取里面的内容:
List
这样里面的数据就拿到了。但是你会发现content的数据是乱的,所以这里要进行解析。这一步花了几天时间终于搞定(主要通过反射的方式)。解析如下:
package com.android.com.yh.gj.util;
import android.content.Context;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.LinkedList;
import dalvik.system.DexClassLoader;
import static de.robv.android.xposed.XposedBridge.log;
public class ParseUtil {
public static String parseContent(byte[] bytes, Context context,String type) throws Exception{
String wxApkFile = context.getApplicationInfo().sourceDir;
DexClassLoader dexClassLoader = new DexClassLoader(wxApkFile,context.getDir("dex1",0).getAbsolutePath(),null,context.getClassLoader());
Class timeLineObjects = dexClassLoader.loadClass(ConfigUtil.wxContenClass);
Method method = timeLineObjects.getMethod(ConfigUtil.parseMethod,byte[].class);
Object object = method.invoke(timeLineObjects.newInstance(),bytes);
if("1".equals(type)){
return parseContentImg(object);
}}else{
return "";
}
}
private static String parseContentImg(Object object)throws Exception{
String result = "";
Field[] fields = object.getClass().getFields();
if(fields.length>0){
String imgUrl = "";
for(Field field:fields){
if(field.getType() == String.class && field.getName().equals(ConfigUtil.wxContentVar[0])){
result = "title:"+field.get(object).toString()+";";
continue;
}
if(field.getType().getName().contains(ConfigUtil.hookRootPackageName)){
Field[] fields1 = field.get(object).getClass().getFields();
for(Field field1:fields1){
if(field1.getType().getName().contains(ConfigUtil.wxContentVar[3]) && field1.get(field.get(object)) != null){
LinkedList linkedLists = (LinkedList)field1.get(field.get(object));
for(Object linkedList:linkedLists){
Field[] fields2 = linkedList.getClass().getFields();
}
}
}
}
}
result = result+"url:"+imgUrl;
}
return result;
}
private static String parseContentPureTxt(Object object)throws Exception{
String result = "";
Field[] fields = object.getClass().getFields();
if(fields.length>0){
for(Field field:fields){
if(field.getType() == String.class && field.getName().equals(ConfigUtil.wxContentVar[0])){
result = "title:"+field.get(object).toString();
break;
}
}
}
return result;
}
private static String parseContentArticle(Object object)throws Exception{
String result = "";
Field[] fields = object.getClass().getFields();
if(fields.length>0){
for(Field field:fields){
if(field.getType() == String.class && field.getName().equals(ConfigUtil.wxContentVar[0])){
result = "title:"+field.get(object).toString()+";";
}
}
}
}
if(!result.contains("url:")){
result = result + "url:请在微信客户端内打开";
}
return result;
}
private static String parseContentVideo(Object object)throws Exception{
String result = "";
Field[] fields = object.getClass().getFields();
if(fields.length>0){
for(Field field:fields){
if(field.getType() == String.class && field.getName().equals(ConfigUtil.wxContentVar[0])){
result = "title:"+field.get(object).toString()+";";
}
if(field.getType().getName().contains(ConfigUtil.hookRootPackageName)){
Field[] fields1 = field.get(object).getClass().getFields();
a:
for(Field field1:fields1){
}
}
}
}
}
}
return result;
}
}
搞定!!!!!!!!!!!!!!!!!!
部分代码涉及到微信核心,故没有贴全,如需要学习的,可评论。也可+我v:YY_yhzf