AlexCTF-Writeup

一个由埃及亚历山大大学的学生俱乐部MSP Teach Club组织的CTF比赛,办得不错。第一次参加外国的CTF比赛,感觉出题的有些思路跟国内真是不一样。由于运维难度比赛主办没出PWN和WEB的题:D.最后还是有几道题没做出来,看各种大神全做完也是很心累。

Reverse Engineering

RE1:Gifted

一个32位ELF,运行要求输入flag。strings一下直接出结果。非常符合题目名。
gifted flag

RE2:C++ is awesome

64位ELF文件,跟题目描述一样是由c++写的。拖到ida里F5一下,主要流程在400B89h处

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
__int64 __fastcall sub_400B89(int argc, __int64 a2)
{
__int64 v2; // rbx@2
__int64 v3; // rax@2
__int64 v4; // rax@2
__int64 v5; // rax@4
__int64 v6; // rax@5
__int64 v7; // rax@6
__int64 i; // [sp+10h] [bp-60h]@4
char v10; // [sp+20h] [bp-50h]@4
char v11; // [sp+4Fh] [bp-21h]@4
__int64 v12; // [sp+50h] [bp-20h]@5
int v13; // [sp+5Ch] [bp-14h]@4
if ( argc != 2 )
{
v2 = *a2;
LODWORD(v3) = std::operator<<<std::char_traits<char>>(6299968LL, 4198153LL);
LODWORD(v4) = std::operator<<<std::char_traits<char>>(v3, v2);
std::operator<<<std::char_traits<char>>(v4, 4198161LL);
exit(0);
}
std::allocator<char>::allocator(&v11);
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::basic_string(&v10, *(a2 + 8), &v11);
std::allocator<char>::~allocator(&v11);
v13 = 0;
LODWORD(v5) = std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::begin(&v10);
for ( i = v5; ; sub_400D7A(&i) ) // Iterator迭代输入字符
{
LODWORD(v6) = std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::end(&v10);
v12 = v6;
if ( !sub_400D3D(&i, &v12) ) // 判断输入是否结束
break;
v7 = sub_400D9A(&i);
if ( *v7 != off_6020A0[dword_6020C0[v13]] ) // 根据6020C0h处数组从6020A0h字符串中选择字符与输入对比,不符合退出
sub_400B56();
++v13;
}
sub_400B73();
std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::~basic_string(&v10);
return 0LL;
}

第35行将输入与flag对比,通过在6020C0h的字符串和6020C0h处的数组选出flag

re2.py
1
2
3
4
5
6
7
d = "L3t_ME_T3ll_Y0u_S0m3th1ng_1mp0rtant_A_{FL4G}_W0nt_b3_3X4ctly_th4t_345y_t0_c4ptur3_H0wev3r_1T_w1ll_b3_C00l_1F_Y0u_g0t_1t."
dd = [36,0,5,54,101,7,39,38,45,1,3,0,13,86,1,3,101,3,45,22,2,21,3,101,0,41,68,68,1,68,43]
answer = []
for i in dd:
answer.append(d[i])
answer = "".join(answer)
print answer

得到结果:re2 flag

RE3:Catalyst system

64位ELF文件,运行之后要求输入一个用户名一个密码。很可恶的是出题人在输入之前和之后加了两个随机的延时,每次输入和等结果都要很久。看起来出题人是想要选手静态分析。拖入ida,F5。400C9Ah为主要流程函数。

1
2
3
4
5
sub_400C9A(username); // 检查长度
sub_400CDD(username); // 检查内容 catalyst_ceo
sub_4008F7(username); // 检查字母或_
sub_400977(username, passwd); // 检查密码
sub_400876(username, passwd); // 异或得flag输出

sub_400C9A和sub_4008F7检查输入格式.

sub_400CDD
1
2
3
4
5
6
7
8
9
10
v4 = *(_DWORD *)username;
v3 = *(_DWORD *)(username + 4);
v2 = *(_DWORD *)(username + 8);
if ( v4 - v3 + v2 != 1550207830
|| v3 + 3 * (v2 + v4) != 12465522610LL
|| (result = 3651346623716053780LL, v2 * v3 != 3651346623716053780LL) )
{
puts("invalid username or password");
exit(0);
}

sub_400CDD中得到三个等式:

v4 - v3 + v2 == 1550207830
v3 + 3 (v2 + v4) == 12465522610
v2
v3 == 3651346623716053780

解方程组之后得到正确用户名 catalyst_ceo

sub_400977
1
2
3
4
5
6
7
srand(*(_DWORD *)(username + 4) + *(_DWORD *)username + *(_DWORD *)(username + 8));// 用户名生成伪随机数种子
v2 = *(_DWORD *)passwd;
if ( v2 - rand() != 0x55EB052A ) // 通过rand得出的随机数来判断密码
{
puts("invalid username or password");
exit(0);
}

sub_400977比较关键,用户名分三段相加后作为rand的种子,之后rand()生成10次,每次再与固定数值相加判断与输入密码是否相等,从这一段可以得出正确密码,通过用户名生成的种子写一段c程序生成正确密码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include<stdio.h>
#include<stdlib.h>
int main(){
srand((signed)5457657390);
printf("%x",rand()+0x55EB052A);
printf("%x",rand()+0xEF76C39);
printf("%x",rand()+0xCC1E2D64);
printf("%x",rand()+0xC7B6C6F5);
printf("%x",rand()+0x26941BFA);
printf("%x",rand()+0x260CF0F3);
printf("%x",rand()+0x10D4CAEF);
printf("%x",rand()+0xC666E824);
printf("%x",rand()+0xFC89459C);
printf("%x",rand()+0x2413073A);
return 0;
}

得到结果为

56534c73763451704763334b385779575a694136776767686a42484c4339786d567073526a676747

由于小端方式存储,反向。得密码

734c5356705134764b336347577957383641695a686767774c48426a6d783943527370564767676a
sLSVpQ4vK3cGWyW86AiZhggwLHBjmx9CRspVGggj

之后通过与保存的数组异或得到真正的flag,由于出题人的延时,这一步也自己动手。最后得到flag:

ALEXCTF{1_t41d_y0u_y0u_ar3gr34treverser__s33}

RE4: unVM me

这个题估计出题人没想过现在一堆做的很完善的py反编译工具会自动改magic number结果成了超简单的一道题。
题给了一个pyc文件,直接找个在线的pyc反编译,得到python代码

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
import md5
md5s = [
0x831DAA3C843BA8B087C895F0ED305CE7L,
0x6722F7A07246C6AF20662B855846C2C8L,
0x5F04850FEC81A27AB5FC98BEFA4EB40CL,
0xECF8DCAC7503E63A6A3667C5FB94F610L,
0xC0FD15AE2C3931BC1E140523AE934722L,
0x569F606FD6DA5D612F10CFB95C0BDE6DL,
0x68CB5A1CF54C078BF0E7E89584C1A4EL,
0xC11E2CD82D1F9FBD7E4D6EE9581FF3BDL,
0x1DF4C637D625313720F45706A48FF20FL,
0x3122EF3A001AAECDB8DD9D843C029E06L,
0xADB778A0F729293E7E0B19B96A4C5A61L,
0x938C747C6A051B3E163EB802A325148EL,
0x38543C5E820DD9403B57BEFF6020596DL]
print 'Can you turn me back to python ? ...'
flag = raw_input('well as you wish.. what is the flag: ')
if len(flag) > 69:
print 'nice try'
exit()
if len(flag) % 5 != 0:
print 'nice try'
exit()
for i in range(0, len(flag), 5):
s = flag[i:i + 5]
if int('0x' + md5.new(s).hexdigest(), 16) != md5s[i / 5]:
print 'nice try'
exit()
continue
print 'Congratz now you have the flag'

这几个MD5都是五位的可以爆破,但是有cmd5一类的网站就又省下不少时间,轻松得到flag

ALEXCTF{dv5d4s2vj8nk43s8d8l6m1n5l67ds9v41n52nv37j481h3d28n4b6v3k}

RE5: packed movement

没做出来的一道题

