2024IrisCTF

Last updated on January 30, 2024 am

The Johnson’s,Rune? What’s that?,The Maze

The Johnson’s

简单的签到,z3一把梭

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
from z3 import *

chosenFoods = [BitVec("chosenFoods_%s" % i, 8) for i in range(4)]
chosenColors = [BitVec("chosenColors_%s" % i, 8) for i in range(4)]

s = Solver()
for i in range(4):
s.add(chosenColors[i] <= 4)
s.add(chosenFoods[i] <= 4)

s.add(chosenColors[i] >= 1)
s.add(chosenFoods[i] >= 1)

dis1 = Distinct(chosenFoods)
dis2 = Distinct(chosenColors)

cond1 = And([chosenFoods[2] != 2, chosenFoods[3] != 2, chosenColors[1] != 1])
cond2 = And([chosenColors[0] != 3, chosenColors[1] != 3])

cond3 = Or([chosenFoods[0] != 4, chosenFoods[3] == 3, chosenColors[2] == 4, chosenColors[3] != 2])
s.add(And(cond1, cond2, dis1, dis2))
s.add(Not(cond3))

s.check()
m = s.model()

for i in chosenColors:
print(i, m[i])

for i in chosenFoods:
print(i, m[i])

Rune? What’s that?

这题考点是go语言中字符串的编码问题。Go语言使用UTF-8编码,因此任何字符都可以用Unicode表示。为此,Go在代码中引入了一个新术语,称为 rune。rune是int32的类型别名。

但是不同字符所占字节数是不同的,比如ascii码只需要一个字节就可以表示,而汉字通常需要三个字节,而不管什么字符在存储中都是一个字节一个字节的存,对占 1 个字节的英文类字符,可以使用 byte(或者 unit8 );对占 1 ~ 4 个字节的其他字符,可以使用 rune(或者 int32 ),如中文、特殊符号等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import (
"fmt"
"unicode/utf8"
"unsafe"
)

func main() {
c := "go语言"
s_rune_c := []rune(c)
s_byte_c := []byte(c)
fmt.Println(s_rune_c) // [103 111 35821 35328]
fmt.Println(s_byte_c) // [103 111 232 175 173 232 168 128]
fmt.Println(utf8.RuneCountInString(c)) //4
fmt.Println(len(c)) //8
fmt.Println(len(s_rune_c)) //4
}

这个程序就体现了byterune的区别,即

byte 按字节存取,不管这个字节是否有意义

rune 按unicode字符存取,所存取的一定有意义

回到题目的代码

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
package main

import (
"fmt"
"os"
"strings"
)

var flag = "irisctf{this_is_not_the_real_flag}"

func init() {
runed := []string{}
z := rune(0)

for _, v := range flag {
runed = append(runed, string(v+z))
z = v
}

flag = strings.Join(runed, "")
}

func main() {
file, err := os.OpenFile("the", os.O_RDWR|os.O_CREATE, 0644)
if err != nil {
fmt.Println(err)
return
}

defer file.Close()
if _, err := file.Write([]byte(flag)); err != nil {
fmt.Println(err)
return
}
}

它的输出文件长这样

截屏2024-01-16 17.32.29

很长而且很像,这其实就是go在把每个字符相加时,会超出ascii的范围,go就认为是其他种类的语言,就会更换存取方式

1
2
3
4
5
6
		a := 'f'
b := 'l'
c := a + b
fmt.Printf("a+b= %x",string(c))

# c3 92

使用Unicode解码即可

1
2
a_rune,_ := utf8.DecodeRune([]byte(string(a+b)))
fmt.Printf("%c\n",a_rune-'f')

