成绩

score

回顾

逆向有两题都是签到,Want2BecomeMagicalGirl非常复杂(对于目前的我),VideoPlayer没看,后面去做misc了,学长出了一道phishing email,我花了挺久做Githacker,但是卡死在不知道加密工具上,后来发现应该先做Voice_hacker的。

reverse

Catfriend

1
2
3
4
5
6
7
小猫猫和它的秘密,你可以找到猫猫的秘密吗?

Kitten cat and its secrets, can you find the secret of the cat?

百度网盘:https://pan.baidu.com/s/1Y7uZP6iLFgGyROetJCxxUw?pwd=utxb

Google Dirve: https://drive.google.com/file/d/1w_tj3ASPQtv5qtNOvk8306-H6ze7Jx99/view?usp=drive_link

附件是一个arm架构的exe,ida打开,可以在字符串(shift+F12)里面看见flag:

catfriend1

flag: WMCTF{5a3e8f2b-1c7d-4a6f-b89e-0d3c2f1a4b5c}

Appfriend

1
2
3
4
5
6
7
我们开发了一个名为SecureBox的安全存储应用,它使用了多层保护机制来保护用户数据。你能逆向分析并提取出被保护的秘密吗?

We have developed a secure storage application called SecureBox, which uses multiple layers of protection mechanisms to safeguard user data. Can you perform reverse engineering and extract the protected secrets?

百度网盘:https://pan.baidu.com/s/1CK9_2N8dnuUOleDKZ1lOTw?pwd=p5wu

Google Dirve: https://drive.google.com/file/d/169Il1QKd7AR6RtOOheR5NN-Lc46Cdrgh/view?usp=drive_link

一道简单的apk,jadx打开之后直接看源代码里面com.example.yellow包,发现so的导入:

1
2
3
static {
System.loadLibrary("yellow");
}

yellow包里面定义了一个checkflag函数,调用在com.google.android.material.datepicker包,关键代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
......
MainActivity mainActivity = (MainActivity) this.f2139b;
String strValueOf = String.valueOf(mainActivity.f1936x.getText());
if (!strValueOf.isEmpty()) {
if (strValueOf.length() == 32) {
if (!mainActivity.checkflag(strValueOf)) {
Toast.makeText(mainActivity.getApplicationContext(), "flag error!", 1).show();
break;
} else {
Toast.makeText(mainActivity.getApplicationContext(), "flag success!", 1).show();
break;
}
} else {
Toast.makeText(mainActivity.getApplicationContext(), "length error!", 1).show();
break;
}
} else {
Toast.makeText(mainActivity.getApplicationContext(), "empty!", 1).show();
break;
}
......

