[Flutter翻譯]通過重新編譯Flutter引擎對Flutter應用進行逆向工程。

Sunbreak發表於2021-03-15

原文地址:tinyhack.com/2021/03/07/…

原文作者:tinyhack.com

釋出時間:2021年3月7日

要對一個flutter應用的釋出版本進行逆向工程並不容易,因為沒有工具,而且flutter引擎本身變化很快。截至目前,如果你很幸運,如果一個flutter應用是用特定版本的Flutter SDK構建的,你可以使用darterDoldrums轉儲該應用的類和方法名。

如果你非常幸運,這就是我第一次需要測試Flutter App時發生的事情:你甚至不需要對App進行反向工程。如果應用非常簡單,使用簡單的HTTPS連線,你可以使用攔截代理(如Burp或Zed Attack Proxy)來測試所有功能。我剛剛測試的應用在HTTPS的基礎上使用了額外的一層加密,這也是我需要進行實際逆向工程的原因。

在這篇文章中,我將只給出Android平臺的例子,但這裡寫的所有內容都是通用的,適用於其他平臺。TLDR是:我們不更新或建立快照解析器,而只是重新編譯flutter引擎,並將其替換到我們的目標應用中。

Flutter編譯後的應用

目前我找到的關於Flutter逆向工程的幾篇文章和資料庫是。

主要程式碼由兩個庫libflutter.so(flutter引擎)和libapp.so(你的程式碼)組成。你可能會好奇:如果你試圖用標準的反彙編器開啟一個libapp.so(被AOT編譯的Dart程式碼),實際上會發生什麼。這只是原生程式碼,對嗎?如果你使用IDA,最初,你只會看到這一堆位元組。

image.png

如果你使用其他工具,比如二進位制ninja會嘗試做一些線性掃描,你可以看到很多方法。所有的方法都沒有命名,也沒有你能找到的字串引用。也沒有外部函式的引用(無論是libc還是其他庫),也沒有直接呼叫核心的syscall(比如Go)。

用Darter dan Doldrums這樣的工具,你可以轉儲類名和方法名,你可以找到函式的實現地址。下面是一個使用Doldrums進行轉儲的例子。這對反轉應用有極大的幫助。你也可以使用Frida在這些地址處進行鉤子,轉儲記憶體或方法引數。

image.png

快照格式問題

特定的工具只能轉儲特定版本的快照,原因是:快照格式不穩定,它是為了特定版本的執行而設計的。與其他一些格式可以跳過未知或不支援的格式不同,快照格式是非常不寬容的。如果你不能解析一個部分,你可以解析下一個部分。

基本上,快照格式是這樣的。<tag> <data bytes> <tag> <data bytes> ......每一個分塊都沒有明確的長度,標籤的頭也沒有特定的格式(所以你不能只做一個模式匹配就想知道一個分塊的開始)。一切都只是數字。除了原始碼本身,沒有任何關於這個快照的文件。

事實上,這種格式甚至沒有版本號。這個格式是由快照版本串來標識的。版本字串是由快照相關檔案的原始碼雜湊生成的。我們假設如果檔案被改變,那麼格式也會被改變。這在大多數情況下是正確的,但並不總是如此(例如:如果你編輯了一條評論,快照版本串就會改變)。

我最初的想法只是通過檢視Dart原始碼的diff來修改Doldrums或Darter到我需要的版本。但事實證明,這並不容易:中間有時會插入enums(意味著我需要將所有常量移位一個數字)。而且dart還使用了大量的位操作,使用C++模板。例如,當我看Doldums的程式碼時,我看到了這樣的東西。

def decodeTypeBits(value):
       return value &amp; 0x7f
複製程式碼

dart.googlesource.com/sdk//+/2beb… 我想我可以快速檢查一下程式碼中的這個常量(新版本中是否有變化),型別原來不是一個簡單的整數。

class ObjectPool : public Object {
 using TypeBits = compiler::ObjectPoolBuilderEntry::TypeBits;
}
struct ObjectPoolBuilderEntry {
  using TypeBits = BitField<uint8_t, EntryType, 0, 7>;
}
複製程式碼

你可以看到這個Bitfield是作為BitField模板類來實現的。這個特殊的位很容易讀懂,但是如果你看到kNextBit,你需要回溯之前所有的位定義。我知道對於經驗豐富的C++開發者來說,這並不難,但還是:要跟蹤這些版本之間的變化,你需要做大量的手工檢查。

我的結論是。我不想維護Python程式碼,下次更新應用進行復測的時候,他們可以使用較新版本的Flutter SDK,再來一個快照版本。而對於我正在做的具體工作。我需要用兩個不同的Flutter版本測試兩個應用:一個是已經在應用商店釋出的東西,另一個是即將釋出的應用。

重建Flutter引擎

flutter引擎(libflutter.so)是一個獨立於libapp.so(主應用邏輯程式碼)的庫,在iOS上,這是一個獨立的框架。思路非常簡單。

  • 下載我們想要的引擎版本
  • 修改它來列印類名、方法等,而不是編寫我們自己的快照分析器。
  • 用我們的補丁版本替換原來的libflutter.so庫。
  • 盈利