Being said that move instruction is enough to build a complete computer, anyway move on while you can.

拿到文件发现加过upxre5 upx
先脱掉upx,脱壳之前2M多,脱完壳9.82M,看着都吓人。拖到ida里一看,全是mov。到这就没思路了re5 mov
结束之后看大神writeup,发现这是由一个混淆工具处理的结果 movfuscator 看起来效果还不错,接下来主要有三种解法

搜索内容

脱完壳之后,根据flag里面有”ALEXCTF{“字符串,即0x41 0x4c 0x45 0x58 0x43 0x54 0x46 0x7b。

1
2
3
4
5
6
7
8
root@leo-kali:~/ctf/pin-3.0-76991-gcc-linux# objdump -d move-unpack|grep 0x41
80493db: c7 05 68 20 06 08 41 movl $0x41,0x8062068
root@leo-kali:~/ctf/pin-3.0-76991-gcc-linux# objdump -d move-unpack|grep 0x4c
8049dde: c7 05 68 20 06 08 4c movl $0x4c,0x8062068
root@leo-kali:~/ctf/pin-3.0-76991-gcc-linux# objdump -d move-unpack|grep 0x45
804a7e1: c7 05 68 20 06 08 45 movl $0x45,0x8062068
root@leo-kali:~/ctf/pin-3.0-76991-gcc-linux# objdump -d move-unpack|grep 0x58
804b1e4: c7 05 68 20 06 08 58 movl $0x58,0x8062068

搜索单个字符,发现一个共同点就是mov到的地址,搜索这个地址0x8062068。

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
root@leo-kali:~/ctf/pin-3.0-76991-gcc-linux# objdump -d move-unpack|grep ,0x8062068
80493db: c7 05 68 20 06 08 41 movl $0x41,0x8062068
8049dde: c7 05 68 20 06 08 4c movl $0x4c,0x8062068
804a7e1: c7 05 68 20 06 08 45 movl $0x45,0x8062068
804b1e4: c7 05 68 20 06 08 58 movl $0x58,0x8062068
804bb9c: c7 05 68 20 06 08 43 movl $0x43,0x8062068
804c59f: c7 05 68 20 06 08 54 movl $0x54,0x8062068
804cfa2: c7 05 68 20 06 08 46 movl $0x46,0x8062068
804d9a5: c7 05 68 20 06 08 7b movl $0x7b,0x8062068
804e357: c7 05 68 20 06 08 4d movl $0x4d,0x8062068
804ed5a: c7 05 68 20 06 08 30 movl $0x30,0x8062068
804f75d: c7 05 68 20 06 08 56 movl $0x56,0x8062068
8050160: c7 05 68 20 06 08 66 movl $0x66,0x8062068
8050b0c: c7 05 68 20 06 08 75 movl $0x75,0x8062068
805150f: c7 05 68 20 06 08 73 movl $0x73,0x8062068
8051f12: c7 05 68 20 06 08 63 movl $0x63,0x8062068
8052915: c7 05 68 20 06 08 34 movl $0x34,0x8062068
80532bb: c7 05 68 20 06 08 74 movl $0x74,0x8062068
8053cbe: c7 05 68 20 06 08 30 movl $0x30,0x8062068
80546c1: c7 05 68 20 06 08 72 movl $0x72,0x8062068
80550c4: c7 05 68 20 06 08 5f movl $0x5f,0x8062068
8055a64: c7 05 68 20 06 08 77 movl $0x77,0x8062068
8056467: c7 05 68 20 06 08 30 movl $0x30,0x8062068
8056e6a: c7 05 68 20 06 08 72 movl $0x72,0x8062068
805786d: c7 05 68 20 06 08 6b movl $0x6b,0x8062068
8058207: c7 05 68 20 06 08 35 movl $0x35,0x8062068
8058c0a: c7 05 68 20 06 08 5f movl $0x5f,0x8062068
805960d: c7 05 68 20 06 08 6c movl $0x6c,0x8062068
805a010: c7 05 68 20 06 08 31 movl $0x31,0x8062068
805a9a4: c7 05 68 20 06 08 6b movl $0x6b,0x8062068
805b3a7: c7 05 68 20 06 08 65 movl $0x65,0x8062068
805bdaa: c7 05 68 20 06 08 5f movl $0x5f,0x8062068
805c7ad: c7 05 68 20 06 08 6d movl $0x6d,0x8062068
805d13b: c7 05 68 20 06 08 34 movl $0x34,0x8062068
805db3e: c7 05 68 20 06 08 67 movl $0x67,0x8062068
805e541: c7 05 68 20 06 08 31 movl $0x31,0x8062068
805ef44: c7 05 68 20 06 08 63 movl $0x63,0x8062068
805f8cc: c7 05 68 20 06 08 7d movl $0x7d,0x8062068

得到flag:ALEXCTF{M0Vfusc4t0r_w0rk5_l1ke_m4g1c}
大神的WP

demovfuscator + Perf

大神的WP
pass

intel-pin

大神的WP
pass

Cryptography

CR1: Ultracoded

题目给了个文件内容都是ZERO和ONE,应该是二进制,替换成01之后输出字符串发现base64编码过。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import base64
f = open("zero_one","r").read()
bi = []
for i in f.split(" "):
if i == "ZERO":
bi.append("0")
else:
bi.append("1")
bi = "".join(bi)
answer = []
for i in range(len(bi)/8):
answer.append(chr(int(bi[i*8:i*8+8],2)))
answer = "".join(answer)
answer = base64.b64decode(answer)
print answer

base64解码之后得到Morse码

.- .-.. . -..- -.-. - ..-. - …. .—- ….. — .—- … — ….. ..- .–. …– .-. — ….. . -.-. .-. …– - — - -..- -

再解码得到:

alexctfth15o1so5up3ro5ecr3totxt

各种改格式提交各种不对,最后发现按词义分析一下:

alexctf this o is o super o secret o txt

得到flag:

AlexCTF{th15_1s_5up3r_5ecr3t_txt}

CR2: Many time secrets

0529242a631234122d2b36697f13272c207f2021283a6b0c7908
2f28202a302029142c653f3c7f2a2636273e3f2d653e25217908
322921780c3a235b3c2c3f207f372e21733a3a2b37263b313012
2f6c363b2b312b1e64651b6537222e37377f2020242b6b2c2d5d
283f652c2b31661426292b653a292c372a2f20212a316b283c09
29232178373c270f682c216532263b2d3632353c2c3c2a293504
613c37373531285b3c2a72273a67212a277f373a243c20203d5d
243a202a633d205b3c2d3765342236653a2c7423202f3f652a18
2239373d6f740a1e3c651f207f2c212a247f3d2e65262430791c
263e203d63232f0f20653f207f332065262c3168313722367918
2f2f372133202f142665212637222220733e383f2426386b

11段密文,题目信息给得很足明显是一次性密码本多次使用了。以前没解密过,在知乎上看到一个解释很详细的问答 传送门
套路是由于异或(a⊕b)⊕b=a的特性

m1⊕key = c1
m2⊕key = c2
c1⊕c2 = m1⊕m2

由于ascii中大写字母0x40开头小写0x60开头,空格为0x20,所以如果两段文字中空格与小写字母异或会出现大写字母,然后得到key。
手工有点麻烦,正好有一个python写的工具FeatherDuster强的很,密文输入之后选择many times pad攻击,过一会得到了解密后的明文。发现明文里没有flag,想到密钥是flag。

ALEXCTF{HERE_GOES_THE_KEY}

CR3: What is this encryption?

p=0xa6055ec186de51800ddd6fcbf0192384ff42d707a55f57af4fcfb0d1dc7bd97055e8275cd4b78ec63c5d592f567c66393a061324aa2e6a8d8fc2a910cbee1ed9
q=0xfa0f9463ea0a93b929c099320d31c277e0b0dbc65b189ed76124f5a1218f5d91fd0102a4c8de11f28be5e4d0ae91ab319f4537e97ed74bc663e972a4a9119307
e=0x6d1fdab4ce3217b3fc32c9ed480a31d067fd57d93a9ab52b472dc393ab7852fbcb11abbebfd6aaae8032db1316dc22d3f7c3d631e24df13ef23d3b381a1c3e04abcc745d402ee3a031ac2718fae63b240837b4f657f29ca4702da9af22a3a019d68904a969ddb01bcf941df70af042f4fae5cbeb9c2151b324f387e525094c41
c=0x7fe1a4f743675d1987d25d38111fae0f78bbea6852cba5beda47db76d119a3efe24cb04b9449f53becd43b0b46e269826a983f832abb53b7a7e24a43ad15378344ed5c20f51e268186d24c76050c1e73647523bd5f91d9b6ad3e86bbf9126588b1dee21e6997372e36c3e74284734748891829665086e0dc523ed23c386bb520

题目直接给了pqe,解出d之后解密,rsa千里送分题。

CR4: Poor RSA

题给了一个公钥key.pub,一个flag.b64。公钥长度太短,解密出私钥然后解密出flag。跟上题差不多。

CR5: Bring weakness

We got this PRNG as the most secure random number generator for cryptography.
Can you prove otherwise
nc 195.154.53.62 7412

正经解法

看起来是找PRNG的漏洞,第一次见。cr5 ti
可以读数也可以猜数,猜对十个给flag。
生成伪随机数最著名的算法LCG,线性同余法。它是根据递归公式:$ x_{n+1} = ax_n +k \mod m$ ,所以第n+1个随机数是由第n个随机数和a,k,m决定的。首先求出m。
设$ t_n = x_{n+1} - x_n $,$ u_n = |t_{n+2}t_n - t_{n+1}^2| $ 。 $ m = gcd(u_1,u_2,…,u_k) $ ,m错误的概率随着k值的增大呈指数下降。
接下来求a和k,$ a = (x_{n+2}-x_{n+1})(x_{n+1}-x_n)^{-1} \mod m $,$ b = x_{n+1} - ax_n \mod m $。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import gmpy2
x = [100909846,1875979913,545570872,2302513355,239025856,2061072608,633003322,3970909550,3762214126,242181143,3088920217]
t = []
u = []
for i in range(1,len(x)):
t.append(x[i]-x[i-1])
for i in range(2,len(t)):
u.append(abs(t[i]*t[i-2]-t[i-1]*t[i-1]))
# u = map(int,u)
m = (reduce(gmpy2.gcd,u))
print "m="+hex(m)
a = (x[2]-x[1])*gmpy2.invert(x[1]-x[0],m) %m
print "a="+hex(a)
k = (x[1] - a*x[0]) % m
print "k="+hex(k)

求得$ m=0xffffffff, a=0x939a5ec0,k=0x8aa90086 $,通过$ x_{n+1} = ax_n +k \mod m$ 计算预测未知数,得到flag。cr5 flag
参考资料:
How to crack a Linear Congruential Generator
cracking-a-linear-congruential-generator

非正经解法

后来看到有人用别的解法,由于int型本身长度有限,直接读出一轮。具体见传送门 (因为是国外服务器,国内会偶尔连不上,想获取那么大数据量很难)

Forensics

Fore1: Hit the core

给了个coredump,但是没有程序,两眼抹黑。strings了一下只有这么一串有点意思的:

cvqAeqacLtqazEigwiXobxrCrtuiTzahfFreqc{bnjrKwgk83kgd43j85ePgb_e_rwqr7fvbmHjklo3tews_hmkogooyf0vbnk0ii87Drfgh_n kiwutfb0ghk9ro987k5tfb_hjiouo087ptfcv}

然后就卡住了,睡了一觉觉得也没啥做法只能在这上做文章试了试栅栏什么的,发现flag

cvqA
eqacL
tqazE
igwiX
obxrC
rtuiT
zahfF
reqc{
bnjrK
wgk83
kgd43
j85eP
gbe
rwqr7
fvbmH
jklo3
tews
hmkog
ooyf0
vbnk0
ii87D
rfgh

n kiw
utfb0
ghk9r
o987k
5tfb_
hjiou
o087p
tfcv}
ALEXCTF{K33P_7H3_g00D_w0rk_up}

Fore2: Mail client

题目给了一个nc地址和一个coredump文件。file一下coredump发现from mutt.fore2 file服务器交互也符合邮箱服务器。想到是取证不是pwn,判断应该是从dump文件找帐号密码。strings得到一长篇字符串,匹配@得到了一个

alexctf@example.com - -Mutt: (no mailbox) [Msgs:0]—(threads/date)———-(all)—

估计是邮箱帐号,接着匹配passwd发现

tp_pass = “e. en kv,dvlejhgouehg;oueh fenjhqeouhfouehejbge ef”

尝试发现不是密码,查看上下文无果。在文件中以这个密码匹配发现另一处位置,相邻有另一个字符串。!fore2 pass
尝试登陆成功,得到flag。fore2 flag

Fore3: USB probing

给了一个pcap包,wireshark打开之后发现不是之前遇到的鼠标轨迹,而是USB储存设备的数据包,把几个有数据的包中数据导出。在其中最大的包中发现PNG头fore3 pcap
导出png用windows自带的图片查看和ps查看都是这样的fore3 fail
以为需要修复,但无从下手。之后在文件iTXt块中发现GIMP。用GIMP打开得到flagfore3 flag

Fore4: Unknown format

跟fore3一样都是usb包,观察数据发现有个文件头是SP01,没思路。fore4 capdata
后来发现是个kindle固件,上网找到KindleTool。克隆下来编译完解出来内容,binwalk发现压缩内容,解压出来之后发现flag.txt,得到flag。

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
root@leo-kali:~/ctf# ./kindletool dm usb_sniff-19.bin ./fore4
root@leo-kali:~/ctf# file fore4
fore4: data
root@leo-kali:~/ctf# binwalk fore4
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
254 0xFE gzip compressed data, from Unix, last modified: Sun Jan 1 04:20:49 2017
root@leo-kali:~/ctf# binwalk -e fore4
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
254 0xFE gzip compressed data, from Unix, last modified: Sun Jan 1 04:20:49 2017
root@leo-kali:~/ctf# cd _fore4.extracted/
root@leo-kali:~/ctf/_fore4.extracted# ls
FE.gz
root@leo-kali:~/ctf/_fore4.extracted# tar -vxzf FE.gz
kindle_out/
kindle_out/rootfs_md5_list.tar.gz
kindle_out/2540270001-2692310002.ffs
kindle_out/flag.txt
kindle_out/update-patches.tar.gz
gzip: stdin: unexpected end of file
tar: Unexpected EOF in archive
tar: Unexpected EOF in archive
tar: Error is not recoverable: exiting now
root@leo-kali:~/ctf/_fore4.extracted# cd kindle_out/
root@leo-kali:~/ctf/_fore4.extracted/kindle_out# cat flag.txt
ALEXCTF{Wh0_N33d5_K1nDl3_t0_3X7R4Ct_K1ND13_F1rMw4R3}

Scripting

SC1: Math bot

nc连上之后发现让算加减乘除,写脚本。开始以为100次就差不多了,结果问了500个问题才给flag。

1
2
3
4
5
6
7
8
9
from pwn import *
r = remote("195.154.53.62",1337)
for i in range(500):
print r.recvuntil(":")
a = r.recvuntil("=")
print a
print r.recv()
r.sendline(str(eval(a[:-2])))
print r.recv()

ALEXCTF{1_4M_l33t_b0t}

SC2: Cutie cat

题目描述

yeah steganography challenges are the worst… that’s why we got only ~~one ~~ two steganography challenges .
Hint: It scripting because we need a python library to solve the challenge, one that is made in japan.

然后给了一张图片。一开始尝试几种隐写方式无果,在irc里看出题人说三行就能解决,想到了频域隐写,还各种尝试结果发现并不是。

出题人取巧做法

Hint出来之后找到了出题人用的库steganography,一行就出结果。

1
2
steganography -d cat_with_secrets.png
ALEXCTF{CATS_HIDE_SECRETS_DONT_THEY}

正经做法

上网找到原图,对比,发现异常,提取数据。具体看大佬Writeup 传送门