可以方便的使用for语句进行解码

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
package main
import (
"fmt"
// "unicode/utf8"
)
func main() {
a1 := []byte{0x69, 0xC3, 0x9B, 0xC3, 0x9B, 0xC3, 0x9C, 0xC3, 0x96, 0xC3, 0x97, 0xC3,
0x9A, 0xC3, 0xA1, 0xC3, 0xA4, 0xC3, 0x88, 0xC3, 0x91, 0xC2, 0xA5, 0x67,
0x65, 0x62, 0xC2, 0xAA, 0xC3, 0x98, 0xC2, 0x90, 0xC2, 0x9A, 0xC3, 0x94,
0xC2, 0x9E, 0xC2, 0x92, 0xC3, 0x8D, 0xC3, 0xA3, 0xC3, 0xA2, 0xC2, 0xA3,
0x69, 0xC2, 0xA5, 0xC2, 0xA7, 0xC2, 0xB2, 0xC3, 0x8B, 0xC3, 0x85, 0xC3,
0x92, 0xC3, 0x8D, 0xC3, 0x88, 0xC3, 0xA4}
a := 0
// // 将字节数组转换为字符串
str := string(a1)

// // 解码整个字符串的 Unicode 码点
for _, r := range str {
// 将解码结果与整数 a 进行相减操作
result:= int(r) - a
fmt.Printf("%c",result)
a=result
}

// a := 'f'
// b := 'l'
// c := a + b
// fmt.Printf("a+b= %x\n",string(c))
// a_rune,_ := utf8.DecodeRune([]byte(string(a+b)))
// fmt.Printf("%c\n",a_rune-'f')
}

Maze

