<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          如何逆向Flutter應用

          共 16727字,需瀏覽 34分鐘

           ·

          2021-09-18 22:56

          相關閱讀:一個90后員工猝死的全過程


          作者:david09
          鏈接:https://www.jianshu.com/p/d55c0419d790

          目前大多數(shù)使用Flutter的應用都是采用add2app的方式,在APP中與Flutter相關的內(nèi)容主要有FlutterEngine、APP產(chǎn)物、資源文件。我們可以在應用市場上尋找一個接入Flutter的應用做實驗。(apk可在各大應用市場下載,ipa下載可以在mac上安裝Apple Configurator 2進行),apk和ipa中flutter相關產(chǎn)物目錄如下:

          iOS包文件為ipa,下載后將其后綴重命名為zip進行解壓,解壓后Payload下即可看到應用文件夾,其中FlutterEngine、APP產(chǎn)物、資源文件分別在如下位置:

          xxx.app
          └── Frameworks
              ├── App.framework
              │   ├── AppDart APP產(chǎn)物)
              │   ├── Info.plist
              │   ├── SC_Info
              │   ├── _CodeSignature
              │   └── flutter_assets
              │       ├── flutter_assets
              │       ├── AssetManifest.json
              │       ├── FontManifest.json
              │       ├── LICENSE
              │       ├── fonts
              │       ├── images
              │       ├── mtf_module_info
              │       └── packages
              └── Flutter.framework
                  ├── FlutterFlutterEngine
                  ├── Info.plist
                  ├── SC_Info
                  ├── _CodeSignature
                  └── icudtl.dat

          Android包文件為apk,下載后將其后綴重命名為zip進行解壓,其中FlutterEngine、APP產(chǎn)物、資源文件分別在如下位置:

          xxx.apk
          ├── assets
          │   └── flutter_assets
          │       └── flutter_assets
          │       ├── AssetManifest.json
          │       ├── FontManifest.json
          │       ├── LICENSE
          │       ├── fonts
          │       ├── images
          │       ├── mtf_module_info
          │       └── packages
          └── lib
                  └── armeabi
                  ├── libapp.oDart APP產(chǎn)物)
                  └── libflutter.soFlutterEngine

          FlutterEngine各個app都是使用官方或者在官方基礎上進行修改,差別不多,我們暫不關心這部分的逆向。資源文件多是圖片,字體等無需逆向即可查看的資源。我們主要關心的是使用Dart編寫的業(yè)務邏輯或者某些框架代碼,這部分代碼在APP產(chǎn)物中。即:App.framework/App 或 armeabi/libapp.o這兩個文件都是動態(tài)庫,我們先簡單看看里面包含什么?

          # 可以安裝常用的 bin utils工具,如 brew update && brew install binutils
          ~/Downloads > objdump -t App
          App:     文件格式 mach-o-arm64

          SYMBOL TABLE:
          0000000001697e60 g       0f SECT   02 0000 [.const] _kDartIsolateSnapshotData
          000000000000b000 g       0f SECT   01 0000 [.text] _kDartIsolateSnapshotInstructions
          0000000001690440 g       0f SECT   02 0000 [.const] _kDartVmSnapshotData
          0000000000006000 g       0f SECT   01 0000 [.text] _kDartVmSnapshotInstructions

          ~/Downloads > greadelf -s libapp.so
          Symbol table '.dynsym' contains 5 entries:
             Num:    Value  Size Type    Bind   Vis      Ndx Name
               000000000     0 NOTYPE  LOCAL  DEFAULT        UND
               100001000 12992 FUNC    GLOBAL DEFAULT        1 _kDartVmSnapshot[...]
               200005000 0x127df60 FUNC    GLOBAL DEFAULT    2 _kDartIsolateSna[...]
               301283000 22720 OBJECT  GLOBAL DEFAULT        3 _kDartVmSnapshotData
               401289000 0x9fc858 OBJECT  GLOBAL DEFAULT     4 _kDartIsolateSna[...]

          可以看到無論是Android還是iOS,Dart App產(chǎn)物中都包含4個程序段。(來自https://github.com/flutter/flutter/wiki/Flutter-engine-operation-in-AOT-Mode)

          • '_kDartVmSnapshotData':代表 isolate 之間共享的 Dart 堆 (heap) 的初始狀態(tài)。有助于更快地啟動 Dart isolate,但不包含任何 isolate 專屬的信息。

          • '_kDartVmSnapshotInstructions':包含 VM 中所有 Dart isolate 之間共享的通用例程的 AOT 指令。這種快照的體積通常非常小,并且大多會包含程序樁 (stub)。

          • '_kDartIsolateSnapshotData':代表 Dart 堆的初始狀態(tài),并包含 isolate 專屬的信息。

          • '_kDartIsolateSnapshotInstructions':包含由 Dart isolate 執(zhí)行的 AOT 代碼。

          看了上面可能還是一臉懵o((⊙﹏⊙))o,為什么分四塊,Data與Instructions,Vm與Isolate是什么?為什么使用Snapshot(快照)命名。關于這些問題,推薦一篇博客https://mrale.ph/dartvm/ 。Data與Instructions,Vm與Isolate這些概念兩兩組合,正好對應上面四個段。也就是VmData、VmInstructions、IsolateData、IsolateInstructions。

          先說一下Data與Instructions。首先我們知道的是Flutter編譯運行在app上分為JIT和AOT模式,線上只能使用AOT模式,也就是Flutter引入的DartVM包含了執(zhí)行AOT產(chǎn)物的功能。為了與JIT模式兼容,DartVM采用了所謂快照的方式,即JIT運行時編譯后的基本結構與AOT編譯的基本結構相同。將類信息、全局變量、函數(shù)指令直接以序列化的方式存在磁盤中,稱為Snapshot(快照)。

          由于快照的序列化格式針對性的為讀取速率做了設計,從快照讀取也大大提高代碼的加載速度(創(chuàng)建所需類信息、全局數(shù)據(jù)等,可以類比OC Runtime啟動加載元類、類信息等)。最開始快照中是不包含機器代碼的(即函數(shù)體內(nèi)部的執(zhí)行邏輯),后來隨著AOT模式的開發(fā)這部分被加入到快照中了,這些后來者也就是前面說的Instructions。

          這里要補充的是,Instructions指的是可執(zhí)行匯編指令,在.o文件中必須放在text段里,標記為可執(zhí)行(否則iOS無法加載并執(zhí)行它)。類信息、全局變量這些內(nèi)容可以放在data端作為普通數(shù)據(jù)被加載。(字節(jié)的優(yōu)化50%包體積也是基于此,有興趣可以看一下文章:https://juejin.im/post/6844904014170030087)。

          接著說DartVmSnapshot 與DartIsolateSnapshot。這就涉及Data虛擬機是如何運行業(yè)務代碼。虛擬是Data代碼運行的載體,VM中運行的邏輯都跑在一個抽象的叫做Isolate(隔離)的實體中。你可以把Isolate當做OC里一個帶有Runloop的Thread看待(至于他們之間的關系又是一個令人頭疼的面試題,這里不展開了)。簡要來說Isolate中維護了堆棧變量,函數(shù)調(diào)用棧幀,用于GC、JIT等輔助任務的子線程等, 而這里的堆棧變量就是要被序列化到磁盤上的東西,即IsolateSnapshot。此外像dart預置的全局對象,比如null,true,false等等等是由VMIsolate管理的,這些東西需序列化后即VmSnapshot。

          到這里大致了解Flutter APP產(chǎn)物中的結構。那如何讀取他們呢?我們可以從clustered_snapshot.cc中的FullSnapshotReader:: 函數(shù)看起,看他是如何反序列化的。

          void Deserializer::ReadIsolateSnapshot(ObjectStore* object_store) {
            Array& refs = Array::Handle();
            Prepare();
            {
              NoSafepointScope no_safepoint;
              HeapLocker hl(thread(), heap_->old_space());
              // N.B.: Skipping index 0 because ref 0 is illegal.
              const Array& base_objects = Object::vm_isolate_snapshot_object_table();
              for (intptr_t i = 1; i < base_objects.Length(); i++) {
                AddBaseObject(base_objects.At(i));
              }
              Deserialize();
              // Read roots.
              RawObject** from = object_store->from();
              RawObject** to = object_store->to_snapshot(kind_);
              for (RawObject** p = from; p <= to; p++) {
                *p = ReadRef();
              }
          #if defined(DEBUG)
              int32_t section_marker = Read<int32_t>();
              ASSERT(section_marker == kSectionMarker);
          #endif

              refs = refs_;
              refs_ = NULL;
            }
            thread()->isolate()->class_table()->CopySizesFromClassObjects();
            heap_->old_space()->EvaluateSnapshotLoad();

          #if defined(DEBUG)
            Isolate* isolate = thread()->isolate();
            isolate->ValidateClassTable();
            isolate->heap()->Verify();
          #endif
            for (intptr_t i = 0; i < num_clusters_; i++) {
              clusters_[i]->PostLoad(refs, kind_, zone_);
            }
            // Setup native resolver for bootstrap impl.
            Bootstrap::SetupNativeResolver();
          }

          要看懂這部分也是十分費力,另一個大神的分析文章可能會為我們帶來很多啟示:https://blog.tst.sh/reverse-engineering-flutter-apps-part-1/

          我們要看如何讀取RawObject對象

          image

          每個對象均以包含以下標記的uint32_t開頭:

          原則上我們自己可以寫一個讀取的程序進行分析,但是網(wǎng)上有一個使用Python寫好的讀取程序(只支持讀取ELF格式文件,也就是只支持Android包產(chǎn)物的分析):https://github.com/hdw09/darter 基于這個讀取工具提供的API我們可以寫一個導出應用所有類定義的工具。

          from darter.file import parse_elf_snapshot, parse_appjit_snapshot
          from darter.asm.base import populate_native_references
          import re
          from collections import defaultdict
          import os
          import shutil

          def get_funciont(fun_index, s, span=False):
              spanStr = ''
              if span:
                  spanStr = '    '
              fun_str = '\n'+spanStr+'// 函數(shù)索引:' + '{0}'.format(fun_index)+'\n'
              returnTypeStr = ''
              if '_class' in s.refs[fun_index].x['result_type'].x.keys():
                  returnTypeStr = s.refs[fun_index].x['result_type'].x['_class'].x['name'].x['value']
              elif 'name' in s.refs[fun_index].x['result_type'].x.keys():
                  returnTypeStr = str(s.refs[fun_index].x['result_type'])
              else:
                  returnTypeStr = s.refs[fun_index].x['result_type'].x['value']
              fun_str = fun_str+spanStr + returnTypeStr
              fun_str = fun_str + ' ' + s.refs[fun_index].x['name'].x['value']+'('
              parameterCount = 0
              if type(s.refs[fun_index].x['parameter_types'].x['value']) != type(''):
                  for parameterName in s.refs[fun_index].x['parameter_names'].x['value']:
                      parType = ''
                      if '_class' in s.refs[fun_index].x['parameter_types'].x['value'][parameterCount].x.keys():
                          parType = s.refs[fun_index].x['parameter_types'].x['value'][parameterCount].x['_class'].x['name'].x['value']
                      else:
                          parType = s.refs[fun_index].x['parameter_types'].x['value'][parameterCount].x['value']
                      fun_str = fun_str + parType + ' '
                      fun_str = fun_str + parameterName.x['value'] + ', '
                      parameterCount = parameterCount + 1
              fun_str = fun_str + ') \n'+spanStr+'{ \n'
              for nrefsItem in s.refs[fun_index].x['code'].x['nrefs']:
                  fun_str = fun_str + spanStr + '    {0}'.format(nrefsItem) + '\n'

              fun_str = fun_str + spanStr+'}'
              return fun_str

          def get_classDis(clas_index, s):
              class_str = '\n// 類索引:' + '{0}'.format(clas_index)+' 使用s.refs[xxxx].x跟查\n'
              superName = ''
              if '_class' in s.refs[clas_index].x['super_type'].x.keys():
                  superName = s.refs[clas_index].x['super_type'].x['_class'].x['name'].x['value']
              else:
                  superName = s.refs[clas_index].x['super_type'].x['value']
              class_str = class_str + \
                  'class {0} : {1} {2}\n'.format(
                      s.refs[clas_index].x['name'].x['value'], superName, '{')
              if type(s.refs[clas_index].x['functions'].x['value']) != type(''):
                  for fun in s.refs[clas_index].x['functions'].x['value']:
                      class_str = class_str+'\n'+get_funciont(fun.ref, s, True)
              return class_str+'\n\n}'

          def get_lob_class(lib, s):
              all_class = ''
              for item in lib.src:
                  if 'name' in item[0].x.keys():
                      all_class = all_class + get_classDis(item[0].ref, s) + '\n'
              if '類索引' in all_class:
                  return all_class
              else:
                  return '沒有獲得任何信息'

          def show_lob_class(lib, s):
              print(get_lob_class(lib, s))

          def writeStringInPackageFile(packageFile, content):
              packageFile = packageFile.replace('dart:''package:dart/')
              filename = packageFile.replace('package:''out/')
              filePath = filename[0:filename.rfind('/')]
              content = '// {0} \n'.format(packageFile)+content
              if os.path.exists(filePath) == False:
                  os.makedirs(filePath)
              file = open(filename, 'w')
              file.write(content)
              file.close()

          def getFiles(elfFile, filter):
              s = parse_elf_snapshot(elfFile)
              populate_native_references(s)
              allLibrary = sorted(s.getrefs('Library'),
                                  key=lambda x: x.x['url'].x['value'])
              for tempLibrary in allLibrary:
                  name = tempLibrary.x['url'].x['value']
                  if filter in name:
                      print(name + '開始生成....')
                      writeStringInPackageFile(
                          name, get_lob_class(s.strings[name].src[1][0], s))
                      print(name + '生成成功?')

          # 開始執(zhí)行
          getFiles('samples/arm-app.so''')

          這個腳本最終會提取所有指定文件的源碼,其中對友商app其中一個類的導出結果如下:

          其中標注了類對象 與函數(shù)的索引,可以在控制臺使用s.refs[xxxxx].x繼續(xù)跟查。



          1、滴滴、滿幫、Boss直聘都被調(diào)查,為啥知乎美國上市沒被查?

          2、字節(jié)跳動重大宣布:取消!員工炸了:直接降薪1

          3、再見了,Teamviewer!

          4、人臉識別的時候,一定要穿上衣服啊!

          5、程序員被公司辭退12天,前領導要求回公司講清楚代碼,結果懵了


          瀏覽 66
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  手机无码视频在线观看 | 亚洲视频完整版在线观看 | 91伊人大香蕉 | 天天干成人电影 | 亚洲成人MV |