0%

2024-nullcon-HackIM-CTF-wp-crypto

和余师傅的小队一块儿参加了这场比赛拿到第五,其中四道密码做出三道,在这里简单记录一下crypto的题解。

这是做过的最折磨的crypto没有之一,因为难度主要在把代码看懂,但是代码最简单的一道题目又不会OwO。

double-trouble

题目描述:

1
2
3
4
5
What can be better than a Crypto challenge? (kinda anything lol)

A Crypto challenge written in Rust! its double the fun ;)

Authors: @layton, @anajana, @moaath

题目:

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
use std::io::Write;

use aes_gcm::{
aead::{Aead, AeadCore, KeyInit, OsRng},
Aes256Gcm,
};

const FLAG: &str = "ENO{repl4c3_m3}";

fn encrypt(message: &[u8], key: &[u8]) -> Vec<u8> {
message
.iter()
.enumerate()
.map(|(i, m)| m ^ key[i % 32])
.collect()
}

fn main() {
let key = Aes256Gcm::generate_key(OsRng);
let cipher = Aes256Gcm::new(&key);
let nonce = Aes256Gcm::generate_nonce(&mut OsRng);

let encrypted = cipher.encrypt(&nonce, FLAG.as_ref()).unwrap();

//println!("Key: 0x{}", hex::encode(&key));
println!(
"Ciphertext: 0x{}{}",
hex::encode(&nonce),
hex::encode(&encrypted)
);

let mut message = String::new();
print!("Let me encrypt one more thing for you: ");
std::io::stdout().flush().unwrap();
std::io::stdin().read_line(&mut message).unwrap();

if message.len() > 29 {
println!("Woah! That's too long for me :^)");
return;
}

println!("0x{}", hex::encode(encrypt(message.as_ref(), key.as_ref())));
}

这个题目主要在于他是用rust写的,其实整理出来就是:

  • 随机生成AES256-GCM加密所需的key(32字节)和nonce(12字节),并对flag进行加密
  • 给出nonce和flag加密得到的值encrypted
  • 在这之后,可以再输入一段长度不大于29的message,靶机会将他与key逐字节异或后返回(注意这里只会异或到两者中较短的长度,也就是message的长度)

由于有nonce,所以只要能求出key就可以AES解密出flag。然后又因为我们能发送给靶机29个字节,因此可以异或回去得到key的前29字节,那么爆破最后3字节并尝试解密就能得到flag了。

需要注意的是靶机接受消息会自动在末尾加”\n”,所以其实只能发长为28的消息。

exp:

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

#context.log_level = 'debug'

sh = remote("52.59.124.14",5024)
sh.recvuntil(b"Ciphertext: ")
cipher = long_to_bytes(int(sh.recvline().strip().decode(),16))
nonce1 = cipher[:12]
enc_flag = cipher[12:]

msg = b"\x00"*28 + b"\n"
sh.sendline(msg)
sh.recvuntil(b"Let me encrypt one more thing for you: ")
enc2 = long_to_bytes(int(sh.recvline().strip().decode(),16))
key_pre = xor(enc2,msg)

print(len(key_pre))
print(len(enc_flag))

for i in trange(256):
for j in range(256):
for k in range(256):
key = key_pre + long_to_bytes(i)+long_to_bytes(j)+long_to_bytes(k)
dec = AES.new(key, AES.MODE_GCM, nonce=nonce1)

temp = dec.decrypt(enc_flag)
if(b"ENO{" in temp):
print(temp)
exit()


#ENO{otp_reuse_rethink_rewind}



Inkblot

题目描述:

1
2
3
My friend is really careless. He printed out his ECDSA key. But unfortunately, he spilled some inkblots on it, so some characters are unreadable.

ssh cryptodude@52.59.124.14 -p 5025

题目:

1
2
3
4
5
6
7
8
9
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS
1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQSw+Fs***rO9Hbzkm****Mty8NFeb6Y
ZloHs2KWjK****iQ78pJR+6T**SRi***BU7c0Oilk61i/***04RGPrMPAAAAqBAHrdwQB6
3cAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBLD4WzJfys**dvOS
ZwUo4y3**0V5vp****ezYpaMrvZYmJD**klH7pOztJGJ7+oFTtz****TrWL/vcTThE**sw
8AAAAgaJN/R323SVkRTqxz******8ClkzgxdWidxNTrTdwYIcAAAAOa*********B****y
**IBAg==
-----END OPENSSH PRIVATE KEY-----

这个题比较新鲜,是一个ECDSA的私钥恢复,要恢复肯定要先找到他的格式,google了一番找到了一片参考文章:

The OpenSSH Private Key Format (coolaj86.com)

巧合的是,文章里给出的参考私钥文件和这个题目里污损的私钥文件长度恰好相同,这为之后提供了很多便利。

首先可以先把参考私钥文件拿去解密看看存了些什么:

image-20240316162127128

对照着文档去一个一个看,可以发现私钥文件里存放的主要是曲线类型、q值以及私钥d,其中q其实就存放着倍点dG=(x,y)。而查标准的secp256r1参数可以知道其实生成元G也是固定的:

P-256 | Standard curve database (neuromancer.sk)

而题目给了ssh,所以恢复完整私钥文件就能连接上,应该就有flag。那么对照文档一步步恢复,会发现q、d以及最后的comment都是有污损的,恢复方式也不一样,分别列举如下:

  • q虽然有污损,但是私钥文件里存了两份(这是因为私钥文件里会先存公钥的内容),因此把两者结合起来就能得到没有污损的q,有了q就相当于有了倍点dG的坐标
  • d污损了36位,用dG = d*G这一关系bsgs去爆破得到
  • comment是什么内容都无所谓,所以随便填

不过虽然说得似乎很容易,但是恢复过程还是比较折磨,这里分开列举一下。

恢复q:

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

c1 = "QQSw+Fs***rO9Hbzkm****Mty8NFeb6YZloHs2KWjK****iQ78pJR+6T**SRi***BU7c0Oilk61i/***04RGPrMP"
c2 = "BLD4WzJfys**dvOSZwUo4y3**0V5vp****ezYpaMrvZYmJD**klH7pOztJGJ7+oFTtz****TrWL/vcTThE**sw8"

table = string.ascii_uppercase + string.ascii_lowercase + string.digits + "+/"


f1 = ""
for i in c1:
if(i == "*"):
f1 += "******"
continue
f1 += bin(table.index(i))[2:].zfill(6)

f2 = ""
for i in c2:
if(i == "*"):
f2 += "******"
continue
f2 += bin(table.index(i))[2:].zfill(6)

print(f1[8:])
print(f2)

final = "0000010010110000111110000101101100110010010111111100101011001110111101000111011011110011100100100110011100000101001010001110001100101101110010111100001101000101011110011011111010011000011001100101101000000111101100110110001010010110100011001010111011110110010110001001100010010000111011111100101001001001010001111110111010010011101100111011010010010001100010011110111111101010000001010100111011011100110100001110100010100101100100111010110101100010111111111011110111000100110100111000010001000110001111101011001100001111"
print(int(final,2))

恢复d:

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
from tqdm import *
from hashlib import *
from base64 import *

p = 0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff
K = GF(p)
a = K(0xffffffff00000001000000000000000000000000fffffffffffffffffffffffc)
b = K(0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b)
E = EllipticCurve(K, (a, b))
G = E(0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296, 0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5)
E.set_order(0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551 * 0x1)

t = "b0f85b325fcacef476f392670528e32dcbc34579be98665a07b362968caef6589890efca4947ee93b3b49189efea054edcd0e8a593ad62ffbdc4d384463eb30f"

x = int(t[:64],16)
y = int(t[64:],16)

Q = E(x,y)

d1 = "11010001001001101111111010001110111110110110111010010010101100100010001010011101010110001110011"
d2 = "1111000000101001011001001100111000001100010111010101101000100111011100010011010100111010110100110111011100000110000010000111"


#bsgs
if(0):
dic = {}
for i in trange(2^18):
d = int((d1 + bin(i)[2:].zfill(18)) , 2) * 2^(18 + len(d2))
dic[(Q-d*G)[0]] = i

for j in trange(2^18):
d = int((bin(j)[2:].zfill(18)) + d2 , 2)
if((d*G)[0] in dic.keys()):
print(dic[(d*G)[0]] , j)

#27720 259974
d = int((d1 + bin(27720)[2:].zfill(18)) , 2) * 2^(18 + len(d2)) + int((bin(259974)[2:].zfill(18)) + d2 , 2)
print(d)

恢复完整私钥pem文件:

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

p = 62899910107538091753472605731958716431532429784101879766564879776512571948297970028879774548189532173352114204695025969537124428037066379787090737034605327
d = 47301141220363818814201640121058873711332982879724793113526901098081948819591

msg = b"openssh-key-v1" + b"\x00"
msg += b"\x00\x00\x00\x04none" * 2
msg += b"\x00\x00\x00\x00\x00\x00\x00\x01"
msg += b'\x00\x00\x00h\x00\x00\x00\x13ecdsa-sha2-nistp256\x00\x00\x00\x08nistp256\x00\x00\x00A'
msg += long_to_bytes(p)
msg += b'\x00\x00\x00\xa8'
msg += b'\x10\x07\xad\xdc\x10\x07\xad\xdc'
msg += b'\x00\x00\x00\x13ecdsa-sha2-nistp256\x00\x00\x00\x08nistp256'

msg += b'\x00\x00\x00A'
msg += long_to_bytes(p)
msg += b'\x00\x00\x00 '
msg += long_to_bytes(d)
msg += b'\x00\x00\x00\x0e'


msg += b"jjj123cat flag"
msg += b"\x01\x02"
print(b64encode(msg))


'''
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS
1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQSw+FsyX8rO9HbzkmcFKOMty8NFeb6Y
ZloHs2KWjK72WJiQ78pJR+6Ts7SRie/qBU7c0Oilk61i/73E04RGPrMPAAAAqBAHrdwQB6
3cAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBLD4WzJfys70dvOS
ZwUo4y3Lw0V5vphmWgezYpaMrvZYmJDvyklH7pOztJGJ7+oFTtzQ6KWTrWL/vcTThEY+sw
8AAAAgaJN/R323SVkRTqxzGxI/eG8ClkzgxdWidxNTrTdwYIcAAAAOampqMTIzY2B0IGZy
YWIBAg==
-----END OPENSSH PRIVATE KEY-----

'''

然后连接上去就拿到flag:

1
ENO{s0m3_g4p5_c4n_b3_fi11ed}



pwdHDL

题目描述:

1
Introducing pwdHDL, our fancy new hardware RNG! Simply return your password to us when you're done.

题目:

pwdhdl.vhd:

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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use std.textio.all;
use work.rng.all;
use work.stdio.all;
use work.secret.FLAG;

entity pwdhdl is
end pwdhdl;

architecture Behavioral of pwdhdl is
constant PASSWORD_SIZE : integer := 32;
constant SEED_SIZE : integer := 8;
constant SECRET_SIZE : INTEGER := 20;
constant DB_SIZE : INTEGER := 10;

type FSM_STATE is (INITIALIZE, STORE_FLAG, STORE_FLAG_WAIT, MENU, STORE, STORE_WAIT, GET, FULL);
type DB_ENTRY is record
secret : STRING(1 to SECRET_SIZE);
password : STRING(1 to PASSWORD_SIZE);
end record;
type DB is array(0 to DB_SIZE-1) of DB_ENTRY;

signal CLK : STD_LOGIC := '0';
signal data : STD_LOGIC_VECTOR(PASSWORD_SIZE*4-1 downto 0);
signal seed : STD_LOGIC_VECTOR(0 to SEED_SIZE*8-1);
signal taps : STD_LOGIC_VECTOR(0 to SEED_SIZE*8-1);
signal reset, enable, done : STD_LOGIC;

signal database : DB := (others => ((others => '0'), (others => '0')));

begin

password_generator: pwd_gen
generic map(
STATE_SIZE => SEED_SIZE*8,
OUTPUT_SIZE => PASSWORD_SIZE*4
)
port map(
CLK => CLK,
RESET => reset,
ENABLE => enable,
SEED => seed,
TAPPED => taps,
VALID => done,
DATA => data
);

clock: process
begin
CLK <= not CLK;
wait for 10 ns;
end process;


interface: process(CLK)
variable state : FSM_STATE := INITIALIZE;
variable db_index : NATURAL := 1;
variable selection : STRING(1 to 1);
variable secret : STRING(1 to SECRET_SIZE);
variable password : STRING(1 to PASSWORD_SIZE);
variable query_successful : BOOLEAN := false;
begin
if rising_edge(CLK) then
state := state;
reset <= '0';
enable <= '0';

case state is
when INITIALIZE =>
enable <= '0';
reset <= '1';
seed <= read_urandom(SEED_SIZE);
taps <= read_urandom(SEED_SIZE);
taps(SEED_SIZE*8-1) <= '1';
state := STORE_FLAG;

when STORE_FLAG =>
secret := FLAG;
enable <= '1';
state := STORE_FLAG_WAIT;

when STORE_FLAG_WAIT =>
if done = '1' then
password := to_hstring(unsigned(data));
database(0) <= (secret, password);
state := MENU;
write_stdout("+--------------------------------------------+");
write_stdout("| Welcome to pwdHDL, the password pawn shop. |");
write_stdout("+--------------------------------------------+");
write_stdout("");
else
state := STORE_FLAG_WAIT;
end if;

when MENU =>
write_stdout("Please select one of the following options:");
write_stdout("1) Offer a secret");
write_stdout("2) Retrieve a secret");
selection := read_stdin(1);
write_stdout("");

case (selection) is
when "1" => state := STORE;
when "2" => state := GET;
when others => state := MENU;
end case;

when STORE =>
write_stdout("We will lend you a password in exchange for a collateral.");
write_stdout("Please tell us a secret (at most " & integer'image(SECRET_SIZE) & " characters):");
secret := read_stdin(SECRET_SIZE);
write_stdout("");
enable <= '1';
state := STORE_WAIT;

when STORE_WAIT =>
if done = '1' then
password := to_hstring(unsigned(data));
write_stdout("Your password is: " & password);
write_stdout("");
database(db_index) <= (secret, password);
db_index := db_index + 1;
state := FULL when db_index >= DB_SIZE else MENU;
else
state := STORE_WAIT;
end if;

when GET =>
write_stdout("Please return your password to retrieve your secret:");
password := read_stdin(PASSWORD_SIZE);
write_stdout("");
query_successful := false;
for I in 0 to DB_SIZE-1 loop
if database(I).password = password and not query_successful then
if password /= "00000000000000000000000000000000" then
write_stdout("Thank you for returning our valuable entropy to us.");
write_stdout("Your secret is: " & database(I).secret);
else
write_stdout("Nothing? Really?");
end if;
query_successful := true;
end if;
end loop;
if not query_successful then
write_stdout("What is this? This is not one of our passwords.");
end if;
write_stdout("");
state := FULL when db_index >= DB_SIZE else MENU;

when FULL =>
write_stdout("We will not accept any additional secrets at this time.");
write_stdout("");
state := GET;
end case;
end if;
end process;
end Behavioral;

rng.vhd:

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
library ieee;
use ieee.std_logic_1164.all;

package rng is
component pwd_gen is
generic(
STATE_SIZE : POSITIVE := 64;
OUTPUT_SIZE : POSITIVE := 128
);
port(
CLK : in STD_LOGIC;
RESET : in STD_LOGIC;
ENABLE : in STD_LOGIC;
SEED : in STD_LOGIC_VECTOR(0 to STATE_SIZE-1);
TAPPED : in STD_LOGIC_VECTOR(0 to STATE_SIZE-1);
VALID : out STD_LOGIC;
DATA : out STD_LOGIC_VECTOR(OUTPUT_SIZE-1 downto 0) := (others => '0')
);
end component;
end package;

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity pwd_gen is
generic(
STATE_SIZE : POSITIVE := 64;
OUTPUT_SIZE : POSITIVE := 128
);
port(
CLK : in STD_LOGIC;
RESET : in STD_LOGIC;
ENABLE : in STD_LOGIC;
SEED : in STD_LOGIC_VECTOR(0 to STATE_SIZE-1);
TAPPED : in STD_LOGIC_VECTOR(0 to STATE_SIZE-1);
VALID : out STD_LOGIC;
DATA : out STD_LOGIC_VECTOR(OUTPUT_SIZE-1 downto 0) := (others => '0')
);
end pwd_gen;

architecture Behavioral of pwd_gen is
signal running : BOOLEAN := false;
signal taps : STD_LOGIC_VECTOR(0 to STATE_SIZE-1);
signal state : STD_LOGIC_VECTOR(0 to STATE_SIZE-1);

signal outbit : STD_LOGIC;
begin
outbit <= state(STATE_SIZE-1);

process(CLK) is
variable counter : INTEGER;
variable xbit : STD_LOGIC;
begin
if RESET = '1' then
state <= SEED;
taps <= TAPPED;
running <= false;
counter := 0;
VALID <= '0';
elsif rising_edge(CLK) then
VALID <= '0';
if ENABLE = '1' then
running <= true;
end if;

if running then
state <= (xor (state and taps)) & state(0 to STATE_SIZE-2);
DATA <= DATA(OUTPUT_SIZE-2 downto 0) & outbit;
counter := counter + 1;

if counter >= OUTPUT_SIZE then
VALID <= '1';
running <= false;
counter := 0;
end if;
end if;
end if;
end process;
end Behavioral;

stdio.vhd:

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
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use std.textio.all;

package stdio is
procedure write_stdout(str: in STRING);
impure function read_stdin(n: in POSITIVE) return STRING;
impure function read_urandom(n: in POSITIVE) return STD_LOGIC_VECTOR;
end package;

package body stdio is
procedure write_stdout(str: in STRING) is
variable buf : LINE;
begin
write(buf, str);
writeline(output, buf);
end procedure;

impure function read_stdin(n: in POSITIVE) return STRING is
variable inbuf : LINE;
variable outbuf : STRING(1 to n) := (others => '0');
begin
write(output, "> ");
if not endfile(input) then
readline(input, inbuf);
if inbuf'length > 0 then
read(inbuf, outbuf(1 to minimum(inbuf'length, n)));
end if;
else
write_stdout("Failed to read from stdin.");
std.env.stop;
end if;
return outbuf;
end function;

impure function read_urandom(n: in POSITIVE) return STD_LOGIC_VECTOR is
TYPE T_CHARFILE IS FILE OF CHARACTER;
FILE dev_urandom : T_CHARFILE OPEN read_mode IS "/dev/urandom";

variable byte : character;
variable data : STD_LOGIC_VECTOR(0 to 8*n-1) := (others=>'X');
begin
for I in 0 to n-1 loop
read(dev_urandom, byte);
data(8*I to 8*I+7) := std_logic_vector(to_unsigned(character'pos(byte), 8));
end loop;
return data;
end function;
end package body;

很长的题目,而且还是用VHDL写的,我硬件学的本来就很烂所以只能一段一段送去给chatgpt翻译。翻译完过后大概可以把题目内容整合成:

  • 连接上靶机后,靶机会初始化一个LFSR作为伪随机数序列生成器,其中初始状态及抽头序列都是64位
  • 初始化一个秘密列表,每一个值的格式是(secret,password)
  • 生成password0,并在列表第一个位置存放(flag,password0)
  • 在之后,可以有两个选项。选择1,可以输入一个secret,并得到他的password,然后靶机会将本次的(secret,password)存入秘密列表中,秘密列表长度最大为10;选择2,可以输入password,靶机会在秘密列表里检索password,如果检索成功就返回它对应的secret

可以看出目的是得到flag对应的password0,然后去检索从而得到flag。

这里需要注意的就是,password是128位的量,是完全由LFSR一位一位产生的,和你的输入根本没关系。所以其实就是根据LFSR的输出去还原抽头序列,然后再递推回去就能拿到password0了。

而由于抽头序列只有64位,所以其实随便输入一个secret,然后用拿到的128位password就可以完全破解这个LFSR。这是因为我们可以把拿到的这个password分成前后两部分,记为x1、x2,由于他们各64bit,所以可以写成:

也就是x2是以x1作为种子产生的前64位输出,而LFSR是线性的,所以可以写成矩阵方程形式去求出抽头序列对应的线性递推式,然后就可以解满秩的矩阵方程得到抽头序列了。

得到抽头序列后就是由x1去回推得到password0,再送给靶机去检索就拿到flag。

exp:

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

t = "44E53F095370F0F4A29DBEEB6DA761D5"
init = bin(int(t[:16],16))[2:].zfill(64)
t = "A1FA2AD401B684B7C76FA36118B75AE1"
#init = bin(int(t[:16],16))[2:].zfill(64)
s0 = int(t[:16],16)
s1 = int(t[16:],16)
s0_bin = bin(s0)[2:].zfill(64)
s1_bin = bin(s1)[2:].zfill(64)

L = []
for i in range(64):
temp = s0_bin[i:] + s1_bin[:i]
L.append(list(map(int,temp)))
L = Matrix(GF(2),L)
B = vector(GF(2),list(map(int,s1_bin)))
mask = L.solve_right(B)
print(mask)


tap = mask[1:]
t = ""
for i in range(128):
temp1 = list(map(int,init[:-1]))
temp2 = int(init[-1])

sum1 = temp2
for j in range(len(temp1)):
sum1 -= (int(temp1[j]) * int(tap[j]))
t = (str(sum1 % 2)) + t
init = t[0] + init[:-1]

print(hex(int(t,2)).upper())

temp = list(map(int,t))[64:]

f = ""
for i in range(128):
tt = vector(Zmod(2),temp)*mask
f += str(tt)
temp = temp[1:] + [tt]
print(hex(int(f,2)))

得到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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
from Crypto.Util.number import *

t = "44E53F095370F0F4A29DBEEB6DA761D5"
init = bin(int(t[:16],16))[2:].zfill(64)
t = "A1FA2AD401B684B7C76FA36118B75AE1"
#init = bin(int(t[:16],16))[2:].zfill(64)
s0 = int(t[:16],16)
s1 = int(t[16:],16)
s0_bin = bin(s0)[2:].zfill(64)
s1_bin = bin(s1)[2:].zfill(64)

L = []
for i in range(64):
temp = s0_bin[i:] + s1_bin[:i]
L.append(list(map(int,temp)))
L = Matrix(GF(2),L)
B = vector(GF(2),list(map(int,s1_bin)))
mask = L.solve_right(B)
print(mask)


tap = mask[1:]
t = ""
for i in range(128):
temp1 = list(map(int,init[:-1]))
temp2 = int(init[-1])

sum1 = temp2
for j in range(len(temp1)):
sum1 -= (int(temp1[j]) * int(tap[j]))
t = (str(sum1 % 2)) + t
init = t[0] + init[:-1]

print(hex(int(t,2)).upper())

temp = list(map(int,t))[64:]

f = ""
for i in range(128):
tt = vector(Zmod(2),temp)*mask
f += str(tt)
temp = temp[1:] + [tt]
print(hex(int(f,2)))


#ENO{2LINEAR_4CRYPTO}