通过查看源代码等方式,可以发现网页游戏逻辑主要是混淆后的tfg-min.js所控制,在这个网站或者这个网站可以在线解混淆,然后可以扔去Cyberchef美化一下顺便排个版。解混淆之后的代码长这个样子

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
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
const e = document.getElementById('c'), t = e.getContext('2d'), a = [
[...Array(4).keys()].map(e => [
-17969,
-16540,
11745,
-12783,
3226,
15010,
10940,
3387,
-5306,
-4100,
-21425,
10338,
-16904,
-355,
13485,
-25858
].map(e => e / 503155).slice(4 * e, 4 * e + 4)),
[...Array(4).keys()].map(e => [
24356,
12443,
-34624,
-20408,
7719,
2169,
-12039,
-4767,
-11817,
-10941,
24441,
12396,
-17878,
-8011,
28295,
19198
].map(e => e / 138081).slice(4 * e, 4 * e + 4)),
[...Array(4).keys()].map(e => [
-14826,
3464,
5822,
-13182,
51761,
-11669,
-19467,
45292,
29097,
-6763,
-10919,
25324,
-11126,
2364,
4412,
-9672
].map(e => e / 10270).slice(4 * e, 4 * e + 4)),
[...Array(4).keys()].map(e => [
-10870,
13314,
3852,
6736,
8930,
-9852,
-1980,
-5468,
-982,
3891,
1980,
3481,
7174,
-9705,
-4194,
-6127
].map(e => e / 35766).slice(4 * e, 4 * e + 4))
], n = 'Dugd8DbBCXnrEF1kKd2Hg4lsRQ1eV/6gQ+NfwsVhtr4UgeXQFq1m6WctmIljEG7PZg==', r = [
'toy cube',
'laser pointer',
'large axle',
'gift box',
'dust pan',
'tea kettle',
'v-type engine',
'stop sign'
], i = {
a: 0,
b: 0,
c: [
0,
0,
0,
0
],
d: [
38,
40,
37,
39
],
e: [],
f: {
x: 40,
y: 40,
z: []
},
g: {
x: -7,
y: -7
},
h: [],
i: [],
j: 1,
k: 0,
l: ''
};
function c(e) {
let t = e + 1831565813;
return t = Math.imul(t ^ t >>> 15, 1 | t), t ^= t + Math.imul(t ^ t >>> 7, 61 | t), ((t ^ t >>> 14) >>> 0) / 4294967296;
}
function o(e, a, n, r, c = 1) {
t.fillStyle = `rgba(${[0].reduce((e, t) => e.slice((c - 1) % 3).concat(e.slice(0, (c - 1) % 3)), [
25,
255 * s(i.a, a / 500),
25
])}, 255)`, t.fillRect(e, a, n, r);
}
function y() {
if (i.g = i.c.reduce((e, t, a) => 1 === t ? {
x: e.x + (a < 2 || 1 & i.f.z[i.g.y + 7][i.g.x + 7 + (a - 2)] ? 0 : 2 * (a - 2) - 1),
y: e.y + (a >= 2 || 2 & i.f.z[i.g.y + 7 + a][i.g.x + 7] ? 0 : 2 * a - 1)
} : {
x: e.x,
y: e.y
}, {
x: i.g.x,
y: i.g.y
}), i.c.some(e => 1 === e)) {
i.j <<= 2, i.j |= 3 & i.d[i.c.findIndex(e => 1 === e)], i.j >= 64 && (i.i.push((63 & i.j) - 32), i.j = 1), i.k = i.k + 211 * (i.g.x + 9) * (i.g.y + 9) * 239 & 4294967295, 16 == i.i.length && 1 === i.j && i.e.push('1,6,11,16' == (e = [...Array(4).keys()].map(e => i.i.slice(4 * e, 4 * e + 4)), t = a[i.b], e.map(e => t[0].map((e, a) => t.map(e => e[a])).map(t => e.map((a, n) => e[n] * t[n]).reduce((e, t) => e + t)))).flatMap(e => e).map(e => Math.round(100 * e) / 100).map((e, t) => 1 === e ? t + 1 : e).filter(e => e) ? i.k : -1);
const r = i.h.findIndex(e => e.x === i.g.x && e.y === i.g.y);
if (-1 !== r) {
const e = i.h[r].name;
i.h.splice(r, 1), i.l = `found ${e}!`, setTimeout(() => {
i.l = '';
}, 4000);
}
i.g.x === i.f.x - 9 && i.g.y === i.f.y - 9 && (i.g.x = -7, i.g.y = -7, i.i = [], i.j = 1, i.k = 0, f(++i.b), 4 !== i.b || 4 !== i.e.length || i.e.some(e => -1 === e) || async function (e) {
const t = i.e.map(e => e.toString(16).padStart(8, '0')).join(''), a = new Uint8Array(atob(e).split('').map(e => e.charCodeAt(0))), n = Uint8Array.from([
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11
]), r = new TextEncoder().encode(t), c = await crypto.subtle.importKey('raw', r, { name: 'AES-GCM' }, !1, ['decrypt']), o = await crypto.subtle.decrypt({
name: 'AES-GCM',
iv: n
}, c, a);
return new TextDecoder().decode(o);
}(n).then(e => i.l = e));
}
var e, t;
i.c = i.c.map(e => 1 & e ? 3 : 0);
}
function l() {
var a;
t.clearRect(0, 0, e.width, e.height), o(e.width / 2 - 4, e.height / 2 - 4, 8, 8), i.h.forEach(t => {
o(e.width / 2 - 4 + 40 * (t.x - i.g.x), e.height / 2 - 4 + 40 * (t.y - i.g.y), 8, 8, 2);
}), [...Array(i.f.y).keys()].forEach(e => {
[...Array(i.f.x).keys()].forEach(t => {
0 != (2 & i.f.z[e][t]) && o(40 * (t - i.g.x), 40 * (e - i.g.y), 40, 1), 0 != (1 & i.f.z[e][t]) && o(40 * (t - i.g.x), 40 * (e - i.g.y), 1, 40);
});
}), a = i.l, t.textAlign = 'center', t.font = '32px monospace', t.fillStyle = 'rgba(25, 255, 25, 255)', t.fillText(a, e.width / 2, e.height / 2);
}
function f(e) {
i.f.z = [...Array(i.f.y).keys()].map(t => [...Array(i.f.x - 1).keys()].reduce((a, n) => t > 0 && t < i.f.x - 1 && c(23 * n + 7 * t + 3 * e) > 0.5 == 0 ? [
[
...a[0].slice(0, a[1] + Math.floor(c(17 * n + 9 * t + 3 * e) * (n - a[1] + 1))),
1 | a[0][a[1] + Math.floor(c(17 * n + 9 * t + 3 * e) * (n - a[1] + 1))],
...a[0].slice(a[1] + Math.floor(c(17 * n + 9 * t + 3 * e) * (n - a[1] + 1)) + 1)
],
n + 1
] : [
[
...a[0].slice(0, n),
2 | a[0][n],
...a[0].slice(n + 1)
],
a[1]
], [
t < i.f.x - 1 ? [
1,
...new Array(i.f.x - 2).fill(0),
1
] : new Array(i.f.x).fill(0),
0
])).map(e => e[0]), i.h = [...Array(6).keys()].map(t => ({
x: Math.floor(30 * c(17 * t + 23 * e)),
y: Math.floor(30 * c(23 * t + 17 * e)),
name: r[Math.floor(8 * c(3 * t + 21 * e))]
}));
}
function s(e, t, a = 800, n = 0.6, r = 0.03) {
const i = (e + t) % a;
return (0.8 + Math.sin(7 * e) * r) * Math.min(1, n + Math.max(0, 0.009 * (i - a / 2) ** 2));
}
e.width = 600, e.height = 600, document.onkeydown = e => {
i.c[i.d.indexOf(e.keyCode)] |= 1;
}, document.onkeyup = e => {
i.c[i.d.indexOf(e.keyCode)] = 0;
}, f(i.b), function e() {
y(), l(), i.a++, requestAnimationFrame(e);
}();