那么flag的加密和密文以及校验都在libyellow.so里面了,这里我选择x86_64架构的so进行分析。

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
__int64 __fastcall Java_com_example_yellow_MainActivity_checkflag(__int64 *a1, __int64 a2, __int64 a3)
{
__int64 v3; // rax
const char *v4; // r15
int v5; // eax
_BYTE *v6; // rbx
__int64 v7; // r12
int v8; // eax
int v9; // eax
unsigned int v10; // ecx
unsigned int v11; // r15d
__int64 v12; // rdx
__int64 v13; // rax
unsigned __int64 v14; // rdx
bool v15; // zf
bool v16; // cf
__int64 v18; // [rsp+0h] [rbp-170h] BYREF
__int64 v19; // [rsp+8h] [rbp-168h]
const char *v20; // [rsp+10h] [rbp-160h]
__int64 *v21; // [rsp+18h] [rbp-158h]
__int64 v22; // [rsp+20h] [rbp-150h]
char v23[264]; // [rsp+28h] [rbp-148h] BYREF
__int128 v24; // [rsp+130h] [rbp-40h] BYREF
unsigned __int64 v25; // [rsp+140h] [rbp-30h]

v25 = __readfsqword(0x28u);
v3 = *a1;
v22 = 0LL;
v19 = a3;
v4 = (*(v3 + 1352))(a1, a3, 0LL);
v5 = strlen(v4);
v21 = &v18;
v24 = byte_AE0;
v6 = &v18 - (((v5 + 16) + 15LL) & 0xFFFFFFFFFFFFFFF0LL);
v7 = v5;
v20 = v4;
memcpy(v6, v4, v5);
v8 = v7 + 15;
if ( v7 >= 0 )
v8 = v7;
v9 = (v8 & 0xFFFFFFF0) - v7;
v10 = v9 + v7 + 16;
v11 = v10;
v12 = v7 + 1;
if ( v7 + 1 <= v10 )
v12 = v10;
memset(&v6[v7], v9 + 16, v12 - v7);
sm4_setkey_enc(v23, &v24);
sm4_crypt_ecb(v23, 1LL, v11, v6, v6);
if ( *v6 == 0xDB )
{
v13 = -1LL;
while ( 1 )
{
if ( v6[v13 + 2] != byte_AF0[v13 + 2] || v6[v13 + 3] != byte_AF0[v13 + 3] || v6[v13 + 4] != byte_AF0[v13 + 4] )
{
v13 = 0LL;
goto LABEL_16;
}
if ( v13 == 43 )
break;
v14 = v13 + 4;
v15 = v6[v13 + 5] == byte_AF0[v13 + 5];
v13 += 4LL;
if ( !v15 )
{
v16 = v14 < 0x2F;
goto LABEL_15;
}
}
v13 = 47LL;
v16 = 0;
LABEL_15:
LOBYTE(v13) = !v16;
LABEL_16:
v22 = v13;
}
(*(*a1 + 1360))(a1, v19, v20);
if ( __readfsqword(0x28u) != v25 )
JUMPOUT(0x1615LL);
return v22;
}

使用了标准SM4进行加密,密文是byte_AF数组,密钥是byte_AE0数组,直接使用CyberChief进行解密。

appfriend1

Misc

Checkin

flag: wmctf{W3lc0m3_t0_wmctf2025_4nd_h4v3_funNnnNnNN!}

Questionnaire

flag: wmctf{Th4nk_u_4_supPorting_us!}

Voice_hacker(复现)

1
2
3
4
5
6
7
你能偷取他的声音吗?

Can you steal his voice?

百度网盘:https://pan.baidu.com/s/1ygNDlVCkRZZt6NKjGU-1xA?pwd=tzv8

Google Dirve: https://drive.google.com/file/d/1OTCqmrymIT85vJwJAWIl9s1elORY0-Rh/view?usp=drive_link

