WeChatボット、友達やグループメッセージへのリアルタイムアクセス、モーメントデータのプル

        退屈していた19年の終わりに、WeChatロボットを調べてみると、それほど難しくはないことがわかりました。当時は、主に友達やグループメッセージのリアルタイム取得を実現し、友達の輪を引っ張っていました。 WeChatローカルデータベースから。モーメントでデータを取得することは難しくありません。データは暗号化されて保存されているため、データを分析することは困難です。それはその時点で数日間行われ、最終的に完了しました。それではプロセスを共有しましょう。

1.関連する環境を準備します

        フックプロセスはすべて電話で行われます。電話機がルート化されている場合は、Xposedを直接インストールできます。ルート化されていない場合は、VirtualXposed(以下、VXPと呼びます)をインストールできます。VXPは、携帯電話の仮想ルート環境です。ルート化されていない携帯電話の場合、ルート化されています。環境は仮想であり、関連するアプリケーションはVXPにインストールされます。root権限を必要とする関連する操作を実現します。GITにVXPをダウンロードしてインストールし、WeChatに7.0.5をインストールして、WeChatアカウントにログインできます。

2.新しいAPPプロジェクトを作成し、xposedを構成します

        スタジオを開いて新しいプロジェクトを作成し、次のようにXposedのフック情報を構成します。

  • フックエントリを構成する

        src.main:assetsの下に新しいフォルダーを作成し、assetsの下にxposed_initという名前のファイルを作成し、フックのホームディレクトリをこのファイルに配置します。私のものは次のようになります。

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

         これは、xposedにフックするように指示するための入り口です。

  • 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>

        重要なのはメタデータのタイプです

  • build-gradleを構成する

        xposed関連パッケージを紹介しますcompileOnly'de.robv.android.xposed:api:82 'compileOnly'de.robv.android.xposed:api:82:sources'

このようにして、基本的な構成が完了します。

3.フックプロセス

        ブルートフォースの場合は、WeChatストレージポイントを直接フックします。メッセージを受信したら、ローカルライブラリに保存する必要があります。ストレージポイントをフックすると、メッセージ情報を取得できます。さらに、図書館のパスワードを取得して、図書館から友達の輪の情報を引き出すことができるようにします

4.ローカルWeChatデータベースのパスワードとライブラリファイルをフックします

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

上記のコードは、WeChatのローカルデータベースのパスワードとライブラリファイルパスを直接取得でき、すべてがパスワードで簡単に処理できます。

5.新しいニュースをフックします

    /**
     * 消息监控
     * @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");
//        }
    }
}

さて、リアルタイムでニュースを取得する上記のプロセスが完了したとしても、データベースからモーメントデータをプルすることについて話しましょう。

6.友人の瞬間のデータ収集

WeChatローカルデータベースのパスワードとファイルの場所は上記で取得されています。最初にライブラリファイルをコピーしましょう。

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

このようにして、内部のデータが取得されます。ただし、コンテンツのデータが乱雑であることがわかるため、ここで解析する必要があります。このステップは、最終的にそれを取得するのに数日かかりました(主にリフレクションを通じて)。分析は次のとおりです。

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

終わらせる!

コードの一部はWeChatのコアに関連しているため、完全には投稿されていません。学習する必要がある場合は、コメントできます。また+我v:YY_yhzf

おすすめ

転載: blog.csdn.net/nanxiaotiantian/article/details/105282422