<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    Flutter APP逆向實踐

    VSole2022-07-21 16:20:09

    很長一段時間對于Flutter的app逆向都比較頭疼,它不像純Java app那樣可以使用jadx-gui看源碼,也不能像原生native那樣可以用ida看字符串交叉引用。分析flutter的時候,沒有交叉引用,那真是想它他拖進回收站。

    最近又遇到了幾個flutter開發的app,對它進行了一些研究,總結了以下的分析流程。

    本文以iOS app為例子作為講解,Android 的flutter app和iOS 的flutter app分析方法類似。

    簡述流程

    抓包,使用frida-ios-dump進行砸殼后,得到目標app的ipa文件,reFlutter對ipa重打包后得到release.RE.ipa,使用ios-app-signer對release.RE.ipa進行自簽名,安裝到手機上并運行,得到dump.dart文件,根據dump.dart里的類名、函數名、函數相對_kDartIsolateSnapshotInstructions的偏移,配合ida靜態分析+frida動態分析,分析出加密算法。

    前期準備

    2.1 抓包,確定需要分析的signsafe算法。

    2.2 frida-ios-dump砸殼

    python dump.py app名

    打開ipa能看到Frameworks目錄下有App.framework, Flutter.framework兩個框架,表示這個app是flutter開發的。

    2.3 reFlutter重打包

    reflutter XXXX.ipa

    2.4 ios-app-signer 重簽名

    如果沒有iOS付費開發者賬號,使用免費開發者賬號的話,需要先使用xcode新建一個demo工程,并運行在iOS上,然后才能使用ios-app-signer重簽名

     

    2.5 xcode安裝重打包的ipa

    xcode -> Window -> Devices and Simulators -> INSTALLED APPS,有個+號,可以把上一步重簽名的ipa安裝到手機上

    2.6 查看dump.dart日志

    Devices and Simulators 界面有個Open Console按鈕,可以打開控制臺,查看dump.dart的路徑。

    reFlutter dump file: /private/var/mobile/Containers/Data/Application/F4F2810A-C863-4732-B871-480BFD1C101B/Documents/dump.dart

    使用scp命令把dump.dart 拷貝到本地,可以看到里面包含了類名,函數名,相對_kDartIsolateSnapshotInstructions的偏移。

    2.7 ida加載App.framework下的App文件

    使用ida加載App文件,看Exports窗口,有如下幾個導出符號,而且運氣比較好,很多函數都有符號,Precompiled_xxx

    signsafe詳細分析流程

    去dump.dart搜索signsafe字符串,沒有搜索到。

    根據前面抓包signsafe的內容10a7d81a264e79640ce3433f1b03d992,這是一個32位的字符串,猜的可能是md5相關的算法.

    于是去dump.dart里搜索md5字符串, 搜索到3處md5相關的類。

    Library:'package:pointycastle/digests/md5.dart' Class: MD5Digest extends MD4FamilyDigest implements Type: Digest {  FactoryConfig factoryConfig = sentinel ;  Function 'get:algorithmName': getter const. String: null {               Code Offset: _kDartIsolateSnapshotInstructions + 0x0000000000cc8a1c       }  Function 'get:digestSize': getter const. String: null {               Code Offset: _kDartIsolateSnapshotInstructions + 0x0000000000ccd5f4       }  Function 'MD5Digest.': constructor. String: null {               Code Offset: _kDartIsolateSnapshotInstructions + 0x0000000000002828       }  Function 'resetState':. String: null {               Code Offset: _kDartIsolateSnapshotInstructions + 0x0000000000cc4d98       }  Function 'processBlock':. String: null {               Code Offset: _kDartIsolateSnapshotInstructions + 0x0000000000ce4ea0       }      }
    Library:'package:crypto/src/md5.dart' Class: _MD5Sink@404143612 extends HashSink {  Function '_MD5Sink@404143612.': constructor. String: null {               Code Offset: _kDartIsolateSnapshotInstructions + 0x000000000026309c       }  Function 'updateHash':. String: null {               Code Offset: _kDartIsolateSnapshotInstructions + 0x0000000000c649f8       }      }
    Library:'package:crypto/src/md5.dart' Class: _MD5@404143612 extends Hash {  Function 'startChunkedConversion':. String: null {               Code Offset: _kDartIsolateSnapshotInstructions + 0x0000000000c4f2d4       }      }
    

    對三個md5相關的函數,進行hook:

    function print_native_stack(addr, context) {    return ('\r\n[' + addr + '] called from:\n' +        Thread.backtrace(context.context, Backtracer.ACCURATE)            .map(DebugSymbol.fromAddress).join('\n') + '\n');}function hook_md5() {    let kDartIsolateSnapshotInstructions = Module.findExportByName("App", "kDartIsolateSnapshotInstructions")    console.log(kDartIsolateSnapshotInstructions);    let processBlock = kDartIsolateSnapshotInstructions.add(0x0000000000ce4ea0);    let updateHash = kDartIsolateSnapshotInstructions.add(0x0000000000c649f8);    let startChunkedConversion = kDartIsolateSnapshotInstructions.add(0x0000000000c4f2d4);    Interceptor.attach(processBlock, {        onEnter(args) {            console.log("processBlock:", print_native_stack("processBlock", this));        }    })    Interceptor.attach(updateHash, {        onEnter(args) {            console.log("updateHash:", print_native_stack("updateHash", this));        }    })    Interceptor.attach(startChunkedConversion, {        onEnter(args) {            console.log("startChunkedConversion:", print_native_stack("startChunkedConversion", this));        }    })}
    

    手機上操作一下,可以看到updateHash被調用:

    去ida中看看PrecompiledapiSign_7813函數:

    對上圖畫紅框的5個函數進行hook

    function hook_addr(addr, name) {    Interceptor.attach(addr, {        onEnter(args) {            this.log = []            this.log.push(name + " onEnter:\r\n")            for(let i = 0; i < 8; i++) {                try {                    this.log.push(hexdump(args[i]), "\r\n");                  } catch (error) {                    this.log.push((args[i]), "\r\n");                }            }        }, onLeave(retval) {            this.log.push(name + " onLeave:\r\n")            try {                this.log.push(hexdump(retval), "\r\n");              } catch (error) {                this.log.push((retval), "\r\n");            }            this.log.push("=======================")            console.log(this.log);        }    })}
    function hook_apisign() {    let Precompiled_Hmac_Hmac__7814 = DebugSymbol.fromName("Precompiled_Hmac_Hmac__7814")    let Precompiled_Hmac_convert_37042 = DebugSymbol.fromName("Precompiled_Hmac_convert_37042")    let Precompiled____base64Encode_5267 = DebugSymbol.fromName("Precompiled____base64Encode_5267")    let Precompiled_Hash_convert_37041 = DebugSymbol.fromName("Precompiled_Hash_convert_37041")    let Precompiled_Digest_toString_34431 = DebugSymbol.fromName("Precompiled_Digest_toString_34431")    hook_addr(Precompiled_Hmac_Hmac__7814.address, Precompiled_Hmac_Hmac__7814.name);    hook_addr(Precompiled_Hmac_convert_37042.address, Precompiled_Hmac_convert_37042.name);    hook_addr(Precompiled____base64Encode_5267.address, Precompiled____base64Encode_5267.name);    hook_addr(Precompiled_Hash_convert_37041.address, Precompiled_Hash_convert_37041.name);    hook_addr(Precompiled_Digest_toString_34431.address, Precompiled_Digest_toString_34431.name);}
    

    得到以下日志,省略了部分無關的日志,以......代替

    Precompiled_Hmac_Hmac__7814 onEnter:,            0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDEF10c0afba9  03 6d 00 00 00 00 00 c0 fb 0a 0c 01 00 00 00 00  .m..............10c0afbb9  00 00 00 14 00 00 00 44 32 33 41 42 43 40 23 35  .......D23ABC@#510c0afbc9  36 00 00 00 00 00 00 00 00 00 00 00 00 00 00 04  6...............
    。。。。。。
    Precompiled_Hmac_convert_37042 onEnter:,            0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDEF10c0afd79  07 6d 00 00 00 00 00 90 fd 0a 0c 01 00 00 00 00  .m..............10c0afd89  00 00 00 9a 00 00 00 61 70 69 2e 78 78 78 2e 63  .......api.xxx.c10c0afd99  6e 2f 78 78 78 78 2f 61 70 69 3f 70 68 6f 6e 65  n/xxxx/api?phone10c0afda9  3d 31 33 38 30 30 31 33 38 30 30 30 26 74 78 79  =13800138000&txy10c0afdb9  7a 6d 3d 26 75 72 69 3d 61 70 69 78 78 78 78 2f  zm=&uri=apixxxx/10c0afdc9  61 70 69 2f 75 73 65 72 2f 73 65 6e 64 73 6d 73  api/user/sendsms10c0afdd9  63 6f 64 65 00 00 00 00 00 00 00 00 00 00 00 04  code............
    。。。。。。
    Precompiled____base64Encode_5267 onEnter:,            0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDEF10c0b0789  03 6d 00 00 00 00 00 a0 07 0b 0c 01 00 00 00 00  .m..............10c0b0799  00 00 00 28 00 00 00 f6 41 84 fc 8b 76 4a f3 03  ...(....A...vJ..10c0b07a9  a0 fe d9 2f e8 5d 85 0f ee 6d b2 00 00 00 00 04  .../.]...m......。。。。。。,Precompiled____base64Encode_5267 onLeave:,            0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDEF10c0b0859  03 55 00 00 00 00 00 38 00 00 00 00 00 00 00 39  .U.....8.......910c0b0869  6b 47 45 2f 49 74 32 53 76 4d 44 6f 50 37 5a 4c  kGE/It2SvMDoP7ZL10c0b0879  2b 68 64 68 51 2f 75 62 62 49 3d 00 00 00 00 00  +hdhQ/ubbI=.....
    。。。。。。,Precompiled_Digest_toString_34431 onLeave:,            0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDEF10c0b0d19  03 55 00 00 00 00 00 40 00 00 00 00 00 00 00 31  .U.....@.......110c0b0d29  30 61 37 64 38 31 61 32 36 34 65 37 39 36 34 30  0a7d81a264e7964010c0b0d39  63 65 33 34 33 33 66 31 62 30 33 64 39 39 32 00  ce3433f1b03d992.
    

    由于結果有比較明顯特征,從日志可以猜出大概算法,有md5(32位字符串或16個字節), base64(ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/= 結果在這個字符串里), hmac_sha1(40位字符串或20字節)

    md5("9kGE/It2SvMDoP7ZL+hdhQ/ubbI=") -> 10a7d81a264e79640ce3433f1b03d992
    00000000  f6 41 84 fc 8b 76 4a f3 03 a0 fe d9 2f e8 5d 85  |?A.ü.vJó. tù/è].|00000010  0f ee 6d b2                                      |.?m2|
    進行 base64 可以得到9kGE/It2SvMDoP7ZL+hdhQ/ubbI=
    hmac_sha1("D23ABC@#56", "api.xxx.cn/xxxx/api?phone=13800138000&txyzm=&uri=apixxxx/api/user/sendsmscode")    //已打碼xxxx
    

    總結

    分析以上的例子,因為運氣好,dump.dart里面有很多業務相關的函數符號,所以降低了很大的難度。對抗以上的逆向方法也很簡單,flutter官方就有解決方案。

    加上 --obfuscate --split-debug-info兩個參數就能抹去這些類名和函數名.

    hmacflutter
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    Flutter APP逆向實踐
    2022-07-21 16:20:09
    分析flutter的時候,沒有交叉引用,那真是想它他拖進回收站。最近又遇到了幾個flutter開發的app,對它進行了一些研究,總結了以下的分析流程。本文以iOS app為例子作為講解,Android 的flutter app和iOS 的flutter app分析方法類似。
    如前文所述,Cobalt Strike 網絡流量可以使用正確的 AES 和 HMAC 密鑰進行解密。之前也講述了如何通過解密 Cobalt Strike 元數據的方法獲取密鑰,本文將會重點描述從內存 Dump 中提取密鑰的方法。
    一個CTF+滲透測試工具包,主要實現一些常用的加密與編碼功能,在軟件使用過程中發現問題或建議歡迎提交 issue,也歡迎提交新功能需求。前身為CryptionTool,為更方便的開發更換框架重構。軟件基于JDK17開發,使用JAVAFX UI框架以及JFoenixUI組件架構。詳見開發文檔。
    01結論SSLv1.0 無法防止重放(Replay)攻擊。SSLv2.0 以及升級版SSL3.0、TLS1.
    意大利民眾電話號碼正在明碼標價地被兜售
    一個CTF+滲透測試工具包,主要實現一些常用的加密與編碼功能,在軟件使用過程中發現問題或建議歡迎提交 issue,也歡迎提交新功能需求。
    繞過小程序簽名驗證
    2022-07-21 08:30:00
    在一些關鍵業務接口,系統通常會對請求參數進行簽名驗證,一旦篡改參數服務端就會提示簽名校驗失敗。在黑盒滲透過程中,如果沒辦法繞過簽名校驗,那么就無法進一步漏洞檢測。
    在一些關鍵業務接口,系統通常會對請求參數進行簽名驗證,一旦篡改參數服務端就會提示簽名校驗失敗。在黑盒滲透過程中,如果沒辦法繞過簽名校驗,那么就無法進一步深入。
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类