2025数字中国APP赛道安全积分争夺赛决赛部分wp

数据加密

crackme

找到关键函数,因为不知道CCCrypt(0, 0, 1u对应什么算法,得到正确的key和iv后尝试解密

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
__int64 __fastcall verify_system_password(const void *a1)
{
_BYTE v2[4]; // [xsp+20h] [xbp-90h] BYREF
__int64 *v3; // [xsp+28h] [xbp-88h]
_BYTE *dataOut; // [xsp+30h] [xbp-80h]
int v5; // [xsp+3Ch] [xbp-74h]
int i; // [xsp+44h] [xbp-6Ch]
const char *v8; // [xsp+48h] [xbp-68h]
size_t dataOutMoved[3]; // [xsp+50h] [xbp-60h] BYREF
size_t dataOutAvailable; // [xsp+68h] [xbp-48h]
size_t v11; // [xsp+70h] [xbp-40h]
const void *v12; // [xsp+78h] [xbp-38h]
unsigned int v13; // [xsp+84h] [xbp-2Ch]
char iv[16]; // [xsp+88h] [xbp-28h] BYREF
char key[16]; // [xsp+98h] [xbp-18h] BYREF

v3 = &qword_1000140B0;
v12 = a1;
++qword_1000140B0;
v11 = strlen((const char *)a1);
dataOutAvailable = (v11 + 16) & 0xFFFFFFFFFFFFFFF0LL;
dataOutMoved[2] = (size_t)v2;
dataOut = &v2[-((dataOutAvailable + 15) & 0xFFFFFFFFFFFFFFF0LL)];
dataOutMoved[1] = dataOutAvailable;
dataOutMoved[0] = 0LL;
v8 = "1234561234561234";
for ( i = 0; i < 16; ++i )
{
++v3[1];
key[i] = encrypted_system_key[i] ^ v8[i];
iv[i] = encrypted_system_iv[i] ^ v8[i];
}
print_crypto_info();
if ( CCCrypt(0, 0, 1u, key, 0x10uLL, iv, v12, v11, dataOut, dataOutAvailable, dataOutMoved) )
{
++v3[2];
v13 = 0;
v5 = 1;
}
else
{
if ( dataOutMoved[0] == 16 )
{
LOBYTE(v13) = memcmp(dataOut, &encrypted_system_password, 0x10uLL) == 0;
v13 = (unsigned __int8)v13;
}
else
{
++v3[3];
v13 = 0;
}
v5 = 1;
}
return v13;
}

逆向工程

task_get_vip-1

题目要求是获取vip账密

会解密生成dex

先用adb shell pm list packages得到真正的包名com.example.wyy,而不是直接显示的cn.yongye.stub

用frida dump得到解密后的dex

或者运行后可以在 /data/user/0/com.example.wyy/files/yongye.dex 找到释放的 dex

搜索关键词会员

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
def rc4(data, key):
S = list(range(256))
j = 0
out = []

for i in range(256):
j = (j + S[i] + key[i % len(key)]) % 256
S[i], S[j] = S[j], S[i]

i = j = 0
for char in data:
i = (i + 1) % 256
j = (j + S[i]) % 256
S[i], S[j] = S[j], S[i]
out.append(char ^ S[(S[i] + S[j]) % 256])

return bytes(out)

a=[79, -46, 102, -12, -14, 20, -63, 54, -104, 78, 93, -97, -124, -83, -108, 45, -81, 112, -46, -119]
for i in range(len(a)):
if a[i]<0:
a[i]%=255
data = bytes(a)
key = b'92e3b9'
decrypted = rc4(data, key)
print(decrypted)
#b'qjaidlhlf15621149463'

实际上so层也能指向这个类

隐私合规

TaskPrivate(1)

发现des和base64

只找到了key,猜测key和iv是一样的

密文先from hex再解base64,最后des解密

**MD5{银行卡+密码}**后提交

TaskPrivate(2)

MainActivity里找到申请读取短信和打电话的权限

md5后得到flag

通信安全

ezapk-1

根据协议分级重点关注http,发现在获取apk

追踪tcp发现有apk,这里提取原始数据有个方便的点是直接全选然后把上面一部分删掉,手动复制比较费时间

把不可见字符删去后从流量包里提取出apk

s盒初始化,魔改点是s盒第一个元素是1,也就是说一开始要加1,这个点比赛的时候咋都没想到

后面就是正常逻辑

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
def rc4(data, key):
S = []
for i in range(256):
S.append((i+1)%256)
j = 0
out = []

for i in range(256):
j = (j + S[i] + key[i % len(key)]) % 256
S[i], S[j] = S[j], S[i]

i = j = 0
for char in data:
i = (i + 1) % 256
j = (j + S[i]) % 256
S[i], S[j] = S[j], S[i]
out.append(char ^ S[(S[i] + S[j]) % 256])

return bytes(out)


data = bytes([0xAF, 0x50, 0x68, 0xEC, 0x0A, 0x4D, 0x9F, 0x51, 0xAB, 0xC8,0xF8, 0x7F, 0x17, 0x72, 0xFD, 0x43, 0x43, 0xE8, 0xE6, 0x4C,0x60, 0xF7, 0xBA, 0xEE, 0xA8, 0x1E])
print(len(data))
key = bytes([0x11,0x22,0x33,0x44,0x55,0x66])
decrypted = rc4(data, key)
print(decrypted)
#b'flag{a2_b3_c4_d4_hahahaha}'

最后因为长度是31,而flag求出来26,一眼顶针把a2变成aa,这道题难绷的点在于队友通过动调so和patch内存得到了上面的flag,但是两个人一直没想通为啥是31,另外在调试的过程中确实出现了aaaa变成a4的情况


2025数字中国APP赛道安全积分争夺赛决赛部分wp
https://j1ya-22.github.io/2025/04/19/2025数字中国APP赛道安全积分争夺赛决赛部分wp/
作者
j1ya
发布于
2025年4月19日
许可协议