《安卓逆向这档事》番外实战篇3-拨云见日之浅谈Flutter逆向

查看 124|回复 11
作者:不苦小和尚   

一、课程目标
1.了解Flutter基本概念以及识别特征
2.了解Flutter应用的抓包对抗策略
3.了解Flutter反编译以及实战
二、工具
1.某读app
2.proxypin
3.blutter
三、课程内容
1.Flutter简介
Flutter是Google构建在开源的Dart VM之上,使用Dart语言开发的移动应用开发框架,可以帮助开发者使用一套Dart代码就能快速在移动iOS 、Android上构建高质量的原生用户界面,同时还支持开发Web和桌面应用。
Flutter引擎是一个用于高质量跨平台应用的可移植运行时,由C/C++编写。它实现了Flutter的核心库,包括动画和图形、文件和网络I/O、辅助功能支持、插件架构,以及用于开发、编译和运行Flutter应用程序的Dart运行时和工具链。引擎将底层C++代码包装成 Dart代码,通过dart:ui暴露给 Flutter框架层。
flutter开源地址
flutter官网

[原创]Flutter概述和逆向技术发展时间线,带你快速了解
2.Flutter特征
在逆向分析前,我们首先要确定测试目标是否用Flutter开发的。当使用Flutter构建Android APP时,在assets文件夹下会有dexopt和flutter_assets两个文件夹

lib文件夹会有两个so文件:libapp.so和libflutter.so(flutter动态链接库,与实际业务代码无关)