第一步已經很困難了:如何才能找到相應的快照版本?darter的這張表可以幫助我們,但是沒有更新最新的版本。對於其他版本,我們需要獵取並測試它是否有匹配的快照號。這裡有重新編譯Flutter引擎的指令,但是編譯過程中出現了一些小插曲,我們需要修改快照版本的python指令碼。還有:Dart內部本身就不是那麼容易操作的。

我測試過的大部分舊版本都不能正確編譯。你需要編輯DEPS檔案來讓它編譯。在我的情況下:差異很小,但我需要在網上搜到這個。不知何故,特定的提交不可用,我需要使用不同的版本。注意:不要盲目應用這個補丁,基本上要檢查這兩點。

  • 如果一個提交沒有了,就找離釋出日期最近的一個。
  • 如果某件事情指的是_internal的,你可能應該把_internal的部分去掉。
diff --git a/DEPS b/DEPS
index e173af55a..54ee961ec 100644
--- a/DEPS
+++ b/DEPS
@@ -196,7 +196,7 @@ deps = {
    Var('dart_git') + '/dartdoc.git@b039e21a7226b61ca2de7bd6c7a07fc77d4f64a9',
 
   'src/third_party/dart/third_party/pkg/ffi':
-   Var('dart_git') + '/ffi.git@454ab0f9ea6bd06942a983238d8a6818b1357edb',
+   Var('dart_git') + '/ffi.git@5a3b3f64b30c3eaf293a06ddd967f86fd60cb0f6',
 
   'src/third_party/dart/third_party/pkg/fixnum':
    Var('dart_git') + '/fixnum.git@16d3890c6dc82ca629659da1934e412292508bba',
@@ -468,7 +468,7 @@ deps = {
   'src/third_party/android_tools/sdk/licenses': {
      'packages': [
        {
-        'package': 'flutter_internal/android/sdk/licenses',
+        'package': 'flutter/android/sdk/licenses',
         'version': 'latest',
        }
      ],
複製程式碼

現在我們可以開始編輯快照檔案,瞭解它的工作原理。但是正如前面提到的:如果我們修改快照檔案:快照雜湊值就會改變,所以我們需要在third_party/dart/tools/make_version.py中返回一個靜態版本號來解決這個問題。如果你碰了VM_SNAPSHOT_FILES中的任何一個檔案,請用靜態字串將snapshot_hash = MakeSnapshotHashString()這一行改為你的特定版本。

image.png

如果我們不給版本打補丁會怎麼樣呢,應用無法啟動。所以,在使用OS::PrintErr("Hello World")打上補丁(就從列印hello world開始)並重新編譯程式碼後,我們可以測試替換.so檔案,然後執行它。

我做了很多實驗(比如嘗試FORCE_INCLUDE_DISASSEMBLER),所以我沒有一個乾淨的修改方法分享給大家,但我可以提供一些修改的提示。

  • 在runtime/vm/clustered_snapshot.cc中,我們可以修改Deserializer::ReadProgramSnapshot(ObjectStore* object_store)來列印類表隔離->class_table()->Print()
  • 在runtime/vm/class_table.cc中,我們可以修改void ClassTable::Print()來列印更多的資訊。

例如,列印函式名稱。

const Array&amp; funcs = Array::Handle(cls.functions());  
 for (intptr_t j = 0; j < funcs.Length(); j++) {
      Function&amp; func = Function::Handle();
      func = cls.FunctionFromIndex(j);
      OS::PrintErr("Function: %s", func.ToCString());
}
複製程式碼

旁註:SSL證照

Flutter應用的另一個問題是:它不會信任使用者安裝的root cert。這對於pentesting來說是一個問題,有人做了一個關於如何給二進位制打補丁的說明(直接或使用Frida)來解決這個問題。引用這個博文的TLDR。

  • Flutter使用的是Dart,而Dart並沒有使用系統的CA商店
  • Dart使用的CA列表被編譯到應用程式中。
  • Dart在安卓系統上是沒有代理意識的,所以使用ProxyDroid與iptables。
  • 勾選 x509.cc 中的 session_verify_cert_chain 函式來禁用鏈式驗證。

通過重新編譯Flutter引擎,可以很容易地實現這一點。我們只需按原樣修改原始碼(third_party/boringssl/src/ssl/handshake.cc),而不需要在編譯後的程式碼中找到彙編位元組模式。

混淆Flutter

使用這裡提供的說明可以混淆Flutter/Dart應用程式。這將使反轉變得有點困難。請注意,只有名稱被混淆,沒有執行高階控制流混淆。

結束語

我很懶,重新編譯flutter引擎是我採取的捷徑,而不是寫一個合適的快照解析器。當然,其他人在反轉其他技術時,也有類似的想法,即黑掉執行時引擎,例如,要反轉一個混淆的PHP指令碼,可以用PHP模組鉤住eval。


通過www.DeepL.com/Translator(免費版)翻譯

相關文章