【轉】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:3.5.4@aar'
關於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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM