推广

如何逆向Flutter应用(反编译)

iseeyu3年前 (2024-02-22)推广143

1.png

由于快照的序列化格式针对性的为读取速率做了设计,从快照读取也大大提高代码的加载速度(创建所需类信息、全局数据等,可以类比OC Runtime启动加载元类、类信息等)。最开始快照中是不包含机器代码的(即函数体内部的执行逻辑),后来随着AOT模式的开发这部分被加入到快照中了,这些后来者也就是前面说的Instructions。

2.png

这里要补充的是,Instructions指的是可执行汇编指令,在.o文件中必须放在text段里,标记为可执行(否则iOS无法加载并执行它)。类信息、全局变量这些内容可以放在data端作为普通数据被加载。(字节的优化50%包体积也是基于此,有兴趣可以看一下文章:https://juejin.im/post/6844904014170030087)。

接着说DartVmSnapshot 与DartIsolateSnapshot。这就涉及Data虚拟机是如何运行业务代码。虚拟是Data代码运行的载体,VM中运行的逻辑都跑在一个抽象的叫做Isolate(隔离)的实体中。你可以把Isolate当做OC里一个带有Runloop的Thread看待(至于他们之间的关系又是一个令人头疼的面试题,这里不展开了)。简要来说Isolate中维护了堆栈变量,函数调用栈帧,用于GC、JIT等辅助任务的子线程等, 而这里的堆栈变量就是要被序列化到磁盘上的东西,即IsolateSnapshot。此外像dart预置的全局对象,比如null,true,false等等等是由VMIsolate管理的,这些东西需序列化后即VmSnapshot。

3.png

到这里大致了解Flutter APP产物中的结构。那如何读取他们呢?我们可以从clustered_snapshot.cc中的FullSnapshotReader:: 函数看起,看他是如何反序列化的。


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对象

4.png

每个对象均以包含以下标记的uint32_t开头:

5.png

原则上我们自己可以写一个读取的程序进行分析,但是网上有一个使用Python写好的读取程序(只支持读取ELF格式文件,也就是只支持Android包产物的分析):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+'// 函数索引:' + '{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 + '生成成功')


# 开始执行
getFiles('samples/arm-app.so', '')

这个脚本最终会提取所有指定文件的源码,其中对友商app其中一个类的导出结果如下:

6.png

其中标注了类对象 与函数的索引,可以在控制台使用s.refs[xxxxx].x继续跟查。

扫描二维码推送至手机访问。

版权声明:本文由西安泽虎代运营发布,如需转载请注明出处。

转载请注明出处https://www.0291.com.cn/post/55964.html

相关文章

西安企业做百度推广,一个月花三千和花三万到底差在哪?

4|最近跟几个西安的老板聊天,发现一个挺有意思的现象:同样做百度推广,有人一个月花三千块,询盘不断;有人一个月砸三万,水花都没见着。问他们区别在哪,大多数人说不清楚,只知道'钱花了,没效果'。 5| 6|百度推广这东西,说复杂也复杂,说简单也简单。核心就一件事:让你的潜在客户搜某个词的时候,你的信息...

我想当策划,大学应该选什么专业?

我想当策划,大学应该选什么专业?

学,广告,传播,公关,这些专业其实都可以。另外也可以学像汉语言文学,以及一些跟文学相关的一些专业,会对你找工作有帮助。但是,你想从事这个这份职业,除了专业的学习之外,更多的是,你要在上学的时候积极参与一些更营销策划相关的社会实践。比如说你可以做一些兼职,比如说帮别人去写稿,...

服装市场营销策略分析,“定位不慎满盘皆输”

服装市场营销策略分析,“定位不慎满盘皆输”

不同的区域需要不同的策略,而不同的营销策略需要不同的销售思路来配合。因此,在研究女装产品销售思路之前,最好先对于市场与营销策略做一次深入地了解。1.市场区域化(market segmentation):目标营销的第一步,是将整个市场拆分成若干个同性质的小市场。2.目标市场的...

晚间悦读丨你的思维方式,决定你的人生高度!

晚间悦读丨你的思维方式,决定你的人生高度!

越长大,越发现,能够决定你人生高度的,往往是你的认知层次。 你的认知在哪个层级,你的人生就处在什么状态。你认知的高度,决定了你人生的高度。 那如何才能升级方式、提升认知能力呢?可以从这三个方面做出改变。 NO. 1 改变态度 有人...

编写项目营销策划方案六大要点

编写项目营销策划方案六大要点

现在商业发展非常迅速,的需求非常的大,但是要做好营销策划是有一定难度的,我把自己做营销策划的一些体会在这里分享给朋友们。营销策划方案六大要点我们先说项目营销策划方案这个话题。大部分的营销策划人都是在企业里工作,大部分项目之初都是来源于一个想法,创始人、老板或者创始团队的一个...

【商场营销策划】如何进行商场营销策划?

【商场营销策划】如何进行商场营销策划?

商场是策划者们关注的永恒话题,那么究竟该如何作好商场营销策划呢?掌握以下这几点营销策略设计原则,至关重要!1.义利兼顾的原则。商场在获取利润的同时要充分考虑到消费者的合法利益和商场的长远利益。在作商场活动营销策略方案的时候应该将这一点作为首要的原则。对商场有利而对顾客不利的...

现在,非常期待与您的又一次邂逅

我们努力让每一部企业宣传片和抖音短视频成为商业大片