2015年6月23日 星期二

Xposed Module 開發教學技巧篇 - 以 MoPTT 為例

Part 1. Xposed Module 開發教學 - 以 MoPTT 為例
Part 2. Xposed Module 開發教學技巧篇 - 以 MoPTT 為例
Part 3. Xposed Module 技巧教學 - 偵測模組啟用 

上篇礙於篇幅,只說明如何停用 MoPTT 的文字過濾,而此篇則講解
如何透過 Xposed Module 修改 MoPTT 發文的簽名檔功能。

由於此功能和上篇相同都是針對 MoPTT 做修改,因此不另外建立一個
專案,使用同一個專案來進行開發。

[ Step 1 - 建立 UI ]

首先,由於可以自訂簽名檔,所以必須要有一個 UI,在此就建立一個
Activity 介面如下:





簡單來說,就是提供選項讓使用者決定是否使用啟用這些功能,以及自訂簽
名檔內容。 按下儲存後才會套用設定。

UI 的部分這邊就不多做說明了,資料儲存在 SharedPreference 中,定義如下:
  • Boolean "stopTextFilter"  儲存是否停用文字過濾
  • Boolean "customSign" 儲存是否使用自訂簽名檔
  • String "signature" 儲存自訂的簽名檔內容

但是要注意,由於 Xposed Module 在運作時,並非 MoTweaker 自己存取這個
preference 檔案,因此建立時必須宣告為 WORLD_READABLE:

  prefs=getSharedPreferences( "prefs" , Context.MODE_WORLD_READABLE );

儲存設定、讀取設定、UI 調整... 等部分,這邊就不再多做說明,請自行完成。


[Step 2 - 在 Module 核心讀取設定檔 ]

由於在 Module 核心 class 是各 App 載入時呼叫的,因此其檔案讀取權限是該 App,
以這邊為例子,也就是 MoPTT 這個 App,Android 是不讓 App (MoPTT)讀取另
一個 App (MoTweaker)中的檔案的,除非將權限開放,這也就是為何我們在上面
要將 SharedPreference 設定為 World Readable 的緣故。

另外在 Module 載入階段無法取得 Context , 因此不能使用 getSharedPreferences
來取得 SharedPreferences 物件,因此這邊要透過 Xposed Bridge 提供的
XSharedPreferences 來存取設定檔。

使用方式可以直接看 XposedBridgeApi.jar 中的 source code,這邊只提我要使用
到的方法:
  // 宣告
  private static String MyPkgName = MoTweaker.class.getPackage().getName();
  private static XSharedPreferences prefs = null ;

  // 建立 prefs 物件
  prefs = new XSharedPreferences(MyPkgName, "prefs");

由於在 Activity 中透過 getSharedPreferences( name , mode); 建立的 prefs 檔案
會儲存於: /data/data/PACKAGE_NAME/shared_prefs/name.xml

而透過 new XSharedPreferences( PACKAGE_NAME, name ); 的方式來建立
XSharedPreferences 物件就可以存取到該檔案。(可以參考 Xposed 中的原始碼)

經過多次測試以及上網搜尋的結果,大多數人建議使用底下
方式使用 XSharedPreferences:
  1. 宣告一 static XSharedPreferences 變數
  2. 實作 IXposedHookZygoteInit 
  3. 在 initZygote(StartupParam param) 中初始化 XSharedPreferences 物件
也就是將程式碼變成如下:


註:XSharedPreferences 的第二個參數給 "prefs" 是因為我們在 Activity 中
  呼叫 getSharedPrefeneces 時的  name 是 "prefs"


XSharedPrefs建立完成後,只要在讀取設定之前,呼叫 prefs.reload(),
它會自動檢查設定是否有更新(透過取得 lastModifyTime),決定是否重新讀取
xml 設定檔案。( 可以參考 XSharedPrefs 的 Source Code )

因此我們將上一篇文章提到的程式碼加入讀取設定的部分:



[ Step 3 - 自訂簽名檔 ]

MoPTT 預設簽名檔是 Sent from my Android,因此我們在 JD-GUI 裡搜尋這個字串,
可以找到在  PostEditActivity 有出現這段字串:

稍微追蹤一下可以發現,整個程式裡面,只有這邊會指派 editingPost.Signature 的值,
而也只有在 EditingPost 這個 Class 的 getContentWithSignature() 方法有使用到它。
 

記得之前說過, Xposed 無法對 Method 內部做修改,上上圖所示的地方是
PostEditActivity 的一個 b(EditingPost) 方法,由於 editingPost.Signature 的
值是在方法中被指定的,因此我們無法對其做修改。

但程式中,只有 editingPost.getContentWithSignature() 有取出這個值,因此我們
可以對這一個 method 做 hook。

這邊有兩種做法,都是 hook 這個 getContentWithSignature()

方法一、
    由於這個 method 內容很簡單,因此可以直接在 beforeHookedMethod 傳回
    我們修改過的簽名檔。

方法二、
    在 beforeHookedMethod 時,將 Signature 變數改成我們自訂的簽名檔,呼叫完
    這個 method 後,再把簽名檔還原。( 其實在這個程式裡,甚至還可以不用還原 )

如果這個 Method 內容簡單,我們可以輕易地在 beforeHookedMethod 中複製出來,
通常可採用第一種方法;若複雜,通常建議採用第二個方法。

但無論是哪種方法,都必須存取 EditingPost 這個 class 中的實體變數,這邊要稍微
有點 reflection  的概念。

在 Reflection 中,假設我們要存取 EditingPost 中,名為 Signature 的 field,我們要
這樣寫:


而在 beforeHookedMethod 傳入的參數 MethodHookParam 中,有一個 thisObject
的 field,正是我們所要的東西。
methodHookParam.thisObject 代表在被 hook 的這個 class 中,this 所指的物件

不過 Xposed 幫我們寫好了幾個方便存取的方法,我們可以直接使用,不用
再自己撰寫繁複的 Reflection Code:
XposedHelpers.getObjectField(param.thisObject, "Signature")
只要傳入 thisObject 以及 field 名稱,就可以取得 editingPost.Signature 的值。

底下先將 hook 這個 getContentWithSignature 方法的部分加入:


由於 getContentWithSignature 沒有參數,因此方法名稱後直接放 XC_MethodHook 的
Callback 物件 ( mCBGetContentWithSign )。

最後在 initCallBack 中,新增 mCBGetContentWithSign 的內容:



底下分為方法一和方法二的方式,對照看一下應該能夠體會箇中的差異:

方法一:


方法二:


到此就大功告成了。


底下提供這個 Xposed Module 的原始碼(僅 .java )以及未簽章過的 apk 檔案
給大家參考,若有興趣可自行簽章後安裝。

點我下載  MoTweaker.rar

PS : 不知道 Xposed for Lollipop 解決 XSharedPrefs 的問題了沒,所以 5.0 以上的不一定能正常運作

沒有留言:

張貼留言