XCTF Final 2021

酒店还不错,就是地方有点偏,希望以后能在交通更方便一点的地区举办。这次拿了第 11 名,还算是个不错的结果,虽然还是有几道题比较遗憾。

Reverse

逆向题目还是有点阴间的,不过整体质量不错。这次只完整做出了一道逆向题,那个 lambda_v2 建议去看 Nu1L的解析,太可怕了。

apk

Android 逆向题,提示只支持特定版本的系统,说明多半动了 libart.so 里面的函数(Android加壳的常规操作,动态加载或修改 dex)。主要的字符串均进行了 XOR 加密,可以在 .init_array 找到对应的代码,批量解密。 .init_array 段,包含字符串和反调试初始化 XOR 解密示例

原生代码内有丰富的反调试操作(应该是从其他的库里面搬来的,但是没找到出处):

  • /data/local/tmp 内是否存在 android_server, gdb_server, frida_server
  • /proc/self/maps 里面是否有异常项
  • Java 层是否有附加调试器,当前进程是否被 ptrace
  • 指令执行时间是否正常
  • 包名是否被修改

加载 libart.so,对一个重要的脱壳点进行了 Hook(这里判断基于脱壳的经验以及对函数里面一些 magic number 的搜索): Hook 初始化 寻找目标函数 替换的函数,存储字节码偏移并设置权限

在到处搜索的时候找到了这篇文章,题目的结构非常类似,对后续的分析很有帮助:KCTF 2020 Win. 第四题 路在何方

JNI_OnLoad 的最后,需要调用的 JNI 函数被注册。

回到 Java 层,发现找不到调用那个原生函数的代码,注意到 assets/icon.png 被 XOR 之后作为字节码加载,同样方式处理后打开,找到调用点和加密主体,主要流程是 TEA - Native - TEA加载隐藏的字节码 处理主体,存在于 icon.png 中

原生层的加密可以很简单地看出是单块的 AES 加密,密钥为 wonderfulday!!!!。这个函数还做了两件事:修改了比对用的最终结果,以及字节码里面的一些引用和数值。

WTF Moment

1
2
3
4
5
6
7
8
19c19
< sum += 305419896;
---
> sum += 1364423841;
64c64
< String ooxxooxxoo = "youaresoclever!!";
---
> String ooxxooxxoo = "zipMatcher";

从刚才的主体流程可以看到,输入在 AES 前后分别被 TEA 了一次,而对字节码的修改发生在第一次加密后,也就意味着两次加密所用的参数是不一样的,需要记住处理一下。参照维基百科上的 TEA 解密示例,写出解密脚本:

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
85
86
87
88
89
90
import java.util.Arrays;
import java.util.Base64;

class Main2 {
public static byte[] ooxx2(byte[] content, int offset, int[] ooxxooxxoo) {
int[] tempInt = byteToInt(content, offset);
int y = tempInt[0];
int z = tempInt[1];
int sum = 0x468acf00;
int a = ooxxooxxoo[0];
int b = ooxxooxxoo[1];
int c = ooxxooxxoo[2];
int d = ooxxooxxoo[3];

for (int i = 0; i < 32; i++) {
z -= (((y << 4) + c) ^ (y + sum)) ^ ((y >> 5) + d);
y -= (((z << 4) + a) ^ (z + sum)) ^ ((z >> 5) + b);
sum -= 305419896; // MODIFIED to 1364423841
}
tempInt[0] = y;
tempInt[1] = z;
return intToByte(tempInt, 0);
}

private static int[] byteToInt(byte[] content, int offset) {
int[] result = new int[(content.length >> 2)];
int i = 0;
for (int j = offset; j < content.length; j += 4) {
result[i] = transform(content[j + 3]) | (transform(content[j + 2]) << 8) | (transform(content[j + 1]) << 16)
| (content[j] << 24);
i++;
}
return result;
}

private static byte[] intToByte(int[] content, int offset) {
byte[] result = new byte[(content.length << 2)];
int i = 0;
for (int j = offset; j < result.length; j += 4) {
result[j + 3] = (byte) (content[i] & 255);
result[j + 2] = (byte) ((content[i] >> 8) & 255);
result[j + 1] = (byte) ((content[i] >> 16) & 255);
result[j] = (byte) ((content[i] >> 24) & 255);
i++;
}
return result;
}

private static int transform(byte temp) {
if (temp < 0) {
return temp + 256;
}
return temp;
}

public static byte[] ooxxoo2(byte[] info) {
String ooxxooxxoo = "youaresoclever!!"; // MODIFIED to zipMatcher
for (int j = 0; j < 16; j++) {
ooxxooxxoo = ooxxooxxoo + "!";
}
byte[] ooxxooxxooarray = ooxxooxxoo.getBytes();
int[] ooxxooxxooxx = new int[16];
for (int i = 0; i < 16; i++) {
ooxxooxxooxx[i] = ooxxooxxooarray[i];
}
if (info.length % 8 != 0) {
return null;
}
byte[] result = new byte[info.length];
for (int offset = 0; offset < result.length; offset += 8) {
System.arraycopy(ooxx2(info, offset, ooxxooxxooxx), 0, result, offset, 8);
}
return result;
}

public static void main(String[] args) {
System.out.println(b64encode(ooxxoo2(b64decode("IgMDcaHeDcHTRr1SUS7urw=="))));
System.out.println(b64encode(ooxxoo2(b64decode("2+8ufn3NDHPjUS+9bJHh6A=="))));
System.out.println(b64encode(ooxxoo2(b64decode("IZ3SMiyJ5dHBYY1lKbX33Q=="))));
System.out.println(b64encode(ooxxoo2(b64decode("rL3LW7nbPNmfvh96gzAfgg==")))); // flag{perfectjob}
}

public static String b64encode(byte[] data) {
return Base64.getEncoder().encodeToString(data);
}

public static byte[] b64decode(String enc) {
return Base64.getDecoder().decode(enc);
}
}

Web

没有过多参与,题目不出网导致了很多的不便(虽然还是可以做的),个人只看了这一道题。

dngs2010

TODO

作者

Harry Cheng

发布于

2021-06-02

更新于

2021-06-02

许可协议