[ByteCTF 2024]babyAPK
本文最后更新于7 天前,其中的信息可能已经过时,如有错误可以直接在文章下留言

参考资料:

吾爱破解安卓逆向入门教程《安卓逆向这档事》番外实战篇3-拨云见日之浅谈Flutter逆向_哔哩哔哩_bilibili

ByteCTF Re WP – 吾爱破解 – 52pojie.cn

无题目描述

是谷歌的Flutter框架写的APP,这里应用程序的图标其实就是Flutter的图标,也算一个提示了,但是当时不懂,还按正常的安卓程序分析,想想就觉得傻,这种如果不知道框架那咋样都不会了。

用Jadx、Jeb反编译不出啥源代码

关于Flutter的一些特征,如下

有上面两张图中导入的包import io.flutter.embedding.android.f;

assets文件夹会有dexopt、flutter_assets两个文件夹

lib文件夹会有 libapp.so 和 libflutter.so 两个文件夹

这里说一般不会libflutter.so,一般程序逻辑可能会在libapp.so里面。但是这题有三个so文件,多了一个librust_lib_babyapk.so

Flutter逆向这里要用到blutter,上面参考资料都有关于这个怎么使用,在blutter输出的内容中

  • asm:存放对dart语音的反编译结果,有很多dart源代码的对应偏移
  • ida_script: so文件的符号表还原脚本
  • blutter_frida.js:目标应用程序的frida脚本模板
  • objs.txt:对象池中对象的完整(嵌套)存储,对象池里面的方法和相应的偏移量
  • pp.txt:对象池中的所有dart对象

我们这里看输出的main.dart,在_ test()函数可以找到部分有关flag的逻辑

这里是ARM汇编应该,有点难看,直接把这段函数的指令丢给DeepSeek分析,它能分析出这里先判断输入是不是ByteCTF{ }的格式,并且也判断了字符串总长度是不是45。

并且调用m3N4B5V6函数验证flag的中间部分,也就是中间的36字节。

并且指示了package,这个simple.dart也是blutter输出的文件,但是看了之后没什么发现,只是又提示了一个dart文件

根据前面多出来的那个so文件,和这里的simple.dart的指令,不难推测出,这题和Rust是有关的,那个多出来的so文件也就很可能有关键逻辑。

后面看这个frb_generated.dart也没看出什么重要线索,而这个simple.dart和frb_generated.dart像是和Rust的Lib库进行交互的。

IDA打开librust_lib_babyapk.so文件,然后通过查看该字符串的交叉引用

我们可以找到关键的逻辑

这里一眼就是用z3解了,因为很多等式的判断,并且代码中有对字符 ‘-‘ 的判断,总共有四处,所以flag里面应该是有4个 ‘-‘

这里的这个关键函数动态调试也是没什么问题的,可以发现传入函数的第一个参数a1就是我们输入的flag的括号中间的长度为36的字符串

那这里可以动调的话,分析就没啥难度了,直接贴别人的解题脚本。

from z3 import *
import itertools
char = [''] * 4
for j in range(4):
    flag = [BitVec(f'flag[{i}]', 32) for i in range(8)]
    data = [0x0001EE59, 0x0000022A, 0x00001415, 0x00040714, 0x000013E0, 0x000008B8, 0xFFFDCEA0, 0x0000313B, 0x0003D798,
            0xFFFFFE6B, 0x00000C4E, 0x00023884, 0x0000008D, 0x00001DB4, 0xFFFC1328, 0x00001EAC, 0x00043C64, 0x0000142B,
            0xFFFFF622, 0x00023941, 0xFFFFEF6D, 0x0000120C, 0xFFFBD30F, 0x00001EBE, 0x00045158, 0xFFFFEF66, 0x00001D3F,
            0x0004C46B, 0xFFFFF97A, 0x00001BFD, 0xFFFBA235, 0x00001ED2]
    s = Solver()
    for i in range(8):
        s.add(flag[i] >= 0, flag[i] <= 0x10FFFF)
    v45 = flag[2]
    v44 = flag[3]
    v46 = flag[0]
    v47 = flag[1]
    v48 = flag[4]
    v49 = flag[5]
    v50 = flag[6]
    v51 = flag[7]
    v52 = v46 * v49
    v53 = v48 * v46
    v54 = v44 - v51 - (v47 + v49)
    s.add(v51 + v47 * v44 * v49 - (v46 + v50 + v45 * v48) == data[0 + j * 8])
    s.add(v44 - v48 - v46 * v49 + v51 * v47 + v45 + v50 == data[1 + j * 8])
    s.add(v52 - (v48 + v51 * v47) + v45 + v50 * v44 == data[2 + j * 8])
    s.add(v47 + v48 * v46 - (v51 + v45) + v50 * v49 * v44 == data[3 + j * 8])
    s.add(v49 * v44 + v47 + v45 * v48 - (v50 + v51 * v46) == data[4 + j * 8])
    s.add(v52 + v47 * v44 + v45 - (v50 + v48 * v51) == data[5 + j * 8])
    s.add(v51 - v47 + v45 * v49 + v50 - v53 * v44 == data[6 + j * 8])
    s.add(v54 + v53 + v50 * v45 == data[7 + j * 8])
    if s.check() == sat:
        m = s.model()
        inp = [m.evaluate(flag[i]).as_long() for i in range(8)]
        char[j] = ''.join([chr(inp[i]) for i in range(8)])
    #     print(inp)
