从bytectf中的一道题开始分析futter框架软件
孤恒 发表于 广东 历史精选 720浏览 · 2024-10-04 09:21

前言

bytectf只能说不亏是字节出的,三道移动端,一道序列化分析,一道……,总之windows一道没有。说回正题,Bytectf中babyAPK使用了flutter开发框架。这里引用google的介绍:Flutter 是 Google 开源的应用开发框架,仅通过一套代码库,就能构建精美的、原生平台编译的多平台应用。Flutter 代码可以直接编译成 ARM 或 Intel 平台的机器代码,以及 JavaScript 代码,确保了 Flutter 应用能够拥有原生平台的性能表现。部署到多种设备,只需要一份代码库,支持移动、网页、桌面和嵌入式设备。
也就是说futter其实是一种新的开发框架包括语言以及编译器还有链接器等等。flutter在安卓中基本已经将java层给抛弃了,观察下图就可以得知,这里的主入口函数继承与父类f,在父类f中实现的是对于futter的初始化,并没有像是正常app一样创建页面等行为。

也就是说,几乎所有的flutter代码都会在so中实现,在apk包中必然会有libapp.so以及libfutter.so。在安卓逆向中可以通过这两个so来分析app是否为flutter框架,以此来确定逆向的方向。

分析思路

在apk包中发现了libapp.so和libfutter.so之后基本可以确定为flutter框架编写的应用,我们可以使用github上的开源项目blutter(GitHub - worawit/blutter: Flutter Mobile Application Reverse Engineering Tool)或者reflutter(Impact-I/reFlutter: Flutter Reverse Engineering Framework (github.com))还原IDA符号表用于分析。通过还原的符号名去猜测函数作用,然后根据目的进行各类分析。

reflutter使用方法

reflutter的使用方法比较简单,根据github上面的提示可以知道具体使用方法。

安装

直接使用python安装

pip install reflutter

使用

在同目录下或者指定路径使用

reflutter test.apk

然后会让你选择:

Choose an option:

 1. Traffic monitoring and interception
 2. Display absolute code offset for functions

选项1是抓包流量监控

选项2是获取函数偏移地址

我们是要获取函数偏移以及函数执行流程,所以选择2,然后会让输入burp的ip,这个是用于抓包使用,我们可以忽略,在ip输入填入127.0.0.1即可,此时reflutter会重打包一遍apk。

注意:重打包后的apk没有进行签名,是无法安装使用的。github上作者使用的是uber-apk-signer项目进行签名,我们也可以使用MT管理器签名。

签名

使用uber-apk-signer进行签名:

java -jar uber-apk-signer-1.2.1.jar --allowResign -a release.RE.apk

签名后会生成一个release.RE-aligned-debugSigned.apk,将apk安装到手机中即可。

执行之后在app的私有目录(/data/data/包名)中可以发现生成了一个dump.dump文件。

注意:查看app私有目录需要有root权限。

blutter使用方法

安装

我这里使用的是windows环境,前置需要安装visual studio的组件

然后使用git将源码从github上面下载下来,切换到源码目录安装

python scripts\init_env_win.py

使用

然后启动visual studio中的命令行"x64 Native Tools Command Prompt",如果使用win11可以在搜索处直接搜索

然后输入

python blutter.py path/to/app/lib/arm64-v8a out_dir

在output目录下会有生成以下文件

这里使用IDA打开libapp.so,在左上角选择file->Script file...导入ida_script文件夹中的py脚本文件即可恢复符号

babyAPK题目分析

我们在上文中使用了Blutter恢复符号表

在函数窗口中搜索"main",可以发现有很多函数匹配

这个时候我们通过函数名来进行猜测其中的实现,我们注意到有一个babyapk$main__MyHomePageState::test_264c0c函数,猜测是主页面的实现

