1. 程式人生 > >用Xposed框架抓取微信朋友圈資料

用Xposed框架抓取微信朋友圈資料

微信朋友圈為私有協議,從抓包上分析朋友圈資料幾乎不可能,目前也尚未找到開源的抓取朋友圈的指令碼。博主於是嘗試通過使用安卓下的Xposed框架實現從微信安卓版上抓取朋友圈資料。
本文針對微信版本6.3.8。
GitHub倉庫

主要思路

從UI獲取文字資訊是最為簡單的方法,於是應該優先逆向UI程式碼部分。

逆向微信apk

首先解包微信apk,用dex2jar反編譯classes.dex,然後用JD-GUI檢視jar原始碼。
當然,能看到的原始碼都是經過高度混淆的。但是,繼承自安卓重要元件(如Activity、Service等)的類名無法被混淆,於是還是能從中看到點東西。

  1. 首先定位到微信APP package。我們知道這個是com.tencent.mm
  2. com.tencent.mm中,我們找到一個ui包,有點意思。
  3. 展開com.tencent.mm.ui,發現多個未被混淆的類,其中發現MMBaseActivity直接繼承自ActivityMMFragmentActivity繼承自ActionBarActivityMMActivity繼承自MMFragmentActivity,並且MMActivity是微信中大多數Activity的父類:
    12345678910111213 public class MMFragmentActivity extends ActionBarActivity implements SwipeBackLayout
    .a, b.a { ...}public abstract class MMActivity extends MMFragmentActivity { ...}public class MMBaseActivity extends Activity { ...}

現在需要找出朋友圈的Activity,為此要用Xposed hookMMActivity

建立一個Xposed模組

參考[TUTORIAL]Xposed module devlopment,建立一個Xposed專案。
簡單Xposed模組的基本思想是:hook某個APP中的某個方法,從而達到讀寫資料的目的。
小編嘗試hookcom.tencent.mm.ui.MMActivity.setContentView

這個方法,並打印出這個Activity下的全部TextView內容。那麼首先需要遍歷這個Activity下的所有TextView,遍歷ViewGroup的方法參考了SO的以下程式碼:

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(被混淆過的類名),該子類下有個名為gyOListView類的例項。我們知道,ListView是顯示列表類的UI元件,有可能就是用來展示朋友圈的列表。

順藤摸瓜

那麼,我們先要獲得一個SnsTimeLineUI.a.gyO的例項。但是在這之前,要先獲得一個com.tencent.mm.plugin.sns.ui.SnsTimeLineUI.a的例項。繼續搜尋,發現com.tencent.mm.plugin.sns.ui.SnsTimeLineUI有一個名為gLZSnsTimeLineUI.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() { @Override 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上的原始碼。