用Xposed框架抓取微信朋友圈資料
因微信朋友圈為私有協議,從抓包上分析朋友圈資料幾乎不可能,目前也尚未找到開源的抓取朋友圈的指令碼。博主於是嘗試通過使用安卓下的Xposed框架實現從微信安卓版上抓取朋友圈資料。
本文針對微信版本6.3.8。
GitHub倉庫
主要思路
從UI獲取文字資訊是最為簡單的方法,於是應該優先逆向UI程式碼部分。
逆向微信apk
首先解包微信apk,用dex2jar反編譯classes.dex,然後用JD-GUI檢視jar原始碼。
當然,能看到的原始碼都是經過高度混淆的。但是,繼承自安卓重要元件(如Activity、Service等)的類名無法被混淆,於是還是能從中看到點東西。
-
首先定位到微信APP package。我們知道這個是
com.tencent.mm
-
在
com.tencent.mm
中,我們找到一個ui
包,有點意思。 -
展開
com.tencent.mm.ui
,發現多個未被混淆的類,其中發現MMBaseActivity
直接繼承自Activity
,MMFragmentActivity
繼承自ActionBarActivity
,MMActivity
繼承自MMFragmentActivity
,並且MMActivity
是微信中大多數Activity的父類:12345678910111213 public class MMFragmentActivity extends ActionBarActivity implements SwipeBackLayout
現在需要找出朋友圈的Activity,為此要用Xposed hookMMActivity
。
建立一個Xposed模組
參考[TUTORIAL]Xposed
module devlopment,建立一個Xposed專案。
簡單Xposed模組的基本思想是:hook某個APP中的某個方法,從而達到讀寫資料的目的。
小編嘗試hookcom.tencent.mm.ui.MMActivity.setContentView
1234567891011 | private void getAllTextViews(final View v) { if (v instanceof ViewGroup) { ViewGroup vg = (ViewGroup) v; for (int i = 0; i < vg.getChildCount(); i++) { View child = vg.getChildAt(i); getAllTextViews(child); } } else if (v instanceof TextView ) { dealWithTextView((TextView)v); //dealWithTextView(TextView tv)方法:列印TextView中的顯示文字 }} |
HookMMActivity.setContentView
的關鍵程式碼如下:
123 | findAndHookMethod("com.tencent.mm.ui.MMActivity", lpparam.classLoader, "setContentView", View.class, new XC_MethodHook() { ...}); |
在findAndHookMethod方法中,第一個引數為完整類名,第三個引數為需要hook的方法名,其後若干個引數分別對應該方法的各形參型別。在這裡,Activity.setContentView(View
view)
方法只有一個型別為View
的形參,因此傳入一個View.class
。
現在,期望的結果是執行時可以從Log中讀取到每個Activity中的所有的TextView的顯示內容。
但是,因為View中的資料並不一定在setContentView()
時就載入完畢,因此小編的實驗結果是,log中啥都沒有。
意外的收穫
當切換到朋友圈頁面時,Xposed模組報了一個異常,異常源從com.tencent.mm.plugin.sns.ui.SnsTimeLineUI
這個類捕捉到。從類名上看,這個很有可能是朋友圈首頁的UI類。展開這個類,發現更多有趣的東西:
這個類下有個子類a
(被混淆過的類名),該子類下有個名為gyO
的ListView
類的例項。我們知道,ListView
是顯示列表類的UI元件,有可能就是用來展示朋友圈的列表。
順藤摸瓜
那麼,我們先要獲得一個SnsTimeLineUI.a.gyO
的例項。但是在這之前,要先獲得一個com.tencent.mm.plugin.sns.ui.SnsTimeLineUI.a
的例項。繼續搜尋,發現com.tencent.mm.plugin.sns.ui.SnsTimeLineUI
有一個名為gLZ
的SnsTimeLineUI.a
例項,那麼我們先取得這個例項。
經過測試,com.tencent.mm.plugin.sns.ui.SnsTimeLineUI.a(boolean,
boolean, String, boolean)
這個方法在每次初始化微信介面的時候都會被呼叫。因此我們將hook這個方法,並從中取得gLZ
。
1234567891011121314151617 | findAndHookMethod("com.tencent.mm.plugin.sns.ui.SnsTimeLineUI", lpparam.classLoader, "a", boolean.class, boolean.class, String.class, boolean.class, new XC_MethodHook() { protected void afterHookedMethod(MethodHookParam param) throws Throwable { XposedBridge.log("Hooked. "); Object currentObject = param.thisObject; for (Field field : currentObject.getClass().getDeclaredFields()) { //遍歷類成員 field.setAccessible(true); Object value = field.get(currentObject); if (field.getName().equals("gLZ")) { XposedBridge.log("Child A found."); childA = value; //這裡獲得了gLZ ... } } }}); |
現在取得了SnsTimeLineUI.a
的一個例項gLZ
,需要取得這個類下的ListView
型別的gyO
屬性。
123456789101112131415161718 | private void dealWithA() throws Throwable{ if (childA == null) { return; } for (Field field : childA.getClass().getDeclaredFields()) { //遍歷屬性 field.setAccessible(true); Object value = field.get(childA); if (field.getName().equals("gyO")) { //取得了gyO ViewGroup vg = (ListView)value; for (int i = 0; i < vg.getChildCount(); i++) { //遍歷這個ListView的每一個子View ... View child = vg.getChildAt(i); getAllTextViews(child); //這裡呼叫上文的getAllTextViews()方法,每一個子View裡的所有TextView的文字 ... } } }} |
現在已經可以將朋友圈頁面中的全部文字資訊打印出來了。我們需要根據TextView的子類名判斷這些文字是朋友圈內容、好友暱稱、點贊或評論等。
12345678910111213141516171819202122232425 | private void dealWithTextView(TextView v) { String className = v.getClass().getName(); String text = ((TextView)v).getText().toString().trim().replaceAll("\n", " "); if (!v.isShown()) return; if (text.equals("")) return; if (className.equals("com.tencent.mm.plugin.sns.ui.AsyncTextView")) { //好友暱稱 ... } else if (className.equals("com.tencent.mm.plugin.sns.ui.SnsTextView")) { //朋友圈文字內容 ... } else if (className.equals("com.tencent.mm.plugin.sns.ui.MaskTextView")) { if (!text.contains(":")) { //點贊 ... } else { //評論 ... } } } |
自此,我們已經從微信APP裡取得了朋友圈資料。當然,這部分抓取程式碼需要定時執行。因為從ListView
中抓到的資料只有當前顯示在螢幕上的可見部分,為此需要每隔很短一段時間再次執行,讓使用者在下滑載入的過程中抓取更多資料。
剩下的就是資料分類處理和格式化輸出到檔案,受本文篇幅所限不再贅述,詳細實現可參考作者GitHub上的原始碼。