函数有很多,不过重点是在y()函数,这里面有AESdecrypt等比较敏感的字样。

现在就可以开始下断点调试,需要先把所有文件都下载下来,然后注意文件里面的路径需要修改一下,最后用浏览器打开index.html就可以在浏览器的控制台调试了。

把断点下在y()函数if语句后的第一行

截屏2024-01-16 00.21.20

会发现每次移动,都会断在这个地方,而如果把下在其他地方或者其他函数,就会不断的中断。因此说明找对了函数并也断在了合适的地方,因为迷宫肯定是需要每走一步就去判断有没有到终点等等,因此走一步断一步才是比较合理的。

可以去控制台打印一下变量i,看看这里面有啥

截屏2024-01-16 00.08.20

通过不断调试,可以确定如下信息

i.f => 迷宫信息,包括终点坐标和迷宫路径(40*40的数组)

i.g => 玩家现在的坐标

i.h => 迷宫中六个红点位的坐标

i.l => 打印在屏幕上的信息

其余信息目前则暂时不清楚。

现在可以再把断点语句给整理一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
if (i.g.x === i.f.x - 9 && i.g.y === i.f.y - 9) {
i.g.x = -7,
i.g.y = -7,
i.i = [],
i.j = 1,
i.k = 0,
f(++i.b),
4 !== i.b || 4 !== i.e.length || i.e.some(e => -1 === e) || async function (e) {
const t = i.e.map(e => e.toString(16)
.padStart(8, '0'))
.join(''),
a = new Uint8Array(atob(e).split('').map(e => e.charCodeAt(0))),
n = Uint8Array.from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]),
r = new TextEncoder().encode(t),
c = await crypto.subtle.importKey('raw', r, { name: 'AES-GCM' }, !1, ['decrypt']), o = await crypto.subtle.decrypt({
name: 'AES-GCM',
iv: n
}, c, a);
return new TextDecoder().decode(o);
}(n).then(e => i.l = e);
}

这里大概流程就是先判断有没有到终点,然后将信息重置,调用一下f(++i.b),这里猜测是重新生成迷宫,i.b用来记录迷宫的层数。然后满足一定条件后就可以进入下面的解密环节。

这里有个JS条件语句的判断特点即

a==x && b==y && c 等价于 if (a==x && b==y) {c}

a == x || b==y || c 等价于 if(!(a==x || b==y)) {c}

