AssetHook:Android應用資源資料執行時編輯工具

玄學醬發表於2017-09-18
本文講的是AssetHook:Android應用資源資料執行時編輯工具AssetHook是一個工具,它可以讓Android安全研究人員和普通使用者能夠在無需修改APK本身的情況下隨時修改Android應用程式的部分Asset。這樣的修改使研究人員可以改變嵌入式資料,以更好地評估和測試移動應用程式。目前來看AssetHook比現有方法更容易使用,且比傳統方法更有效。

背景

去年年底,我開始關注Android啟用React Native 後 Facebook的新框架,它將跨平臺移動開發統一到JavaScript,顯然JavaScript是一種不需要介紹的語言。在React Native之前,在JavaScript中構建跨平臺移動應用程式的主要方法是使用PhoneGap(…或者現在是Cordova嗎?甚至不讓我從Ionic開始)。這些都是基於將JavaScript載入到Webview中(UIWebView在iOS上,WebView在Android上)的主要應用程式邏輯。然後,開發人員可以在平臺的“本機”語言中編寫一些平臺特定的程式碼,並將它們與幽靈一般的webview魔術FFI連線在一起。

React Native將這種設計(也可能是理由)一腳踢開,然後顛覆了傳統。它不是使用webview和HTML / CSS進行渲染,它將JavaScript宣告的UI對映到平臺的本地UI工具包,並嵌入WebKit的JavaScriptCore(JSC)庫來執行該JavaScript,完全避免了平臺的Webview實現。JSC支援在沒有JIT的情況下解釋JavaScript,這在iOS上是必需的,因為它的安全模型是基於不允許任何人生成可執行記憶體的(而且人們說OpenBSD的人是Luddites for W ^ X …); 最後我檢查過,V8只是JIT(新的ignition interpreter似乎並沒有改變,因為“ JIT程式碼生成仍然是IC和程式碼存根 ”)。

作為我最初在Android上進行React Native的一部分,我試圖在應用程式中注入額外的JavaScript程式碼,以將開發控制檯REPL載入(並執行janky函式hook)。所以,我在這種情況下做了一個通常的工作,併為Android的android.content.res.AssetManager類寫了一個Xposed函式hook。..之後我再沒有做什麼。

Android使用AssetManager該類作為應用程式的介面來訪問嵌入在其APK中的特定型別的檔案資源(特別是APK中的assets/目錄中)。鑑於我知道(從開放一個釋出版本的React Native應用程式)主要的後處理JavaScript軟體包作為資產儲存,所以hook不起作用是非常奇怪的。然後,我通過React Native的Android程式碼庫開始測試,很快就發現使用C API為Android的資產管理器載入了C ++程式碼,這解釋了為什麼Java函式掛起不起作用。

因為我想要修改這些軟體包的內容,而不需要建立和簽名修改的APK(然後需要更多的hook來欺騙他們的簽名),我設定了構建一個工具來hook這些資源負載。我在C ++中寫了最初的POC,然後在Rust中重寫。

(A)AssetHook

AssetHook是一款LD_PRELOAD用於Android應用程式的函式hook庫。它hook了Android的內部資產管理程式碼,並將資產檔案載入從合法(即簽名的)APK重定向到裝置本地檔案系統上的單獨位置。(A)AssetHook的初始版本“AAssetHook”(兩個“A”)攔截了Android 公共 AAssetManager C API的呼叫,其實際上是用extern “C”C ++編寫的。然而,由於這個API只是一個直接由Java 類使用的Android 內部類的封裝,所以我把它重寫為“AssetHook”(一個`A`)來代替底層的C ++ API。 AssetManagerandroid.content.res.AssetManager

如果沒有像AssetHook這樣的東西的話,替換Android資產將需要解壓APK,替換資產檔案,重新打包APK,可能會重新對齊,簽署新的APK檔案,然後安裝它。這不但是一個繁瑣和緩慢的過程(特別是APK複製到裝置併為更大的應用程式安裝過程),還必須處理新的簽名打破跨應用程式簽名檢查的後果。對於使用自定義許可權,共享使用者ID或自定義IPC訪問控制的應用程式組,此新簽名將阻止修改的應用程式與其他應用程式進行互動或完全不起作用。然後需要額外的hook來欺騙修改後的APK的簽名證照以匹配原來的APK。此外,如果沒有更多根深蒂固和靈活的功能hook基礎架構(例如修改系統分割槽二進位制檔案的Xposed),這種hook在Android上可能是不可能的。使用這種框架的一個關鍵問題是由於需要移植工作,因此必然會延遲支援新的Android版本。

C API Hooking 使用了 LD_PRELOAD

C API hooking變體(“AAssetHook”)是由vanilla的 LD_PRELOADhook來實現的,它宣告瞭一些重複的函式符號,儘管動態連結的魔力 – 在從其二進位制檔案外部訪問時重寫對這些函式的呼叫。然後在dlopen(3)/ dlsym(3)(在Unix上)使用一個Rust包裝庫來根據需要代理對原始函式的呼叫。之後,它會檢查一個APK中的給定檔案路徑是否與裝置檔案系統上的現有路徑匹配,並欺騙C API返回磁碟檔案的檔案內容,而不是in-

C++ API Hooking 使用了 LD_PRELOAD

