2024ISCC

2024ISCC

要么0解,要么千解,6

Reverse

迷失之门

思路

查壳发现有个奇怪的玩意image-20240502004402803

但是进入IDA后发现并不影响,输入传进check后判断image-20240502021003303

然后进行处理后再通过check_2进行判断image-20240502021050783

因此我们可以根据逻辑直接通过脚本爆破

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
encode = ['F', 'S', 'B', 'B', 'h', 'K', 'J', 'y', 'P', 'J', '2', 'e', 'G', 'P', 'S', 'k', 'L', 'X', 'y', 'c', 't', 'j', 'O', 'P', 'e', 'Q', '6']

passwd = 'DABBZXQESVFRWNGTHYJUMKIOLPC'
v15 = [0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A]
v9 = [0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A]
v3 = [0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x2B, 0x2F, 0x2D, 0x3D, 0x21, 0x23, 0x26, 0x2A, 0x28, 0x29, 0x3F, 0x3B, 0x3A, 0x2A, 0x5E, 0x25]

for i in range(27):
for ch in range(32, 127):
v21 = ch - ord(passwd[i])
if 0 < v21 <= 25:
ch1 = v15[v21]
elif 25 < v21 <= 51:
ch1 = v9[v21 - 26]
elif v21 > 51:
ch1 = v3[v21 - 52]
else:
continue
if chr(ch1) == encode[i]:
print(chr(ch), end='')
break
# ISCC{bZwb_|p]]YxSp|qznW^j`}

CrypticConundrum

解题思路

查壳发现UPX

image-20240502022248977

使用upx.exe -d Cryptic.exe脱壳后打开,得到主要逻辑image-20240502022555856

可以发现输入会先进入mix,再进入Encryption。但观察mix函数内部即可发现,其内部操作在输入长度为26时前后完全抵消。通过动态调试也可以验证image-20240502023245005

进入Encryption后以及再进入的NewEncryption就是一些基本的操作逻辑了,这里不再过多叙述,根据操作逻辑还原就行

exp

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
encode = [0x00, 0x1E, 0x39, 0xDB, 0x7C, 0xD0, 0xAD, 0x72, 0x58, 0xD7, 0xF9, 0x03, 0x6F, 0xC5, 0xA6, 0x8C, 0x29, 0x16, 0xB1, 0x66, 0x1E, 0xCD, 0x8A, 0xA5, 0x1A, 0x34]

a2 = [0x49, 0x53, 0x43, 0x43]

for i in range(len(encode)):
encode[i] -= 10
encode[i] &= 0xff

for i in range(len(encode) - 1):
encode[i] += encode[i + 1]
encode[i] &= 0xff

for i in range(len(encode)-1):
encode[i] ^= a2[2]

for i in range(0, len(encode), 2):
encode[i] ^= a2[i % 4]

for i in range(len(encode)):
encode[i] += a2[i % 4]
encode[i] &= 0xff

for c in encode:
print(chr(c), end='')
# ISCC{}N8ZR+`sga%jCFv&S^+y}

Badcode

解题思路

拖入IDA后可以留意到在输入flag后有一段神奇的检测,不影响后面程序运行,跳过

然后到下面对输入的奇偶数索引的字符分别进行一个变换。

image-20240502025022566紧接着就有一个函数,通过srand设置种子生成24个随机数与处理过的flag异或,在这里可以通过动调直接dump出来image-20240502025055843

image-20240502024618292

然后再往下看我们可以发现有个Tea加密(此处函数已重命名)

image-20240502024759081

循环次数在Tea里面通过计算得到。至此加密处理的逻辑理清

exp

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
from ctypes import *

key = [0x12345678, 0x9ABCDEF0, 0xFEDCBA98, 0x76543210]
encode = [0xEC5432D0, 0x6FC5FC7, 0x9D20D069, 0xAF6DBF25, 0xE3167389, 0x88E01452]
xor_num = [6, 7, 4, 0, 9, 4, 8, 7, 2, 0, 3, 8, 7, 7, 1, 1, 4, 8, 6, 6, 6, 7, 3, 7]

