参考资料:
吾爱破解安卓逆向入门教程《安卓逆向这档事》番外实战篇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报错来爆破调用链?这是什么技术,挖个坑改天研究一下。
学习(☆ω☆)