那么这里4 !== i.b || 4 !== i.e.length || i.e.some(e => -1 === e) || async function (e) {...}就等价于if(!(4 !== i.b || 4 !== i.e.length || i.e.some(e => -1 === e))){async function (e) {...}},也就是想要解密需要满足这几个条件

  1. i.b == 4 即通过四层迷宫
  2. i.e.length ==4 这个暂时不知道是什么
  3. i.e 这个数组没有 -1

因此现在核心又转到i.e的生成上,往前看就有i.e的生成方式,也就是断点那个地方

截屏2024-01-16 00.22.30

同样把这一句话的JS给整理一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
i.j <<= 2;
i.j |= 3 & i.d[i.c.findIndex(e => 1 === e)];

if (i.j >= 64) {
i.i.push((63 & i.j) - 32);
i.j = 1;
}

i.k = i.k + 211 * (i.g.x + 9) * (i.g.y + 9) * 239 & 4294967295;

if (16 == i.i.length && 1 === i.j) {
e = [...Array(4).keys()].map(e => i.i.slice(4 * e, 4 * e + 4));
t = a[i.b];
data = e.map(e => t[0].map((e, a) => t.map(e => e[a])).map(t => e.map((a, n) => e[n] * t[n]).reduce((e, t) => e + t)));
data = flatMap(e => e).map(e => Math.round(100 * e) / 100).map((e, t) => 1 === e ? t + 1 : e).filter(e => e)

i.e.push('1,6,11,16' == data ? i.k : -1)
};

直接看最后的比较语句,若data == '1,6,11,16'(这里的data是自己增加的变量,用于拆分运算,便于审计),那么i.e就会压入一个i.k否则就压入-1那么就会失败。这个data又是通过et的运算得到,这里的e是通过[...Array(4).keys()].map(e => i.i.slice(4 * e, 4 * e + 4))语句得到,也就是i.i,而t则是已知的一个数组

截屏2024-01-16 00.27.55

因此现在又需要知道i.i是怎么得到的,就继续往前看

1
2
3
4
if (i.j >= 64) {
i.i.push((63 & i.j) - 32);
i.j = 1;
}

这里会有i.i的操作,i.j则是这又这两个操作得到

1
2
i.j <<= 2;
i.j |= 3 & i.d[i.c.findIndex(e => 1 === e)];

其中i.di.c都是已知数组

截屏2024-01-16 00.30.30

截屏2024-01-16 00.32.49

结合移动只有上下左右四个方向,猜测这里就应该对应四个方向,下个断点

截屏2024-01-16 00.34.00

发现确实如此,即每个方向都对应一个值。

1
2
3
4
down 	-> 40  & 3  = 00
right -> 39 & 3 = 11
left -> 37 & 3 = 01
up -> 38 & 3 = 10

那么这段语句实际含义就是这样子,每走一步,取这步对应的两位值,每次走了三步后减32再push进i.i

1
2
3
4
5
6
7
8
9
10
11
12
13
i.j <<= 2;
i.j |= 3 & i.d[i.c.findIndex(e => 1 === e)];

if (i.j >= 64) {
i.i.push((63 & i.j) - 32);
i.j = 1;
}

00 00 00 01
00 00 11 10
00 01 11 11
01 11 11 11
i.i.push(01 11 11 11 - 01 00 00 00)

那么现在就知道了i.i的生成方式,重新回头看那个if判断语句

1
2
3
4
5
6
7
8
if (16 == i.i.length && 1 === i.j) {
e = [...Array(4).keys()].map(e => i.i.slice(4 * e, 4 * e + 4));
t = a[i.b];
data = e.map(e => t[0].map((e, a) => t.map(e => e[a])).map(t => e.map((a, n) => e[n] * t[n]).reduce((e, t) => e + t)));
data = data.flatMap(e => e).map(e => Math.round(100 * e) / 100).map((e, t) => 1 === e ? t + 1 : e).filter(e => e)

i.e.push('1,6,11,16' == data ? i.k : -1)
};

e = [...Array(4).keys()].map(e => i.i.slice(4 * e, 4 * e + 4));这个语句会生成一个4*4数组