byte_18E46 = [
    0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
    0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
    0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
    0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
    0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
    0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
    0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
    0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
    0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
    0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
    0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
    0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
    0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
    0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
    0x02, 0x02, 0x02, 0x02, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
    0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
    0x04, 0x04, 0x04, 0x04, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
def check(input_str):
    a1 = [ord(i) for i in input_str]
    v2 = byte_18E46[a1[0]]
    if v2 == 36:
        return 0
    v3 = byte_18E46[a1[v2]] + v2
    if v3 == 36:
        return 0
    v4 = v3 + byte_18E46[a1[v3]]
    if v4 == 36:
        return 0
    v5 = v4 + byte_18E46[a1[v4]]
    if v5 == 36:
        return 0
    v6 = v5 + byte_18E46[a1[v5]]
    if v6 == 36:
        return 0
    v7 = v6 + byte_18E46[a1[v6]]
    if v7 == 36:
        return 0
    v8 = v7 + byte_18E46[a1[v7]]
    if v8 == 36:
        return 0
    v9 = v8 + byte_18E46[a1[v8]]
    if v9 == 36:
        return 0
    v10 = a1[v9]
    if v10 != ord('-'):
        return 0
    v12 = v9 + byte_18E46[a1[v9]]
    if v12 == 36:
        return 0
    v13 = v12 + byte_18E46[a1[v12]]
    if v13 == 36:
        return 0
    v14 = v13 + byte_18E46[a1[v13]]
    if v14 == 36:
        return 0
    v15 = v14 + byte_18E46[a1[v14]]
    if v15 == 36:
        return 0
    v16 = v15 + byte_18E46[a1[v15]]
    if v16 == 36:
        return 0
    v18 = a1[v16]
    if v18 != ord('-'):
        return 0
    v20 = v16 + byte_18E46[a1[v16]]
    if v20 == 36:
        return 0
    v21 = v20 + byte_18E46[a1[v20]]
    if v21 == 36:
        return 0
    v22 = v21 + byte_18E46[a1[v21]]
    if v22 == 36:
        return 0
    v23 = v22 + byte_18E46[a1[v22]]
    if v23 == 36:
        return 0
    v24 = v23 + byte_18E46[a1[v23]]
    if v24 == 36:
        return 0
    v25 = a1[v24]
    if v25 != ord('-'):
        return 0
    v27 = v24 + byte_18E46[a1[v24]]
    if v27 == 36:
        return 0
    v28 = v27 + byte_18E46[a1[v27]]
    if v28 == 36:
        return 0
    v29 = v28 + byte_18E46[a1[v28]]
    if v29 == 36:
        return 0
    v30 = v29 + byte_18E46[a1[v29]]
    if v30 == 36:
        return 0
    v31 = v30 + byte_18E46[a1[v30]]
    if v31 == 36:
        return 0

    v32 = a1[v31]
    if v32 != ord('-'):
        return 0
    return 1
str = char[0] + char[1] + char[2] + char[3]
table = list(itertools.combinations(range(len(str) + 1), 4))
c = 0
success = 0
for i in table:
    tmp = str
    for i, j in enumerate(i):
        tmp = tmp[:j + i] + '-' + tmp[j + i:]
    if check(tmp):
        print("ByteCTF{" + f"{tmp}" + "}")
#ByteCTF{32e750c8-fb21-4562-af22-973fb5176b9c}

所以flag就是ByteCTF{32e750c8-fb21-4562-af22-973fb5176b9c}

这一题主要是学习怎么对Flutter框架的应用程序进行逆向分析,学会blutter工具的使用

Flutter是用Dart语言进行开发的,这也是我们无法用Jeb、Jadx反编译出源码的原因

2024ByteCtf_babyapk分析flutter逆向 – 哔哩哔哩

根据上面这篇文章,我们用blutter输出的ida_script文件夹中的addNames.py脚本,可以恢复libapp.so的符号表。然后直接搜索APP的名字

主要逻辑会在main__MyHomePageState__test当中,函数代码如下

__int64 __fastcall babyapk_main__MyHomePageState::test_264c0c(__int64 a1, __int64 a2)
{
  __int64 v2; // x15
  __int64 v3; // x21
  __int64 v4; // x22
  DartThread *v5; // x26
  __int64 v6; // x27
  __int64 v7; // x28
  __int64 v8; // x29
  __int64 v9; // x30
  __int64 v10; // x29
  _QWORD *v11; // x15
  __int64 v12; // x1
  __int64 v13; // x2
  __int64 v14; // x2
  __int64 v15; // x2
  __int64 v16; // x0
  __int64 v17; // x0
  __int64 v19; // x0

  *(v2 - 16) = v8;
  *(v2 - 8) = v9;
  v10 = v2 - 16;
  if ( (v2 - 56) <= v5->stack_limit )
    StackOverflowSharedWithoutFPURegsStub_3bbe84(a1, a2);
  *(v10 - 8) = *&byte_7[*(&qword_20 + *(a2 + 19) + (v7 << 32) + 7) + (v7 << 32)] + (v7 << 32);
  if ( (dart_core__StringBase::startsWith_198d18() & 0x10) != 0
    || (v12 = *(v10 - 8),
        v13 = *(v12 + 7) >> 1,
        *(v10 - 16) = v13,
        v11[1] = 2 * (v13 - 1),
        v11[2] = v12,
        *v11 = *(v6 + 35392),
        (dart_core__StringBase::_substringMatches_198df8() & 0x10) != 0)
    || *(v10 - 16) != 45LL )
//对输入flag的长度和格式判断
  {
    (fluttertoast_fluttertoast_Fluttertoast::showToast_264d88)();
    return v4;
  }
  v14 = (*(v3 + 8 * ((*(*(v10 - 8) - 1LL) >> 12) - 4096LL)))();
  if ( (*(v14 + 11) >> 1) <= 1 )
  {
    v16 = (RangeErrorSharedWithoutFPURegsStub_3bc2cc)();
  }
  else
  {
    v15 = (*(v3 + 8 * ((*(*(&word_12 + *(v14 + 15) + (v7 << 32) + 1) + (v7 << 32) - 1) >> 12) - 4096LL)))();
    v16 = *(v15 + 11) >> 1;
    if ( v16 )
    {
      v17 = babyapk_src_rust_api_simple_::m3N4B5V6_265088(
              *(v15 + 15) + (v7 << 32),
              *&byte_9[*(v15 + 15) + 6 + (v7 << 32)] + (v7 << 32));
//调用的关键函数
      fluttertoast_fluttertoast_Fluttertoast::showToast_264d88(v17);
      return v4;
    }
  }
  v19 = RangeErrorSharedWithoutFPURegsStub_3bc2cc(v16);
  return fluttertoast_fluttertoast_Fluttertoast::showToast_264d88(v19);
//总共三个showToast函数,一个应该是输入格式和长度的错误提示,一个是flag不对的错误提示,另一个则应该一个正确提示

所以Flutter的主函数一般也会在So文件里面,这一题特殊在利用了Rust语言开发了一个So,然后在libapp.so里面调用Rust的库。如果没有利用Rust开发,那猜测简单Flutter的逆向的程序主逻辑就全在libapp.so里面了。

但是就算知道和librust_lib_babyapk.so这个So文件有关,通过那个字符串引用到关键逻辑还是有点难想到的。

2024 ByteCTF Reverse BabyAPK – lrhtony 的小站

上面这篇文章有介绍部分找关键函数的方法,作者是直接Hook所有函数,看输入。对于那些加密代码明显的函数,其实一个个翻So的函数好像也没毛病。

还有利用Frida报错来爆破调用链?这是什么技术,挖个坑改天研究一下。

文末附加内容

评论

  1. 1
    Windows Edge
    3 天前
    2025-4-28 20:30:24

    学习(☆ω☆)

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