counter = 14
v8 = c_uint32(-0x61C88647*counter)
while counter > 0:
v6 = v8.value >> 2 & 3
encode[5] = encode[5] - (((encode[4] ^ key[v6 ^ 5 & 3]) + (encode[0] ^ v8.value)) ^ (((16 * encode[4]) ^ (encode[0] >> 3)) + ((4 * encode[0]) ^ (encode[4] >> 5)))) & 0xFFFFFFFF
for i in range(4, 0, -1):
encode[i] = encode[i] - (((encode[i - 1] ^ key[v6 ^ i & 3]) + (encode[i + 1] ^ v8.value)) ^ (((16 * encode[i - 1]) ^ (encode[i + 1] >> 3)) + ((4 * encode[i + 1]) ^ (encode[i - 1] >> 5)))) & 0xFFFFFFFF
encode[0] = encode[0] - (((encode[5] ^ key[v6 ^ 0 & 3]) + (encode[1] ^ v8.value)) ^ (((16 * encode[5]) ^ (encode[1] >> 3)) + ((4 * encode[1]) ^ (encode[5] >> 5)))) & 0xFFFFFFFF
v8.value += 0x61C88647
counter -= 1

result = []
for i in range(6):
temp = encode[i]
for j in range(4):
result.append(temp & 0xFF)
temp >>= 8

for i in range(len(result)):
result[i] ^= xor_num[i]

for i in range(len(result)):
if i % 2 == 0:
print(chr(result[i] + 3), end='')
else:
print(chr(result[i] - 2), end='')
# ISCC{3NScka9RRIDO8H3h@!}

DLLCode

解题思路

通过动态调试可知,输入首先通过索引的奇偶分成两部分进行存储。索引为偶数的部分被送入DLL中的Encode函数进行加密。通过对相应dll进行逆向可以发现关键代码

image-20240502203900531

此处为对输入与"ISCC"进行异或并返回

然后索引为奇数的部分被送入sub_B314D0进行处理,追踪该函数可发现仅为字符顺序调换。然后下面的函数对上面奇偶两部分前后拼接在一起,最后进行对比判断

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
enc = [0x00000000, 0x00000010, 0x00000038, 0x00000014, 0x00000011, 0x00000023, 0x0000003B, 0x00000004, 0x0000001B, 0x00000000, 0x00000014, 0x00000003, 0x00000043, 0x00000059, 0x00000053, 0x00000059, 0x00000065, 0x00000047, 0x00000061, 0x00000067, 0x00000074, 0x0000007D, 0x00000075, 0x00000062]
key = 'ISCC'

part1 = enc[0:12]
part2 = enc[12:24]

for i in range(12):
part1[i] ^= ord(key[i % 4])

part2_bak = part2.copy()
for i in range(12):
orig_string = 'SCacegikmoq}'
chang_string = 'CcSagkeio}mq'
part2[i] = part2_bak[chang_string.index(orig_string[i])]

for i in range(12):
print(chr(part1[i]), end='')
print(chr(part2[i]), end='')
# ISCC{YWYXapexgGGRuStWb@}

mobile

Puzzle_Game

解题思路

拖进Jadx观察函数逻辑,发现在MainActivity中首先通过Jformat函数校验flag格式开头ISCC{和结尾},然后将中间部分送入a.a作进一步校验

a中首先将传入内容分为两部分。前半部分要通过b函数的检验,而后半部分则要与whathappened库中getstr的返回值一致。然后两部分加起来的sha256要等于437414687cecdd3526281d4bc6492f3931574036943597fddd40adfbe07a9afa

先看前半部分。在进入b函数后,首先通过c函数要求输入字符串为8位长度,然后转换为整型通过get1函数和d函数的检验。get1函数要求数字开头为4,d函数要求数字为质数。然后在该数基础上+11得到一个新数,不满足get1函数和d函数的条件。由此我们筛到的数有[49999991, 4999999, 49991, 49993, 49999, 4993, 4999, 491, 499]