由於資產管理器的C ++實現不是公共API,C ++ APIhook變體(“AssetHook”)稍微複雜一點,這在很大程度上依賴於多型性,並且在較小的Android版本中可能會發生變化。當在C ++中使用多型時,通常將通過動態排程處理虛擬方法呼叫。在大多數“平穩”平臺上,通過將虛擬表(vtable)指標作為類的記憶體結構的第一個元素之一來實現。該指標在構造期間設定,並指向一個填充有該類使用的特定虛擬方法的函式指標的表。LD_PRELOAD在這種情況下,函式hook非常有限,因為它只能掛接匯出符號的二進位制函式呼叫,而動態排程呼叫則使用直接函式指標。這樣可以避免基於vtable的呼叫被LD_PRELOAD符號覆蓋直接掛起。此外,這些函式指標的順序很可能會在次級類定義更改時發生更改。在不同的編譯器之間也是不一致的,通常可能難以推斷出多個版本的二進位制檔案。

注意:這完全取決於編譯器,但是這一點在Unix上的Clang和GCC(至少對於x86 / amd64和ARM / AArch64來說都是一致的)。

例如,下面的程式碼片段的物件可以被佈置,如下圖所示:

#include <stdio.h>struct Base {
  virtual void foo() {
    puts("base!");
  }
  size_t a = (size_t)-};struct Derived : public Base {
  virtual void foo() {
    puts("derived!");
  }
  size_t b = (size_t)-};struct DDerived : public Derived {
  void foo() final override {
    puts("dderived!");
  }
  size_t c = (size_t)-};void call_foo(Base& br.foo();}int main() {
  Base b;
  Derived d;
  DDerived dd;
  call_foo(b);
  call_foo(d);
  call_foo(dd);
  return 0;}

 AssetHook:Android應用資源資料執行時編輯工具

功能搜尋和vtable插槽對映

處理vtable排序的波動的一種方法是將給定的類“vtable”與其二進位制檔案的符號表進行交叉引用(用binutils的GPL bfd.h或libelfin解析)。使用手中的函式地址,可以對vtable進行交叉引用以找到其偏移量。然而,這可能不起作用,因為一些虛擬方法函式可能沒有符號,就像Android資產管理器的C ++實現一樣。另一種方法是掃描這種不符號的函式的原始位元組,但這不太可能跨越多個二進位制版本。 

事實上,這是完全可能的(使用類似Capstone的東西)去解析呼叫目標類的虛擬方法並提取這些方法的vtable偏移量的符號符號函式的彙編。

vtable Slot Knocking

AssetHook的C ++ APIhook實現使用了最後一種方法的一個變體,依靠這樣一個事實,即將虛擬方法呼叫的C API本身就是具有ABI穩定性保證的公共API。AssetHook不是分析C API函式的指令來剔除vtable偏移量,而是建立一個假的C ++物件,並使用它來配置vtable。該物件vtable指標指向一個虛構的vtable,其中包含一系列報告呼叫順序的函式指標。AssetHook將該物件作為嵌入到void*掩碼包裝結構體中的指標傳遞給C API,然後C API通過編譯器提供的偏移量呼叫關聯的虛擬方法。執行此呼叫時,將觸發嵌入式函式指標,暴露給定操作的vtable偏移量。這允許通過呼叫這個假的物件上已知的C API函式來逐個獲取實際的vtable順序,以獲得它們包裝的C ++方法的vtable偏移量。

注意:對於特殊的涉及 thunk的 “複雜”類,需要更加動態地分析vtable,因為類別的vtable段中的“正常”成員函式指標實際上是由編譯器生成的包裝函式。該函式相應地移動this指標,並從實際類的更大的vtable中的其他地方呼叫“真實”成員函式指標。

hook虛擬方法

之後,在返回物件指標LD_PRELOAD的AssetManager類的“公共”非虛擬方法的變形符號名稱上建立一個型別的hookAsset。這個hook確定檔案是否應該被hooking,如果是這樣,返回一個指向一個修改後的Asset物件的指標,其中一個vtable包含了函式hook。

例子:

在這個例子中,我們將使用AssetHook將我們自己的JavaScript檔案交換到Tic-tac-toe示例應用程式中(參考React Native文件,我必須傳遞–dev false給react-native build它來使其大部分應用程式成為最小化的)。我們假設您按照檔案中的描述安裝了它。

1、安裝一個“release”版本的應用程式(為了儘可能的縮小,AssetHook可以同時釋出和除錯Android APK版本)在一個根深蒂固的測試裝置上(AssetHook已知可以與Android 5.x,6.x和7.x一起用32位和64位ARM,以及32位x86上的Android O Developer Preview)。

2、從APK檔案中提取assets/index.android.bundle(從嵌入的Android來源app/build目錄或使用該設定adb shell pm list packages -f | grep <pkg>和adb pull)。

3、修改檔案並注入JavaScript alert(…)。

4、執行以下命令:

$ adb push path/to/index.android.bundle /data/local/tmp/assethook/<pkg>/assets/
$ adb shell su -c `setprop wrap.<pkg> LD_PRELOAD=/data/local/tmp/lib/libassethook_cppapi.so`

注意: React Native使用32位二進位制檔案,甚至在64位Android上。因此,32位模式下程式載入,我們需要使用32位版本的AssetHook。

5、啟動應用程式(如果已經開啟,請先關閉它)。

AssetHook:Android應用資源資料執行時編輯工具

未來的工作

主要優先事項是在執行模式下完全啟用SEAndroid時支援Android。現在,AssetHook要求將SELinux置於允許模式,因為替換檔案存在於Google試圖通過SELinux限制訪問現代Android版本的共享臨時目錄中。我正在尋求支援從應用程式自己的內部目錄載入替換資產檔案,但這可能需要額外的工具來將檔案上傳到裝置上。

原文釋出時間為:2017年6月3日
本文作者:Change
本文來自雲棲社群合作伙伴嘶吼,瞭解相關資訊可以關注嘶吼網站。


相關文章