截屏2024-01-16 09.50.20

验证一下两个语句的作用

1
data = e.map(e => t[0].map((e, a) => t.map(e => e[a])).map(t => e.map((a, n) => e[n] * t[n]).reduce((e, t) => e + t)));
截屏2024-01-16 10.00.17
1
data = data.flatMap(e => e).map(e => Math.round(100 * e) / 100).map((e, t) => 1 === e ? t + 1 : e).filter(e => e)
截屏2024-01-16 10.03.13

第二个语句像是取整操作,但会发现少了一个值,即第一个data的data[0][1]=-0.000476...,这个值没了,这里尝试一些特殊矩阵,比如单位矩阵

截屏2024-01-16 10.09.10

正好对上,那么猜测前面的语句应该是矩阵乘法,我们就需要找到这个每个矩阵的逆,然后再接着反推出每次移动的方,简单求一下各个矩阵的逆

截屏2024-01-16 10.19.07

正好都在32以内,侧面验证了猜想。

现在就有i.i,可以反推出i.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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
import numpy as np

a = [
[[-0.035712653158569425, -0.03287257405769594, 0.02334270751557671, -0.02540569009549741],
[0.006411543162643718, 0.02983176158440242, 0.02174280291361509, 0.0067315240830360425],
[-0.010545458159016606, -0.00814858244477348, -0.042581311921773606, 0.020546352515626396],
[-0.033596009182061196, -0.000705547992169411, 0.02680088640677326, -0.051391718257793324]],

[[0.17638922081966382, 0.09011377379943657, -0.2507513705723452, -0.14779730737755375],
[0.055901970582484195, 0.015708171290764118, -0.08718795489603928, -0.034523214634888215],
[-0.08558020292437048, -0.07923610054967736, 0.17700480152953701, 0.08977339387750669],
[-0.1294747286013282, -0.05801667137404857, 0.20491595512778732, 0.13903433491935893]],

[[-1.4436222005842259, 0.33729308666017527, 0.5668938656280429, -1.2835443037974683],
[5.040019474196689, -1.136222005842259, -1.895520934761441, 4.410126582278481],
[2.833203505355404, -0.6585199610516066, -1.0631937682570594, 2.4658227848101264],
[-1.0833495618305744, 0.23018500486854918, 0.4296007789678676, -0.9417721518987342]],

[[-0.30391992395012024, 0.37225297768830734, 0.10770005032712632, 0.18833529049935693],
[0.2496784655818375, -0.27545713806408323, -0.055359838953195774, -0.15288262595761337],
[-0.027456243359615277, 0.10879047139741654, 0.055359838953195774, 0.09732707040205782],
[0.20058155790415477, -0.2713470894145278, -0.11726220432813286, -0.1713079460940558]]
]
ans = []

def cac_ik(i_e: np.array):
i_e = i_e.reshape(1, 16)[0]
i_e = list(i_e)
move = []
for x in i_e:
move.append(bin(round(x) + 32)[2:].zfill(6))
i_x = -7
i_y = -7
i_k = 0
for x in move:
for k in range(0, 6, 2):
step = x[k:k + 2]
if step == '00':
i_x = i_x + 1
elif step == '11':
i_y = i_y + 1
elif step == '01':
i_y = i_y - 1
elif step == '10':
i_x = i_x - 1
i_k = i_k + 211 * (i_x + 9) * (i_y + 9) * 239 & 4294967295
ans.append(i_k)

# for i in range(16):
# print(move)


for i in range(4):
aa = np.array(a[i])
arr = np.linalg.inv(aa)
cac_ik(arr)
print(ans)
# [40645774, 130661539, 116339703, 150379278]

最后直接在控制台控制变量就可以打印出flag

截屏2024-01-16 10.58.42

(1.需要使用https协议,2.需要到终点后手动再移动一下)


2024IrisCTF
http://example.com/2024/01/14/2024IrisCTF/
Author
yring
Posted on
January 14, 2024
Licensed under