ByteCTF2021决赛re writeup

ByteCTF 中部分逆向题的wp

BabyHeaven

​ 这下面是队友做的部分和写的 wp ⬇

​ 使用以下代码加载所给文件:

1
2
3
4
auto mem = VirtualAlloc(NULL, 0x4000, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
FILE* fp = fopen("BabyHeaven", "rb");
fread(mem, 1, 0x3476, fp);
((void (*)())mem)();

​ 调试可得程序使用VirtualAlloc函数分配了一段可执行的空间,并写入了自己的汇编代码来执行,直接将其中的二进制数字用正则表达式筛选出来,需要注意的是其中顺序有颠倒,写成二进制文件,构造函数,即可得到如下算法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
memset(v4, 0, 220);
v4[0] = 5;
v4[1] = 18;
v4[2] = 14;
v4[3] = 23;
v4[4] = 9;
v4[5] = 15;
v4[6] = 4;
v4[7] = 21;
v4[8] = 10;
v4[9] = 20;
v4[10] = 19;
v4[11] = 25;
v4[12] = 24;
v4[13] = 22;
v4[14] = 12;
v4[15] = 16;
v4[16] = 2;
v4[17] = 17;
v4[18] = 7;
v4[19] = 1;
v4[20] = 8;
v4[21] = 11;
v4[22] = 6;
v4[23] = 13;
v4[24] = 3;
memset(v3, 0, 220);
v3[0] = 5;
v3[1] = 18;
v3[2] = 14;
v3[3] = 23;
v3[4] = 11;
v3[5] = 17;
v3[6] = 12;
v3[7] = 4;
v3[8] = 25;
v3[9] = 24;
v3[10] = 1;
v3[11] = 20;
v3[12] = 19;
v3[13] = 15;
v3[14] = 13;
v3[15] = 10;
v3[16] = 6;
v3[17] = 21;
v3[18] = 7;
v3[19] = 22;
v3[20] = 8;
v3[21] = 3;
v3[22] = 9;
v3[23] = 2;
v3[24] = 16;
v5 = 25;
*a1 = 0i64;
do
{
++*a1;
result = (unsigned int)(v5 - 1);
for ( i = v5 - 1; i > 0; --i )
{
result = (unsigned int)v4[i - 1];
if ( v4[i] > (int)result )
break;
}
if ( i <= 0 )
break;
for ( j = v5 - 1; j >= i && v4[j] <= v4[i - 1]; --j )
;
v2 = i - 1;
v4[i - 1] ^= v4[j];
v4[j] ^= v4[v2];
v4[i - 1] ^= v4[j];
for ( j = v5 - 1; i < j; --j )
{
v4[i] ^= v4[j];
v4[j] ^= v4[i];
v4[i++] ^= v4[j];
}
for ( i = 0; i < v5 && v4[i] == v3[i]; ++i )
;
result = (unsigned int)i;
}
while ( i < v5 );
return result;

​ 并通过所给文件的最后几行

​ 推测是上述算法的运行次数转ASCII就是flag

​ 求运行次数这部分是我写的脚本⬇

​ 通过找每一次执行的输出结果的规律,得到计算执行次数的方法

​ 这一段可以看出要从序列末尾 11 3 6 8 13 变为 13 3 6 8 11 需要执行的次数是 4!,即一个数变成后面序列中比它稍大的数(只大一个),并且后面的序列为升序的次数是后面序列数字个数的阶乘

​ 那就可以算出从 5, 18, 14, 23, 9, 1, 2, 3, 4, 6, 7, 8, 10, 11, 12, 13, 15, 16, 17, 19, 20, 21, 22, 24, 25 变化到 5, 18, 14, 23, 11, 1, 2, 3, 4, 6, 7, 8, 9, 10, 12, 13, 15, 16, 17, 19, 20, 21, 22, 24, 25 的次数是 2*20!

​ 之后减去和增加剩余的部分就可以得到最终执行的次数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import math

dst = [5, 18, 14, 23, 9, 15, 4, 21, 10, 20, 19, 25, 24, 22, 12, 16, 2, 17, 7, 1, 8, 11, 6, 13, 3]
src = [5, 18, 14, 23, 9, 1, 2, 3, 4, 6, 7, 8, 10, 11, 12, 13, 15, 16, 17, 19, 20, 21, 22, 24, 25]
dst2 = [5, 18, 14, 23, 11, 17, 12, 4, 25, 24, 1, 20, 19, 15, 13, 10, 6, 21, 7, 22, 8, 3, 9, 2, 16]
src2 = [5, 18, 14, 23, 11, 1, 2, 3, 4, 6, 7, 8, 9, 10, 12, 13, 15, 16, 17, 19, 20, 21, 22, 24, 25]

result = 2*math.factorial(20)
ans = 0
factor = 19
for i in range(5,len(src)):
ans+=(src.index(dst[i])-i)*math.factorial(factor)
factor-=1
for j in range(src.index(dst[i]),i,-1):
src[j]=src[j-1]
src[i]=dst[i]

ans2 = 0
factor = 19
for i in range(5,len(src2)):
ans2+=(src2.index(dst2[i])-i)*math.factorial(factor)
factor-=1
for j in range(src2.index(dst2[i]),i,-1):
src2[j]=src2[j-1]
src2[i]=dst2[i]

print(hex(result - ans + ans2))

#'Qw021zbG'

ByteService(未完成)

这题并没有做出来,还差最后的 java 逆向,这里先记录一下已有的进度,然后就等官方 wp 了

​ 首先看 apk 反编译结果发现是实现了一个进程通信,反编译出来的是客户端代码,而服务端是在题目描述里下载得到的sdk

Android Studio 下一个版本对应的 sdk,用从 xml 下载得到的 zip 替换已有 sdk 目录下的system-images 里的文件,开模拟器打开 apk 测试可以得到

​ 输入是16个字符的时候能够得到最底下显示的内容(apk实际上没有调用 checkCTFService 函数)

adb shell service list可以查看开的系统服务,这里可以看到自定义了一个服务

​ 查资料可以知道 /system/framework 目录下存的是 sdk 核心文件,services.jar 里是系统服务的代码。找到 com.android.server.os.ByteCTFService 可以看到 flag 的验证部分,而com.bytedance.bytectf.Aandroid.os.IByteCTFService 这两个类在 framework.jar 里面

​ 之后就是逆向的过程,使用了 java 的 lambda 函数,然后那一堆函数就看不懂了

参考资料

作者

0wl

发布于

2021-12-13

更新于

2021-12-13

许可协议

评论