3.Flutter抓包对抗
原理:
[ol]
  • Dart语言标准库的网络请求不走Wi-Fi代{过}{滤}理:Flutter使用的是Dart语言,其标准库中的网络请求不会通过代{过}{滤}理发送,这与许多其他应用不同。常规的抓包工具通常依赖于代{过}{滤}理来捕获网络流量,因此无法捕获Flutter应用的网络请求。
  • Dart SDK中的证书信任:Dart SDK在Android平台上强制只信任系统目录下的证书。这意味着Flutter应用不会信任用户安装的证书,除非这些证书位于Android系统的/system/etc/security/cacerts目录中。这是通过Dart源码中的runtime/bin/security_context_linux.cc文件实现的。
    [/ol]
    通过分析Flutter应用程序抛出的错误,可以定位到触发错误的源代码位置,错误指向了handshake.cc:352,这是一个处理SSL握手的源代码位置。
    E/flutter (10371):  [ERROR:flutter/runtime/dart_isolate.cc(805)] Unhandled exception:  
    E/flutter (10371):  HandshakeException: Handshake error in client (OS Error:
    E/flutter (10371):  NO_START_LINE(pem_lib.c:631)
    E/flutter (10371):  PEM routines(by_file.c:146)
    E/flutter (10371):  NO_START_LINE(pem_lib.c:631)
    E/flutter (10371):  PEM routines(by_file.c:146)
    E/flutter (10371):  CERTIFICATE_VERIFY_FAILED: self signed certificate in certificate chain(handshake.cc:352))
    为了绕过SSL验证,需要找到一个合适的hook点,即源代码中可以被拦截和修改以改变程序行为的位置。ssl_verify_peer_cert函数是一个可能的hook点,但经过测试,仅仅修改这个函数的返回值并不能成功绕过SSL验证。
    进一步分析源代码后,发现session_verify_cert_chain函数可以作为另一个hook点。这个函数在验证证书链时,如果证书验证失败,会返回一个错误。
    ret = ssl->ctx->x509_method->session_verify_cert_chain(
                  hs->new_session.get(), hs, &alert)
                  ? ssl_verify_ok
                  : ssl_verify_invalid;
    session_verify_cert_chain函数定义在ssl_x509.cc,在该方法里可以看到有ssl_client和ssl_server两个字符串可以辅助定位方法

    1.hook_ssl_client
    在libflutter.so里搜索ssl_client定位到方法,内存搜刮函数前10字节定位,在运行时将返回函数改为true即可绕过证书链检查实现抓包(这里以64位的so为例)

    function hook_dlopen() {
        var android_dlopen_ext = Module.findExportByName(null, "android_dlopen_ext");
        Interceptor.attach(android_dlopen_ext, {
            onEnter: function (args) {
                var so_name = args[0].readCString();
                if (so_name.indexOf("libflutter.so") >= 0) this.call_hook = true;
            }, onLeave: function (retval) {
                if (this.call_hook) hookFlutter();
            }
        });
    }
    function hook_ssl_verify_result(address) {
        Interceptor.attach(address, {
                onEnter: function(args) {
                    console.log("Disabling SSL validation")
                },
                onLeave: function(retval) {
                                    console.log("Retval: " + retval);
                    retval.replace(0x1);
                }
            });
        }
    function hookFlutter() {
        var m = Process.findModuleByName("libflutter.so");
        //利用函数前10字节定位
        var pattern = "FF C3 01 D1 FD 7B 01 A9 FC 6F 02 A9FA 67 03 A9 F8 5F 04 A9 F6 57 05 A9 F4 4F 06 A9 08 0A 80 52 48 00 00 39";
        var res = Memory.scan(m.base, m.size, pattern, {
            onMatch: function(address, size){
                console.log('[+] ssl_verify_result found at: ' + address.toString());
            // Add 0x01 because it's a THUMB function
            // Otherwise, we would get 'Error: unable to intercept function at 0x9906f8ac; please file a bug'
                hook_ssl_verify_result(address);
            },
            onError: function(reason){
                console.log('[!] There was an error scanning memory');
            },
            onComplete: function() {
                console.log("All done")
            }
        });
    }
    2.reflutter之patch
    reFlutter开源地址

    1.pip3 install reflutter pip安装对应的库
    2.输入命令:reflutter flutter.apk
    选择1流量监控和拦截,输入PC端的IP地址后(cmd窗口输入ipconfig),将获取到release.RE.apk,但此apk尚未签名,需要我们手动签名(输入命令的过程需要全局代{过}{滤}理)

    3.使用MT管理器或者uber-apk-signer.jar签名,输入命令:java -jar uber-apk-signer-1.2.1.jar --apk release.RE.apk。然后将重签名的apk安装到真机或者模拟器上。
    4.设置BurpSuite的代{过}{滤}理,端口为8083,绑定所有地址,并且勾选All interfaces,使非代{过}{滤}理意识的客户端直接连接到侦听器。
    BurpSuitePro-2.1


    5.设置Drony的wifi代{过}{滤}理主机名端口和BurpSuite一致,然后触发app就能抓到包了

    3.Reqable&proxyPin(推荐)
    Reqable或者proxyPin直接抓包即可(工具下载看上一课)


    4.Flutter反编译
    1.快照
    使用readelf -s命令读取保存快照信息的libapp.so将会输出下面的内容
    Symbol table '.dynsym' contains 6 entries:
    Num:    Value          Size Type    Bind   Vis      Ndx Name
    0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND
    1: 000000000014c000 29728 OBJECT  GLOBAL DEFAULT    7 _kDartVmSnapshotInstructi
    2: 0000000000153440 0x22bd30 OBJECT  GLOBAL DEFAULT    7 _kDartIsolateSnapshotInst
    3: 0000000000000200 15248 OBJECT  GLOBAL DEFAULT    2 _kDartVmSnapshotData
    4: 0000000000003dc0 0x147af0 OBJECT  GLOBAL DEFAULT    2 _kDartIsolateSnapshotData
    5: 00000000000001c8    32 OBJECT  GLOBAL DEFAULT    1 _kDartSnapshotBuildId
    “快照”指的是 Flutter 应用在编译过程中生成的特定数据结构,用于加速应用的启动和运行。具体来说,快照包括四种类型:

  • _kDartVmSnapshotData: 代表 isolate 之间共享的 Dart 堆 (heap) 的初始状态。有助于更快地启动 Dart isolate,但不包含任何 isolate 专属的信息。

  • _kDartVmSnapshotInstructions:包含 VM 中所有 Dart isolate 之间共享的通用例程的 AOT 指令。这种快照的体积通常非常小,并且大多会包含程序桩 (stub)。

  • _kDartIsolateSnapshotData:代表 Dart 堆的初始状态,并包含 isolate 专属的信息。

  • _kDartIsolateSnapshotInstructions:包含由 Dart isolate 执行的 AOT 代码。

    其中_kDartIsolateSnapshotInstructions是最为重要的,因为包含了所有要执行的AOT代码,即业务相关的代码。
    2.逆向方法
    1.(静态)解析libapp.so,即写一个解析器,将libapp.so中的快照数据按照其既定格式进行解析,获取业务代码的类的各种信息,包括类的名称、其中方法的偏移等数据,从而辅助逆向工作。
    关于Flutter快照的具体刨析只需要看下面引用的两篇文章
    Reverse engineering Flutter apps (Part 1) (tst.sh)
    Reverse engineering Flutter apps (Part 2) (tst.sh)
    2.(动态)编译修改过的libflutter.so并且重新打包到APK中,在启动APP的过程中,由修改过的引擎动态链接库将快照数据获取并且保存。
    PS:不同版本的Dart引擎其快照格式不同,所以静态的方法就需要频繁跟着版本更新迭代,成本极高,而动态也需要重新编译对应版本的链接库。同时如果APP作者抹除版本信息和hash信息,则无从下手,且重打包APK极易被检测到。
    静态方法推荐工具:blutter
    动态方法推荐工具:reFlutter
    3.blutter实战
    环境:python3.10
    1.首先安装git
    下载地址

    2.下载visual studio
    下载地址

    3.下载安装,在工作负荷里勾选"使用C++的桌面开发"

    4.clone项目(全程运行在代{过}{滤}理环境否则会导致无法下载),或者下载解压到指定文件夹
    git clone https://github.com/worawit/blutter --depth=1
    5.进入到blutter文件夹,cmd窗口运行初始化脚本
    python .\scripts\init_env_win.py

    6.要打开x64 Native Tools Command Prompt,它可以在Visual Studio文件夹中找到

    7.把需要反编译的flutterapp用压缩包打开,提取v8a里的libflutter.so和libapp.so(现在基本上是64位)解压到blutter文件夹,并创建一个输出结果的文件夹

    8.在刚才打开的x64窗口运行下面的命令(全局代{过}{滤}理!),等待运行完后会在output文件下生成一些脚本信息
    PS:blutter目前支持最新的版本的dart快照解析,如果这个跑不起来可以参考第四步手动配置
    python blutter.py .\arm64-v8a\ .\output


    asm 对dart语言的反编译结果,里面有很多dart源代码的对应偏移
    ida_script so文件的符号表还原脚本
    blutter_frida.js目标应用程序的 frida 脚本模板
    objs.txt对象池中对象的完整(嵌套)转储,对象池里面的方法和相应的偏移量
    pp.txt对象池中的所有 Dart 对象
    9.接下来ida加载libapp.so,然后ida左上角点击file,再点击Script file加载符号解析脚本

    10.至此可以看到so里的相关函数以显现

    协议实现:
    import hashlib  
    import base64  
    import requests  
    headers = {  
        'user-agent': 'Dart/3.1 (dart:io)',  
        'content-type': 'application/json; charset=utf-8',  
        'accept-encoding': 'gzip',  
        'host': 'api.mandu.pro',  
        'Content-Length': '98',  
    }  
    def hash_and_encode(input_string):  
        sha256_hash = hashlib.sha256()  
        sha256_hash.update(input_string.encode('utf-8'))  
        hash_bytes = sha256_hash.digest()  
        base64_encoded = base64.b64encode(hash_bytes).decode('utf-8')  
        return base64_encoded  
    input_string = "md123456"  
    result = hash_and_encode(input_string)  
    json_data = {  
        'account': '[email protected]',  
        'type': 1,  
        'password': result,  
    }  
    response = requests.post('https://api.xxx.pro/user/session', headers=headers, json=json_data)  
    print(response.text)
    六、视频及课件地址
    百度云
    阿里云
    哔哩哔哩
    教程开源地址
    PS:解压密码都是52pj,阿里云由于不能分享压缩包,所以下载exe文件,双击自解压
    七、其他章节
    《安卓逆向这档事》一、模拟器环境搭建
    《安卓逆向这档事》二、初识APK文件结构、双开、汉化、基础修改
    《安卓逆向这档事》三、初识smail,vip终结者
    《安卓逆向这档事》四、恭喜你获得广告&弹窗静默卡
    《安卓逆向这档事》五、1000-7=?&动态调试&Log插桩
    《安卓逆向这档事》六、校验的N次方-签名校验对抗、PM代{过}{滤}理、IO重定向
    《安卓逆向这档事》七、Sorry,会Hook真的可以为所欲为-Xposed快速上手(上)模块编写,常用Api
    《安卓逆向这档事》八、Sorry,会Hook真的可以为所欲为-xposed快速上手(下)快速hook
    《安卓逆向这档事》九、密码学基础、算法自吐、非标准加密对抗
    《安卓逆向这档事》十、不是我说,有了IDA还要什么女朋友?
    《安卓逆向这档事》十二、大佬帮我分析一下
    《安卓逆向这档事》番外实战篇1-某电影视全家桶
    《安卓逆向这档事》十三、是时候学习一下Frida一把梭了(上)
    《安卓逆向这档事》十四、是时候学习一下Frida一把梭了(中)
    《安卓逆向这档事》十五、是时候学习一下Frida一把梭了(下)
    《安卓逆向这档事》十六、是时候学习一下Frida一把梭了(终)
    《安卓逆向这档事》十七、你的RPCvs佬的RPC
    《安卓逆向这档事》番外实战篇2-【2024春节】解题领红包活动,启动!
    《安卓逆向这档事》十八、表哥,你也不想你的Frida被检测吧!(上)
    《安卓逆向这档事》十九、表哥,你也不想你的Frida被检测吧!(下)
    《安卓逆向这档事》二十、抓包学得好,牢饭吃得饱(上)
    八、参考文档
    [原创]Flutter概述和逆向技术发展时间线,带你快速了解
    blutter
    reFlutter
    [翻译]Flutter 逆向初探
    [原创]一种基于frida和drony的针对flutter抓包的方法
    Android-Flutter逆向
    Flutter Android APP 逆向系列 (一)

    图片, 快照

  • 似水流年2015   


    正己 发表于 2024-8-29 15:49
    算法助手一键开启

    不行的,有各种检测,写了这个脚本也不行,xpose,frida,[Asm] 纯文本查看 复制代码function load_all_class() {
    if (Java.available) {
        Java.perform(function () {
            var DexFileclass = Java.use("dalvik.system.DexFile");
            var BaseDexClassLoaderclass = Java.use("dalvik.system.BaseDexClassLoader");
            var DexPathListclass = Java.use("dalvik.system.DexPathList");
    var hookedClasses = new Set(); // 用于跟踪已经 hook 的类
            Java.enumerateClassLoaders({
                onMatch: function (loader) {
                    console.log("loader:", loader);
                   
                    // 为了排除 BootClassLoader 和不相关的 ClassLoader
                    if ((loader.toString().indexOf("java.lang.BootClassLoader") === -1)) {
                        try {
                            var BaseDexClassLoader_obj = Java.cast(loader, BaseDexClassLoaderclass);
                            var pathList = BaseDexClassLoader_obj.pathList.value;
                            var pathList_obj = Java.cast(pathList, DexPathListclass);
                            var ElementsArray = pathList_obj.dexElements.value;
                            console.log("ElementsArray---->", ElementsArray);
                            
                            if(hookedClasses.has(className)){return}
                            
                            // 遍历 ElementsArray,查找指定的 .apk 文件
                            for (var index in ElementsArray) {
                                var element = ElementsArray[index];
                                try {
                                    try { var dexfile = element.dexFile.value; } catch (e) {}
                                    if (dexfile) {
                                        var dexfileobj = Java.cast(dexfile, DexFileclass);
                                        const classNames = [];
                                        const enumeratorClassNames = dexfileobj.entries();
                                        // 查找指定的 .apk 文件
                                        if (dexfileobj.toString().indexOf("/data/app/com.dlxx.mam.Internal-dJ7kxt_bZ0UnShdkHHubMg==/base.apk") >= 0 ) {
                                            console.log("Found .apk file in dexfile: ", dexfileobj);
                                           
                                            while (enumeratorClassNames.hasMoreElements()) {
                                                var className = enumeratorClassNames.nextElement().toString();
                                               // console.log("ClassName : " + className);
                                                classNames.push(className);
                                                // 指定加载某个类
                                                if (className=="com.tencent.tbs.core.webkit.WebView"  ) {
                                                    var webviewClass = Java.use("com.tencent.tbs.core.webkit.WebView");
                                                         
                                                    console.log("ClassName : " + className);
                                                // Hook WebView 的构造方法
                                                var constructors = webviewClass['$init'].overloads;
                                                for (var i = 0; i
    zhaohainuo   

            佬,能出一期uniapp打包的apk加弹窗的教程吗,用np一键加弹窗不生效,因为家人下载安装了一堆uniapp打包的资金盘apk, 时不时被忽悠往里投钱,什么税费,对接费,激活费,我动之以情,晓之以理,都没有说通他放弃,从去年和他吵到今年,快2年了,都没说服他。。。所以想给这些apk加上弹窗,比如在弹窗上面写“我是xx,感谢家人们多年的信息与追随,我已定居国外,感谢您们多年的信任与奉献,很遗憾,不得不告诉你们,我一直在骗你们,根据不存在xx项目,哈哈”。。。。。。。再覆盖安装到家里人的手机上,看看这种方法,能不能唤醒他
    Ly1988   

    视频晚上剪辑
    Skdf123   

    感谢分享,坐等
    FtsOZz   

    感谢分享,坐等
    如果我是DJ?   

    虽然有点看不懂但是好厉害
    hyxhhh826   

    flutter开发的app还是很丝滑的
    honwam   

    前排,已经跟不上正己大佬了
    leospring   

    大神出手了   厉害
    您需要登录后才可以回帖 登录 | 立即注册

    返回顶部