再看后半部分。这里可以通过IDA逆向库文件或动态调试获取str2得到,我选择动调。首先给apk开启调试权限image-20240502033210433

然后在Jadx对应函数位置下个断点image-20240502033406137

手机安装应用后在Jadx中启动并附加,运行到断点处,逐步运行直到得到参数

image-20240502033928182

此时再结合sha256的结果,可得flagISCC{04999999gwC9nOCNUhsHqZm},直接提交显示错误。留意到提示文字OH YES, ONE STEP AWAY FROM SUCCESS!,发现还有一个Receiver的类还没有用到。里面要求通过广播传入参数并通过sha256进行校验,对应传入参数就是前面解出来的两部分

image-20240502034536192

AndroidManifest.xml中找到活动为com.example.FINALFLAG,尝试通过adb发送广播adb shell am broadcast -a com.example.FINALFLAG --es EXTRA_PART1 04999999 --es EXTRA_PART2 gwC9nOCNUhsHqZm,无果

于是直接提取函数写成Java脚本

exp

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
import java.util.Base64;
import java.util.Random;
import java.nio.charset.StandardCharsets;

public class mobile {

public static String encrypt(String str, String str2) {
byte[] generateSalt = generateSalt(16);
byte[] customEncrypt = customEncrypt(combineStrings(str, str2).getBytes(StandardCharsets.UTF_8), generateSalt);
byte[] bArr = new byte[generateSalt.length + customEncrypt.length];
System.arraycopy(generateSalt, 0, bArr, 0, generateSalt.length);
System.arraycopy(customEncrypt, 0, bArr, generateSalt.length, customEncrypt.length);
return Base64.getEncoder().encodeToString(bArr);
}

private static byte[] generateSalt(int i) {
byte[] bArr = new byte[i];
new Random(3000L).nextBytes(bArr);
return bArr;
}

private static byte[] customEncrypt(byte[] bArr, byte[] bArr2) {
byte[] bArr3 = new byte[bArr.length];
for (int i = 0; i < bArr.length; i++) {
bArr3[i] = (byte) (bArr[i] ^ bArr2[i % bArr2.length]);
}
return bArr3;
}

public static String encrypt2(String str) {
byte[] bytes = str.getBytes(StandardCharsets.UTF_8);
for (int i = 0; i < bytes.length; i++) {
bytes[i] = (byte) ((bytes[i] + 127) % 256);
}
byte[] bArr = new byte[bytes.length];
for (int i2 = 0; i2 < bytes.length; i2++) {
bArr[i2] = (byte) (i2 % 2 == 0 ? bytes[i2] ^ 123 : bytes[i2] ^ 234);
}
return Base64.getEncoder().encodeToString(bArr);
}

private static String combineStrings(String str, String str2) {
return str + str2;
}
}

class Main {
public static void main(String[] args) {
String str = "04999999";
String str2 = "gwC9nOCNUhsHqZm";
System.out.println(mobile.encrypt2(mobile.encrypt(str, str2)).substring(0, 32));
// zhvOC88Er1m/C8xcyF2OIqwNvwCPUosg
}
}

得到后再套上flag格式即可ISCC{zhvOC88Er1m/C8xcyF2OIqwNvwCPUosg}

火眼金睛

解题思路

观察发现会传入一个参数到库函数getflag里面

image-20240502041747893

阅读getflag的反编译代码

image-20240502041830629

即可得到知道flag:999dtJFkgiuTWcFBdgeGh,套上格式得到ISCC{999dtJFkgiuTWcFBdgeGh}

本文作者:lrhtony
本文链接:https://lrhtony.cn/2024/05/01/2024ISCC/
版权声明:本文采用 CC BY-NC-SA 3.0 CN 协议进行许可