题目描述说需要窃取声音,启动容器发现交互界面是录音,要求说出”CTF,启动!”,感觉有种开放世界探索游戏的味道( ,F12查看网页源代码发现认证接口”/api/authenticate”,可以提交本地的音频。

voice1

附件是.pcap文件,用wireshark打开,发现全部都是UDP数据报。

voice2

上网查找UDP如何传输音频数据,参考了这篇文章 https://www.cnblogs.com/dier-gaohe/p/17716972.html ,需要将UDP数据报解码为RTP。然后进入RTP流(电话->RTP->RTP流)发现有一对流,只是IP相反,分析发现这两个流传输的数据相同,使用其中一个即可。进入Play Streams,用export功能将RTP的payload提取出来保存为voice.raw文件。

voice3

voice4

接下来将voice.raw文件解析成output.wav,这里我直接使用了deepseek给的命令:

1
sox -t ul -r 8000 -c 1 voice.raw output.wav

使用音乐播放器打开是一段80多秒的正常的对话,现在大概懂这道题的意思了,应该是使用音频内的人的音色来读出”CTF,启动!”,于是使用AI语音克隆工具进行音色克隆,再生成符合题意的音频.注意需要将.mp3转换为.wav,最后按接口进行提交得到flag。这里我使用的是 https://aivoicecloning.io/zh

1
curl -X POST http://81.70.100.5:30823//api/authenticate -F "audio=@audio_202509221158.wav"

响应如下

1
2
3
4
5
{
"flag": "WMCTF{01a9a4f1-e748-43fa-8d6d-bba372016adc}",
"message": "\u8ba4\u8bc1\u6210\u529f",
"success": true
}

Githacker

1
2
3
4
5
To be a GitHacker.

百度网盘:https://pan.baidu.com/s/18iCGk0EHLha1rUAwdMEKMA?pwd=y8c4

Google Dirve: https://drive.google.com/file/d/1sppWgVmrz8t0C6YeXqJIHKUCaPFgCc-U/view?usp=drive_link
1
2
3
View Hint: Hint 1:
file is not image
文件不是图片

这题目做一半卡死了,但只能说完成了1/3。
附件就是一个git仓库,当前的master分支只有一个README.md文件,没有可用信息,想到了查看历史commit。查看.git/logs/refs/heads/master.txt:

1
2
3
4
5
6
7
8
0000000000000000000000000000000000000000 6b6285cc1283144db890f1b27cc8c7b6ccd4b643 toto <toto@WMCTF2025.com> 1754730819 +0800	commit (initial): Init
6b6285cc1283144db890f1b27cc8c7b6ccd4b643 6ec92bcfdf3044bf21dcfa74500cbb929c0f0037 toto <toto@WMCTF2025.com> 1754730846 +0800 commit: encryptedFile
6ec92bcfdf3044bf21dcfa74500cbb929c0f0037 a026274fb418ec88af16444644fccab9b8a7e8dd toto <toto@WMCTF2025.com> 1754730880 +0800 commit: password
a026274fb418ec88af16444644fccab9b8a7e8dd 6ec92bcfdf3044bf21dcfa74500cbb929c0f0037 toto <toto@WMCTF2025.com> 1754730889 +0800 reset: moving to HEAD~1
6ec92bcfdf3044bf21dcfa74500cbb929c0f0037 93ab7b9e28a4d442ec77a3fb37d64912bbddfdad toto <toto@WMCTF2025.com> 1754731172 +0800 commit: change password
93ab7b9e28a4d442ec77a3fb37d64912bbddfdad d504bbf75693fc83f6cf5c873306b7fc67edd804 toto <toto@WMCTF2025.com> 1754731431 +0800 commit: encryptedFile
d504bbf75693fc83f6cf5c873306b7fc67edd804 93ab7b9e28a4d442ec77a3fb37d64912bbddfdad toto <toto@WMCTF2025.com> 1754731474 +0800 reset: moving to HEAD~1
93ab7b9e28a4d442ec77a3fb37d64912bbddfdad 484c0d313c560eb48986ef96690ef2f034addc90 toto <toto@WMCTF2025.com> 1754731529 +0800 commit: have a good time!

备注看起来很明显,第1个commit进行初始化,第2个commit提交了密文,第3个commit提交了密钥,第4个commit回退到上一次(HEAD~1表示第一个父提交即上一个提交),第5个commit提交修改后的密钥,第6个commit重新进行加密,后面又进行了退回。

依次回退到每个commit,只找到了第1次提交的密钥EasyP@ssw0rd_from_Git_History和第一个加密文件image.png以及第二个加密文件image.jpg,暂时不知道密钥用在哪,也没有看见修改后的密钥,估计挺多人和我一样到这里就没有进展了。这两个文件没有任何常见文件的可识别标识,提示说文件不是图片,我觉得可能是加密了,尝试了一些使用密钥的加密算法的逆向,直接操作文件原始数据,然并卵。

出题人说是使用了卷加密软件VeraCrypt,原来这两个文件是加密后的容器,这真无敌了。

打开VeraCrypt(这软件甚至有防截图),随便选择一个盘符,然后选择刚刚的那个image.png文件进行加载,输入密钥进行解密,使用文件管理器对该新增盘符进行查看,里面是一个flag.txt文件:

1
2
3
Congratulations! This is half of the gift I gave you:
WMCTF{G00d_J0b_F1nding_Th3_0ld_V3rsi0n_
Don't forget the way you came!Please keep trying to find the remaining gifts!

意思是另一段flag在image.jpg文件里面,但问题是不知道修改后的密钥是什么,使用原来的密钥也不正确。

这里读了Aristore师傅的WP:https://www.aristore.top/posts/WMCTF2025 ,知道了VeraCrypt的文档中有关于修改容器密钥的说明: https://veracrypt.io/zh-cn/Changing%20Passwords%20and%20Keyfiles.html

git1

意思就是加密卷的卷头是使用你的密码或者密钥文件的内容进行加密的,会包含用于加密整个卷的的主密钥,主密钥是一直不变的,不管你是否修改密码或者密钥文件的内容。那么可以在第一次解密image.png的时候备份加密卷头信息,第二次解密image.jpg的时候再用这份信息中的主密钥来挂载。

流程:卸载之前已经解密的容器,仍然选择image.png那个容器,加载之前在工具中选择备份加密卷头信息,输入之前的密钥,保存文件。选择image.jpg容器,加载之前在工具中选择恢复加密卷头信息,选择刚刚备份的文件,输入之前的密钥(就是使用备份时使用的密钥来恢复),在对容器进行加载,就成功解密了,使用文件管理器对该新增盘符进行查看,里面也是一个flag.txt文件:

1
2
3
4
5
Congratulations! You've solved it!
Figuring out the volume header manipulation trick is a sign of a true expert.
You've earned this. Well played!

And_Y0u_M4ster_The_VeraCrypt_H34der_Trick!}

flag: WMCTF{G00d_J0b_F1nding_Th3_0ld_V3rsi0n_And_Y0u_M4ster_The_VeraCrypt_H34der_Trick!}

知识点

RTP协议

这里简单讲一下实时数据传输协议RTP(Real-time Transport Protocol)的原理:

RTP协议是一种用于在互联网上传输音频和视频数据的应用层协议,它被设计用来支持实时多媒体通信应用,如VoIP、视频会议、流媒体等。以下是RTP数据报格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 0                   1                   2                   3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|V=2|P|X| CC |M| PT | Sequence Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Timestamp |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| SSRC Identifier |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| CSRC Identifiers |
| .... |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Payload Data |
| .... |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

这里我引用csdn上的一篇文章对RTP数据报中的字段作解释,原文链接:https://blog.csdn.net/Dreamandpassion/article/details/107525385

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
·V:RTP协议的版本号,占2位,当前协议版本号为2。

·P:填充标志,占1位,如果P=1,则在该报文的尾部将填充一个或多个额外的八位组,它们不是有效载荷的一部分。

·X:扩展标志,占1位,如果X=1,则在RTP报头后跟有一个扩展报头。

·CC:CSRC计数器,占 4位,指示CSRC 标识符的个数。主要用于多流组合传播,比如说5个人发送rtp到网关边缘,边缘把5个rtp转发给另外一个边缘,然后解出原始流。

·M: 标记,占1位,不同的有效载荷有不同的含义,对于视频,标记一帧的结束;对于音频,标记会话的开始。

·PT: 有效载荷类型,占7位,用于说明RTP报文中有效载荷的类型,如GSM音频、JPEM图像等。

·序列号:占16位,用于标识发送者所发送的RTP报文的序列号,每发送一个报文,序列号增1。接收者通过序列号来检测报文丢失情况,重新排序报文,恢复数据。

·时戳 (Timestamp):占32位,时戳反映了该RTP报文的第一个八位组的采样时刻。接收者使用时戳来计算延迟和延迟抖动,并进行同步控制。

·同步信源(SSRC)标识符:占32位,用于标识同步信源。该标识符是随机选择的,参加同一视频会议的两个同步信源不能有相同的SSRC。

·特约信源(CSRC)标识符:每个CSRC标识符占32位,可以有0~15个。每个CSRC标识了包含在该RTP报文有效载荷中的所有特约信源。

重点:其中PT(9-15位,占7位)说明了payload类型,有如下类型:

1
2
3
4
5
6
7
8
PT值	编码格式	             描述	            采样率
0 PCMU (G.711 μ-law) 北美标准电话音频 8 kHz
8 PCMA (G.711 A-law) 欧洲标准电话音频 8 kHz
9 G.722 高质量语音编码 16 kHz
18 G.729 压缩语音编码 8 kHz
26 JPEG 视频:Motion JPEG -
31 H.261 视频编码 -
33 MP2T MPEG-2传输流 -

那么对于这道题,前面我无脑使用了deepseek给的命令,但其实是默认了使用的是μ-law格式,即G.711 μ-law编码,正常来说应该先查看RTP的头部字段来进行判断,查看方法有很多。

在wireshark中可以直接点击UDP报文查看RTP头部字段信息:

voice5

也可以追踪UDP流:

voice6

或者也可以使用tshark提取UDP搭载的RTP数据(UDP的payload),输出指定字段——data,也就是原始的十六进制数据:

1
tshark -r out.pcap -T fields -e data > udp_data.txt

我这里的分析参考了Aristore师傅的WP,原文链接:https://www.aristore.top/posts/WMCTF2025/

截取前面几条,可以看到所有包的中前2个字节(表示前16位)都是80 00,转换成二进制表示RTP头部字段前16为1000 0000 0000 0000,那么对应的PT(9-15位)就是0,表示payload格式为PCMU(G.711 μ-law),采样率为8kHz;次两个字节16位二进制表示序列号,可以发现这些数据包是连续传输的,属于一次数据发送的不同数据段;再接着4个字节,即SSRC,都是0x12345678,说明这些报文来自同一个音频流。这里可以发现前面一半的RTP数据报的SSRC是0x12345678,后面一半的是0x87654321,说明是另一个IP发来的。

1
2
3
4
5
800000000000000012345678fdfbfbfbfcfdfefdfdfdfcfcfdfdfdfdfdfdfdfdfdfefdfdfefefefefffefefefefefefefefefefefefefefefffeffff7e7e7e7eff7efffefefefdfefefefeffffff7e7e7e7e7e7e7e7efffefefefefefefefefeffff7e7e7e7d7e7e7efffffefefdfefdfefefeff7e7e7e7e7e7e7e7efffffefefefdfdfefeffff7e7e7d7d7d7e7e7efffefefefdfdfefefefeff7e7e7d7d7d7d7e7e7efffefefdfdfdfdfdfe
80000001000000a012345678fffeff7e7d7d7d7d7d7e7efffefdfdfdfdfdfdfdfeffff7e7d7d7d7d7e7e7efffefefdfdfdfdfefefeffff7e7e7e7e7e7e7efffefefefdfefdfdfefefeff7e7e7e7e7e7e7e7efffefefefefefefefeffffffff7e7e7eff7e7efffefefefefefefefefeffffff7e7e7e7e7e7e7e7efffefefefefefefefefeffff7e7e7e7e7e7e7e7efffefefefefefefefeffffffff7e7e7e7e7e7e7efffffefefefefefefefe
800000020000014012345678feffff7e7e7e7e7e7efffffffefefefdfdfefefeffff7e7e7e7d7d7e7efffffefefdfdfdfdfefeffff7e7e7e7e7e7efffefefefefdfdfdfdfeffff7e7e7d7d7e7e7e7efffefefdfdfdfdfefeffff7e7e7e7d7e7e7efffefefefdfdfdfefefeff7e7e7d7d7e7e7e7efffefefdfdfdfdfdfeff7e7e7d7d7d7d7e7e7efffefefdfdfcfdfdfeff7e7e7d7d7d7d7d7e7efefefdfdfcfcfdfdfeff7e7d7d7c7c7d7d7e
80000003000001e012345678fffffefdfdfcfcfcfdfeff7e7d7c7c7c7c7d7e7efefdfcfbfbfcfcfeff7e7d7c7b7b7c7c7e7efefdfcfbfbfbfbfcfdff7d7c7b7a7a7b7c7dfffdfbfafafafbfcfdff7d7c7a7a7a7b7c7d7efefdfcfbfafafbfcfeff7d7c7b7b7b7b7c7d7efefdfcfbfbfcfcfdfe7e7e7d7c7c7b7c7d7d7efefdfdfcfcfbfcfdfdfe7e7e7d7c7c7c7d7efffdfcfbfcfcfdfeff7e7e7e7d7d7e7d7d7e7efefdfcfbfcfcfdff7e7d
........

可以直接使用sox提取音频:

1
2
sox -t ul -r 8000 -c 1 voice.raw output.wav
# 表示提取PCMU格式的文件

参数-t表示输入文件的类型,-r表示采样率,-c表示声道数。可以自行查阅不同RTP payload对应的参数,这里不一一列出。