使用Frida hook其中两个函数可以得知,这里验证了前面的ByteCTF{以及限定flag长度为45

hook脚本如下:

function startsWith_hook(){
    var libapp = Process.findModuleByName("libapp.so")
    if(libapp){
        console.log("find so:", libapp.base)
        Interceptor.attach(libapp.base.add(0x198D18), {
            onEnter : function(args){
                console.log(hexdump(ptr(this.context.x0)))
                console.log(hexdump(ptr(this.context.x1)))
                console.log(hexdump(ptr(this.context.x2)))
            }, 
            onLeave : function(retval){

            }
        })
    }
}

function toast_hook(){
    var libapp = Process.findModuleByName("libapp.so")
    if(libapp){
        console.log("find so:", libapp.base)
        Interceptor.attach(libapp.base.add(0x264d88), {
            onEnter : function(args){
                // console.log(hexdump(ptr(this.context.x0)))
                // console.log(hexdump(ptr(this.context.x1)))
                // console.log(hexdump(ptr(this.context.x2)))
                console.warn("[toask from]", this.context.lr.sub(libapp.base))
            }, 
            onLeave : function(retval){

            }
        })
    }
}

function main(){
    startsWith_hook()
    toast_hook()
}

setImmediate(main)
// com.example.babyapk

然后通过函数名猜测是使用了另一个so来执行检测flag

使用IDA连接手机进行动调,对librust_lib_babyapk.so中的导出表的所有函数下断点

然后输入测试,发现在frb_pde_ffi_dispatcher_sync函数中断了下来

一直动调跟进去

继续跟入第一个函数

还是第一个函数

到这里就是检测flag函数

进入后发现还有一个"-"的检测,可以得出flag格式类似于 ByteCTF{12345678-7654-2112-4567-876543211234}

这里这一块是提取非"-"的部分,也就是说12345678-7654-2112-4567-876543211234之中去除"-"之后的内容,12345678765421124567876543211234

这里就是最后的检测

此处可以使用z3解出内容,脚本如下:

from z3 import *

data = [
    0x1EE59, 0x22A, 0x1415, 0x40714, 0x13E0, 0x8B8, 0xFFFDCEA0, 0x313B,
    0x3D798, 0xFFFFFE6B, 0xC4E, 0x23884, 0x8D, 0x1DB4, 0xFFFC1328, 0x1EAC,
    0x43C64, 0x142B, 0xFFFFF622, 0x23941, 0xFFFFEF6D, 0x120C, 0xFFFBD30F,
    0x1EBE, 0x45158, 0xFFFFEF66, 0x1D3F, 0x4C46B, 0xFFFFF97A, 0x1BFD, 0xFFFBA235, 0x1ED2
]

def decrypt(i):
    s = Solver()
    v46, v47, v45, v44, v48, v49, v50, v51 = BitVecs("v46 v47 v45 v44 v48 v49 v50 v51", 8)
    s.add(And(48 <= v46, v46 <= 127))
    s.add(And(48 <= v47, v47 <= 127))
    s.add(And(48 <= v45, v45 <= 127))
    s.add(And(48 <= v44, v44 <= 127))
    s.add(And(48 <= v48, v48 <= 127))
    s.add(And(48 <= v49, v49 <= 127))
    s.add(And(48 <= v50, v50 <= 127))
    s.add(And(48 <= v51, v51 <= 127))
    s.add((v51 + v47 * v44 * v49 - (v46 + v50 + v45 * v48)) & 0xffffffff == data[i * 8])
    s.add((v44 - v48 - v46 * v49 + v51 * v47 + v45 + v50) & 0xffffffff == data[i * 8 + 1])
    s.add((v46 * v49 - (v48 + v51 * v47) + v45 + v50 * v44) & 0xffffffff == data[i * 8 + 2])
    s.add((v47 + v48 * v46 - (v51 + v45) + v50 * v49 * v44) & 0xffffffff == data[i * 8 + 3])
    s.add((v49 * v44 + v47 + v45 * v48 - (v50 + v51 * v46)) & 0xffffffff == data[i * 8 + 4])
    s.add((v46 * v49 + v47 * v44 + v45 - (v50 + v48 * v51)) & 0xffffffff == data[i * 8 + 5])
    s.add((v51 - v47 + v45 * v49 + v50 - v48 * v46 * v44) & 0xffffffff == data[i * 8 + 6])
    s.add((v44 - v51 - (v47 + v49) + v48 * v46 + v50 * v45) & 0xffffffff == data[i * 8 + 7])
    if s.check()==sat:
        print(s.model()[v46], end=",")
        print(s.model()[v47], end=",")
        print(s.model()[v45], end=",")
        print(s.model()[v44], end=",")
        print(s.model()[v48], end=",")
        print(s.model()[v49], end=",")
        print(s.model()[v50], end=",")
        print(s.model()[v51], end=",")
    else:
        print("无解")

转化一下最后得出flag:ByteCTF{32e750c8-fb21-4562-af22-973fb5176b9c}

附件:
0 条评论
某人
表情
可输入 255

没有评论