WeChat bot, real-time access to friends and group messages, pull Moments data

        At the end of 19, when I was bored, I studied the WeChat robots and found that it was not very difficult. At that time, it mainly realized the real-time acquisition of friends and group messages, and pulled the circle of friends data from the WeChat local database. It is not difficult to obtain data in Moments. The difficult thing is to analyze the data, because the data is encrypted and stored. It was done for several days at the time, and then finally got it done. Now let’s share the process.

1. Prepare the relevant environment

        The hook process is all done on the phone. If the phone has been rooted, you can install Xposed directly. If you have not been rooted, you can install VirtualXposed (hereinafter referred to as VXP). VXP is a virtual root environment for mobile phones. For an unrooted mobile phone, a virtual root environment is provided, and related applications are installed in VXP. Realize related operations that require root privileges. You can download and install VXP on GIT, install 7.0.5 on WeChat, and log in to your WeChat account.

2. Create a new APP project and configure xposed

        Open the studio to create a new project, and configure the hook information of Xposed, as follows:

  • Configure hook entry

        Create a new folder under src.main: assets, then create a file named xposed_init under assets, and put your hook home directory in this file, mine is like this:

com.android.com.yh.gj.xp.XpMain

         This is the entrance to tell xposed to hook.

  • Configure AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.android.com.yh.gj">
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <meta-data
            android:name="xposedmodule"
            android:value="true" />
        <meta-data
            android:name="xposeddescription"
            android:value="获取联系人" />
        <meta-data
            android:name="xposedminversion"
            android:value="82" />
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

        The important thing is the type in the meta-data

  • Configure build-gradle

        Introduce xposed related packages compileOnly'de.robv.android.xposed:api:82' compileOnly'de.robv.android.xposed:api:82:sources'

In this way, the basic configuration is complete.

3. Hook process

        For brute force, directly hook the WeChat storage point. After the message is received, it must be stored in the local library. When the storage point is hooked, the message information can be obtained. In addition, get the library password, so that you can pull the information of the circle of friends from the library

4. Hook the password and library file of the local WeChat database

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

The above code can directly get the password and library file path of WeChat's local database, and everything is easy to handle with the password.

5. Hook new news

    /**
     * 消息监控
     * @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 will be called when there is new news. The implementation of WxMsgMonitor is as follows:

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

Okay, even if the above process of getting news in real time is complete, let’s talk about pulling Moments data from the database.

6. Data acquisition in Moments of Friends

The password and file location of the WeChat local database have been obtained above, let's copy the library file first:

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

Then read the content inside:

 List<Map> list = new ArrayList<Map>();
        Map map;
        SQLiteDatabaseHook hook = new SQLiteDatabaseHook() {
            @Override
            public void preKey(SQLiteDatabase database) {}
            @Override
            public void postKey(SQLiteDatabase database) {
                database.rawExecSQL("PRAGMA cipher_migrate;");//兼容2.0的
            }
        };
        android.database.sqlite.SQLiteDatabase db = android.database.sqlite.SQLiteDatabase.openDatabase(ConfigUtil.copySnsDbPath,null,0);
        Cursor cursor = db.rawQuery("select snsId,userName,strftime('%Y-%m-%d %H:%M:%S',datetime(createTime, 'unixepoch')) createTime,type,content from SnsInfo order by createTime desc limit "+row+" offset 0",null);
        for(int i =0;i<cursor.getColumnCount();i++){//要取哪些字段,自行在这里打印了查看字段名
//            log("cName:"+cursor.getColumnName(i));
        }
        if(cursor.getCount()>0){
            while(cursor.moveToNext()) {
                map = new HashMap();
                Long snsId = cursor.getLong(cursor.getColumnIndex("snsId"));
                String userName = cursor.getString(cursor.getColumnIndex("userName"));
                String createTime = cursor.getString(cursor.getColumnIndex("createTime"));
                String type = cursor.getString(cursor.getColumnIndex("type"));
                byte[] content = cursor.getBlob(cursor.getColumnIndex("content"));
                map.put("snsId",snsId);
                map.put("userName",userName);
                map.put("createTime", createTime);
                map.put("type",type);
                map.put("content", ParseUtil.parseContent(content,context,type));
                list.add(map);
            }
        }
        return list;
    }

In this way, the data inside is obtained. But you will find that the data of content is messy, so it needs to be parsed here. This step took a few days to finally get it (mainly through reflection). The analysis is as follows:

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

Get it done! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! !

Part of the code is related to the core of WeChat, so it is not posted completely. If you need to learn, you can comment. Also +我v:YY_yhzf

Guess you like

Origin blog.csdn.net/nanxiaotiantian/article/details/105282422