BuckeyeCTF 2022

null太难了打不动…

这个比赛还是学到了点密码学的知识的。

暂时只做了密码学和逆向。

Crypto

megaxord

1
2
3
Some pesky wizard stole the article I was writing. I got it back, but it's all messed up now :(

Hint: the wizard used the same magic on every character...

打开文件看一眼:

1
2
7/=*x
96?=*+x1+x96x5=*1;96x=6,=*,9165=6,x96<x5=*;096<1+16?x>*96;01+=x:-14,x9*7-6<x9x41.=u9;,176x+-(=*0=*7x,=4=.1+176x+=*1=+tx:9+=<x76x,0=x9(96=+=x,73-+9,+-x>*96;01+=x-(=*x=6,91vx*7<-;=<x>1*+,x:!x9:96x6,=*,9165=6,tx+=;76<x:!xx6,=*,9165=6,tx49,=*x:!x9:96x*96<+tx96<x,7<9!x:!xx7/=*x

猜测是异或加密。取第一行作为样本,来爆破密钥。

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 Crypto.Util.strxor import strxor

with open(r"megaxord.txt", "rb") as fp:
raw = fp.read()
data = raw.splitlines()

test = data[0]

for i in range(255):
print(i, strxor(bytes([i])*len(test), test))

"""
85 b']bzh\x7f-'
86 b'^ayk|.'
87 b'_`xj}/'
88 b'Power '
89 b'Qnvds!'
90 b'Rmugp"'
"""

print(strxor(bytes([88])*len(raw), raw))

"""
The New Zealand Herald published on March 7, 2009, identified RPM as the last season of the Power Rangers run.
Production manager Sally Campbell stated in an interview, "...at this stage we will not be shooting another
season."[buckeye{m1gh7y_m0rph1n_w1k1p3d14_p4g3}][23] A September
"""

# buckeye{m1gh7y_m0rph1n_w1k1p3d14_p4g3}

Twin prime RSA

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

while True:
p = cun.getPrime(1024)
q = p + 2
if cun.isPrime(q):
break

n = p * q
e = 0x10001

phi = (p - 1) * (q - 1)
d = pow(e, -1, phi)

FLAG = cun.bytes_to_long(b"buckeye{?????????????????????????????????????????????????????????????}")
c = pow(FLAG, e, n)
assert pow(c, d, n) == FLAG

print(f"n = {n}")
print(f"c = {c}")

开方求解。

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
from Crypto.Util.number import *
from gmpy2 import iroot, invert
from sympy import nextprime, prevprime

n = ...
c = ...
e = 0x10001

sqrt = int(iroot(n, 2)[0])

if isPrime(sqrt):
p = sqrt
q = nextprime(p)
else:
p = prevprime(sqrt)
q = nextprime(sqrt)

assert abs(p-q) == 2

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

print(long_to_bytes(pow(c,d,n)))

# b'buckeye{B3_TH3R3_OR_B3_SQU4R3__abcdefghijklmonpqrstuvwxyz__0123456789}'
# buckeye{B3_TH3R3_OR_B3_SQU4R3__abcdefghijklmonpqrstuvwxyz__0123456789}

fastfor

这题不知道他在考什么…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from PIL import Image
import numpy

def check_hash(fi):
image = numpy.asarray(Image.open('static/IMG.png'))
submission = numpy.asarray(Image.open(fi))
if image.shape != submission.shape:
return False
same = numpy.bitwise_xor(image, submission)
if (numpy.sum(same) == 0):
return False
im_alt = numpy.fft.fftn(image)
in_alt = numpy.fft.fftn(submission)
im_hash = numpy.std(im_alt)
in_hash = numpy.std(in_alt)
if im_hash - in_hash < 1 and im_hash - in_hash > -1:
return True
return False

就是算了一下两个的n维傅里叶变换的值,判断前后的差值是否在1中。但实际上我只要修改一个点就过了。而且由于浮点数精度的问题。傅里叶和还是一样的。

这题有个web网页。IMG.png是从网站上拔下来的。

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
import numpy
from PIL import Image

image = numpy.array(Image.open('IMG.png'))
fixed = image.copy()
fixed[0][0][0] = image[0][0][0] - 100

print(fixed[0][0][0])
print(image[0][0][0])
im_alt = numpy.fft.fftn(image)
im_hash = numpy.std(im_alt)

print(im_hash)

in_alt = numpy.fft.fftn(fixed)
in_hash = numpy.std(in_alt)

print(in_hash)

same = numpy.bitwise_xor(image, fixed)
if (numpy.sum(same) == 0):
print("NO")


im = Image.fromarray(fixed)
im.save("fixed.png")

# buckeye{D33p_w0Rk_N07_WhY_574ND4RD_d3V}

powerball

1
What could go wrong using a Linear Congruential Generator to get some random numbers?

直接告诉你是LCG问题。

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
import express from 'express'
import http from 'http'
import { Server } from 'socket.io'
import crypto from 'crypto'

function nextRandomNumber () {
return (multiplier * seed) % modulus
}

function areArraysEqual (a, b) {
return (
a.length === b.length &&
a.every((x, i) => {
return x === b[i]
})
)
}

function seedToBalls (n) {
const balls = []
for (let i = 0; i < 10; i++) {
balls.push(Number(n % 100n))
n = n / 100n
}
return balls
}

const app = express()
app.use(express.static('static'))

const server = http.createServer(app)
const io = new Server(server)

const modulus = crypto.generatePrimeSync(128, { safe: true, bigint: true })
const multiplier = (2n ** 127n) - 1n
let seed = 2n
for (let i = 0; i < 1024; i++) {
seed = nextRandomNumber()
}
let winningBalls = seedToBalls(seed)
let lastLotteryTime = Date.now()

setInterval(() => {
seed = nextRandomNumber()
winningBalls = seedToBalls(seed)
lastLotteryTime = Date.now()
}, 60 * 1000)

io.on('connection', (socket) => {
socket.ticket = { balls: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], submissionTime: 0 }

socket.on('updateRequest', () => {
let flag = ''
if (
areArraysEqual(socket.ticket.balls, winningBalls) &&
socket.ticket.submissionTime < lastLotteryTime
) {
flag = process.env.FLAG
}

socket.emit('update', {
last_winning_seed: seed.toString(),
flag: flag
})
})

socket.on('submitBalls', (balls) => {
if (!(Array.isArray(balls) && balls.length === 10)) return
for (let i = 0; i < 10; i++) {
if (typeof balls[i] !== 'number') return
}

socket.ticket = { balls: balls, submissionTime: Date.now() }
})
})

server.listen(3000, () => {
console.log('Ready')
})

这题也算半个web了。f12控制台会放出seed的信息。

1
2
3
4
{
"last_winning_seed": "118531139462567680237383046608527171358",
"flag": ""
}

等个几个分钟,多收集一点seed序列。

这个序列的长度不固定,有可能你运气好4个就够了,这次我用了8个才出正确的结果。一直添加直到能过检查。

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
from math import gcd
from Crypto.Util.number import *
from functools import reduce
from libnum import n2s


Xn = [95306060132134274034264579463842889279, 129883182640808436703118317079255328712, 116962711139558921267457520653198681598, 46574140996987716764198865501432463924,
248115531362286822224564168433858654366, 157763231920003498467573503185948866818, 174267870530630061122438873970235742109, 134449749791170055602523013022475444442]
mul = 2**127 -1

def crack_unknown_modulus(states):
diffs = [s1 - s0 for s0, s1 in zip(states, states[1:])]
zeroes = [t2*t0 - t1*t1 for t0, t1, t2 in zip(diffs, diffs[1:], diffs[2:])]
modulus = abs(reduce(gcd, zeroes))
return modulus

n = crack_unknown_modulus(Xn)
assert isPrime(n) and n.bit_length() == 128
print(n)

next_number = lambda x: mul*x % n

winner = next_number(71129646383219012561794335952713553500)
print(winner)
numbers = []
for i in range(10):
numbers.append(winner % 100)
winner //= 100

print(numbers)

"""
282197447588972913465964086601636273787
214505068864644244632829240151367451760
[60, 17, 45, 67, 13, 15, 40, 92, 82, 32]
"""

把这个序列输入到网站中等待下一次刷新即可。

1
2
3
4
5
6
{
"last_winning_seed": "214505068864644244632829240151367451760",
"flag": "buckeye{y3ah_m4yb3_u51nG_A_l1N34r_c0nGru3Nt1al_G3n3r4t0r_f0r_P0w3rB4lL_wA5nt_tH3_b3st_1d3A}"
}

// buckeye{y3ah_m4yb3_u51nG_A_l1N34r_c0nGru3Nt1al_G3n3r4t0r_f0r_P0w3rB4lL_wA5nt_tH3_b3st_1d3A}

SSSHIT

算是有意思的一题。

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
import Crypto.Util.number as cun
import random
import ast


def evaluate_polynomial(polynomial: list, x: int, p: int):
return (
sum(
(coefficient * pow(x, i, p)) % p for i, coefficient in enumerate(polynomial)
)
% p
)


N_SHARES = 3


def main():
print(
f"I wrote down a list of people who are allowed to get the flag and split it into {N_SHARES} using Shamir's Secret Sharing."
)
MESSAGE = cun.bytes_to_long(b"qxxxb, BuckeyeCTF admins, and NOT YOU")

p = cun.getPrime(512)

polynomial = [MESSAGE] + [random.randrange(1, p) for _ in range(N_SHARES - 1)]
points = [(i, evaluate_polynomial(polynomial, i, p)) for i in range(1, N_SHARES + 1)]

print("Your share is:")
print(points[0])
print("The other shares are:")
for i in range(1, len(points)):
print(points[i])

print()
print("Now submit your share for reconstruction:")
your_input = ast.literal_eval(input(">>> "))
if (
type(your_input) is not tuple
or len(your_input) != 2
or type(your_input[0]) is not int
or type(your_input[1]) is not int
or your_input[0] != 1
or not (0 <= your_input[1] < p)
):
print("Bad input")
return

points[0] = your_input

xs = [point[0] for point in points]
ys = [point[1] for point in points]

y_intercept = 0
for j in range(N_SHARES):
product = 1
for i in range(N_SHARES):
if i != j:
product = (product * xs[i] * pow(xs[i] - xs[j], -1, p)) % p
y_intercept = (y_intercept + ys[j] * product) % p

reconstructed_message = cun.long_to_bytes(y_intercept)
if reconstructed_message == b"qxxxb, BuckeyeCTF admins, and ME":
print("Here's your flag:")
print("buckeye{?????????????????????????????????????????}")
else:
print(f"Sorry, only these people can see the flag: {reconstructed_message}")


main()

简单来说就是要求我们输入一个新的秘密分片。从而将解的的秘密修改。但是我们不知道模数,这点就很烦了。

1
2
3
4
5
6
7
8
9
I wrote down a list of people who are allowed to get the flag and split it into 3 using Shamir's Secret Sharing.
Your share is:
(1, 4606841840792655741097335098615362105611241060090579768729749798744970453857096222319491682007964447453172274966991930716097294236308788531966428690778079)
The other shares are:
(2, 4756354074373237467895340117578308603595140714341760525573566385495771532267815165432109346493025885413182472403095126874117192693469522589916267535361022)
(3, 448536700741745180394015056888839493951698962753542270531449760308834795408073603079157806360778988603183244270431724096406965890008305428978180913324978)

Now submit your share for reconstruction:
>>>

现在我们直到三个密文,和三个x。对于这个x,我们在其解密算法中有:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
for j in range(3):
product = 1
for i in range(3):
if i != j:
print(xs[i], xs[i] - xs[j])
print("\n")

"""
2 1
3 2
1 -1
3 1
1 -2
2 -1
"""

那么其算法就为:

211321y1+1(1)1311y2+1(2)12(1)1y3=MESSAGE2*1^{-1}*3*2^{-1}y_1 + 1*(-1)^{-1}*3*1^{-1}y_2 + 1*(-2)^{-1}*2*(-1)^{-1}y_3 = MESSAGE

化简为:

3y13y2+y3=MESSAGE3y_1-3y_2 + y_3 = MESSAGE

由于不存在分数。我们可以在不知道模数的情况下进行计算。

那么这个式子其实有三种情况。

3y_1-3y_2 + y_3 - MESSAGE = \left\{ \begin{array}{**lr**} 0 & \\ m\\ km \end{array} \right.

在下面的两种情况,我们可以求出模数m。

而且以这个式子为基础,我们可以修改任意的明文。

由于我们只能修改y1y_1,所以这里我们设对y1y_1的修改为xx,目标为MESSAGE2MESSAGE2。有以下推到:

3(y1+x)3y2+y3=MESSAGE23x+MESSAGE=MESSAGE23(y_1 + x)-3y_2 + y_3 = MESSAGE2 \\ 3x + MESSAGE = MESSAGE2

所以我们只需要找到合适的x即可修改我们的明文。

但是这里有个小问题就是,我们的MESSAGE2MESSAGEMESSAGE2 - MESSAGE他并不是三的倍数。所以我们需要使用模数对他进行修正。 因为在模m的情况下,加减m对结果是没有影响的。

由于环境每次都是随机的模数,所以我们多试几次就能得到满足要求的数,即可得到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
from pwn import *
from Crypto.Util.number import *
from sympy import *

sh = remote("pwn.chall.pwnoh.io", 13382)

sh.recvuntil(b"Your share is:\n")
point = []
point.append(eval(sh.recvline()[:-1]))

sh.recvline()
point.append(eval(sh.recvline()[:-1]))
point.append(eval(sh.recvline()[:-1]))
print(point)
sh.recvuntil(b">>> ")

MESSAGE = bytes_to_long(b"qxxxb, BuckeyeCTF admins, and NOT YOU")
message = bytes_to_long(b"qxxxb, BuckeyeCTF admins, and ME")

y1,y2,y3 = point[0][1],point[1][1],point[2][1]

kp = 3*y1 - 3*y2 + y3

# 等于0的情况,这种情况下我们无法解
if kp == MESSAGE:
print("This time fail")
exit(0)

# 这种情况下我们需要手动进行yafu计算m,时间可能来不及。
if not isPrime(3*y1 - 3*y2 + y3 - MESSAGE):
print(f"need factor {3*y1 - 3*y2 + y3 - MESSAGE}")
exit(0)

# 找到p,而且p与差值的和是三的倍数。这个我们就可以解了。
p = 3*y1 - 3*y2 + y3 - MESSAGE
if (p + message - MESSAGE) % 3 == 0:
print("this is chance!")
sh.sendline(f"(1,{y1 + (p + (message - MESSAGE)) // 3})".encode("UTF-8"))
print(sh.recvall())


# buckeye{tH1s_SSS_sch3Me_c0uLd_u5e_s0M3_S1gna7Ur3s}

这里面这个解密算法还是需要研究研究的。在一定程度上,如果用这种解密算法的话。我们可以做到伪造任意明文。

bonce

奇奇怪怪的一提,不难。

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
import random

with open('sample.txt') as file:
line = file.read()

with open('flag.txt') as file:
flag = file.read()

samples = [line[i:i+28] for i in range(0, len(line) - 1 - 28, 28)]

samples.insert(random.randint(0, len(samples) - 1), flag)

i = 0
while len(samples) < 40:
samples.append(samples[len(samples) - i - 2])
i = random.randint(0, len(samples) - 1)

encrypted = []
for i in range(len(samples)):
x = samples[i]
if i < 10:
nonce = str(i) * 28
else:
nonce = str(i) * 14
encrypted.append(''.join(str(ord(a) ^ ord(b)) + ' ' for a,b in zip(x, nonce)))

with open('output.txt', 'w') as file:
for i in range(0, 4):
file.write('input: ' + samples[i] + '\noutput: ' + encrypted[i] + '\n')
file.write('\n')
for i in range(4, len(samples)):
file.write('\ninput: ???\n' + 'output: ' + encrypted[i])

也就是异或加密。密钥也都告诉你了。

1
2
3
4
if i < 10:
nonce = str(i) * 28
else:
nonce = str(i) * 14

直接解就行了。

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
with open(r"output.txt", "r") as fp:
data = fp.read()

lines = data.splitlines()

lines = list(filter(lambda x: x.startswith("output: "), lines))

output = []
for item in lines:
output.append(list(map(int,item.split()[1:])))


for i in range(len(output)):
x = output[i]
if i < 10:
nonce = str(i) * 28
else:
nonce = str(i) * 14

print("".join( chr(a^ord(b)) for a,b in zip(x, nonce) ))

"""
indows of thine age shall se
eDespite of wrinkles this th
y golden time.But if thou li
buckeye{some_say_somefish:)}
ve, remember’d not to be,D
ie single, and thine image d
"""

# buckeye{some_say_somefish:)}

Quad prime RSA

感谢这题让我了解到了连分数在渐进的应用。

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 Crypto.Util.number as cun

p = cun.getPrime(500)

while True:
q = cun.getPrime(1024)
r = q + 2
if cun.isPrime(r):
break

s = cun.getPrime(500)

n_1 = p * q
n_2 = r * s

e = 0x10001
d_1 = pow(e, -1, (p - 1) * (q - 1))
d_2 = pow(e, -1, (r - 1) * (s - 1))

FLAG = cun.bytes_to_long(b"buckeye{??????????????????????????????????????????????????????????????????????}")
c_1 = pow(FLAG, e, n_1)
c_2 = pow(FLAG, e, n_2)

assert pow(c_1, d_1, n_1) == FLAG
assert pow(c_2, d_2, n_2) == FLAG

print(f"n_1 = {n_1}")
print(f"n_2 = {n_2}")
print(f"c_1 = {c_1}")
print(f"c_2 = {c_2}")

"""
Output:
n_1 = ...
n_2 = ...
c_1 = ...
c_2 = ...
"""

在这题中。我们可以得到一个结论。

n1n2=pqrs=pq(q+2)sps\frac{n_1}{n_2} = \frac{pq}{rs} = \frac{pq}{(q+2)s} \approx\frac{p}{s}

那么在这种情况下我们可以使用连分数逼近。当我们找到一个连分数可以整除n时。那么我们就找到了对于的p或s。这也是拓展维纳攻击的核心。

这一部分的学习还要深入,这题这次能做出来不过恰好我找到了对于的代码而已。

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

e = 0x10001

def transform(x, y): # 使用辗转相除将分数x/y转为连分数的形式
res = []
while y:
res.append(x // y)
x, y = y, x % y
return res


def continued_fraction(sub_res):
numerator, denominator = 1, 0
for i in sub_res[::-1]: # 从sublist的后面往前循环
denominator, numerator = numerator, i * numerator + denominator
return denominator, numerator # 得到渐进分数的分母和分子,并返回


# 求解每个渐进分数
def sub_fraction(x, y):
res = transform(x, y)
res = list(map(continued_fraction, (res[0:i] for i in range(1, len(res))))) # 将连分数的结果逐一截取以求渐进分数
return res


def wienerAttack(n1, n2):
for (q2, q1) in sub_fraction(n1, n2): # 用一个for循环来注意试探n1/n2的连续函数的渐进分数,直到找到一个满足条件的渐进分数
if q1 == 0: # 可能会出现连分数的第一个为0的情况,排除
continue
if n1 % q1 == 0 and q1 != 1: # 成立条件
return (q1, q2)
print("该方法不适用")


N1 = 266809852588733960459210318535250490646048889879697803536547660295087424359820779393976863451605416209176605481092531427192244973818234584061601217275078124718647321303964372896579957241113145579972808278278954608305998030194591242728217565848616966569801983277471847623203839020048073235167290935033271661610383018423844098359553953309688771947405287750041234094613661142637202385185625562764531598181575409886288022595766239130646497218870729009410265665829
N2 = 162770846172885672505993228924251587431051775841565579480252122266243384175644690129464185536426728823192871786769211412433986353757591946187394062238803937937524976383127543836820456373694506989663214797187169128841031021336535634504223477214378608536361140638630991101913240067113567904312920613401666068950970122803021942481265722772361891864873983041773234556100403992691699285653231918785862716655788924038111988473048448673976046224094362806858968008487
C = 90243321527163164575722946503445690135626837887766380005026598963525611082629588259043528354383070032618085575636289795060005774441837004810039660583249401985643699988528916121171012387628009911281488352017086413266142218347595202655520785983898726521147649511514605526530453492704620682385035589372309167596680748613367540630010472990992841612002290955856795391675078590923226942740904916328445733366136324856838559878439853270981280663438572276140821766675
p,s = wienerAttack(N1, N2)

q = N1 // p
r = N2 // s

print(q,r)

d = pow(e, -1, (p - 1) * (q - 1))
print(long_to_bytes(pow(C,d,N1)))

# b'buckeye{I_h0p3_y0u_us3D_c0nt1nu3d_fr4ct10Ns...th4nk5_d0R5A_f0r_th3_1nsp1r4t10n}'
# buckeye{I_h0p3_y0u_us3D_c0nt1nu3d_fr4ct10Ns...th4nk5_d0R5A_f0r_th3_1nsp1r4t10n}

Revserse

sinep

flag的加密值为0x111c0d0e150a0c151743053607502f1e10311544465c5f551e0e。

给的文件时加密算法。

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
int result; // eax
size_t v4; // rbx
_WORD v5[9]; // [rsp+1Ah] [rbp-26h] BYREF
int i; // [rsp+2Ch] [rbp-14h]

if ( argc == 2 )
{
printf("Your plain text: %s\n", argv[1]);
puts("Applying Sinep Industry's Certified unbreakable algorithm.");
strcpy((char *)v5, "sinep");
*(_QWORD *)&v5[3] = argv[1];
printf("Final: 0x");
for ( i = 0; ; ++i )
{
v4 = i;
if ( v4 >= strlen(*(const char **)&v5[3]) )
break;
*(_BYTE *)(i + *(_QWORD *)&v5[3]) ^= *((_BYTE *)v5 + i % 5);
printf("%02x", (unsigned int)*(char *)(i + *(_QWORD *)&v5[3]));
}
putchar(10);
result = 0;
}
else
{
puts("Please enter the text to apply Sinep's patented algorithm.");
result = 1;
}
return result;
}

其实就是用一个key做异或加密。key是sinep。

1
2
3
4
5
6
7
8
9
10
from Crypto.Util.strxor import strxor
from Crypto.Util.number import *

target = long_to_bytes(0x111c0d0e150a0c151743053607502f1e10311544465c5f551e0e)
key = b"sinep"

print(strxor((key*6)[:len(target)], target))

# b'buckeye{r3v_i5_my_p45510n}'
# buckeye{r3v_i5_my_p45510n}

soda

很有意思的一题。

反编译的文件比较长这里就不放出来了。符号表都在,简单看看看到了printflag这个函数。从这个函数出发来找那个地方调用了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public void retrieve() {
int var1 = -1;
float var2 = -1.0F;

for(int var3 = 0; var3 < 12; ++var3) {
if (this.drinks[var3].status != soda.Drink.DrinkStatus.EMPTY && this.drinks[var3].cost > var2) {
var1 = var3;
var2 = this.drinks[var3].cost;
}
}

if (this.drinks[var1].status == soda.Drink.DrinkStatus.DROPPED) {
soda.printFlag();
} else {
System.out.println(">> No flags in here... was the prophecy a lie...?");
}

}

在retrive中我们调用了printflag。得到flag的要求是,var1指向的饮料的状态是掉落的(这里还有一个状态是空,空和掉落是不一样的。这个就要求了这个必须是我们买到的才有dropped状态)。而var1是由上面的for循环指定的。这个循环是将var1指向到饮料机里面最贵的饮料。所以我们必须要买最贵的饮料才能得到flag。

看看怎么进入retrieve这个函数。

1
2
3
4
5
6
7
} else if (var1[0].equalsIgnoreCase("grab")) {
if (var0.dropped > 0) {
System.out.println(">> Alright!! Let's see what I got!");
var0.retrieve();
} else {
System.out.println(">> There's nothing to grab...");
}

要求我们输入"grab"指令并且要有掉落的饮料才可进入。

那么这个drop要怎么修改呢。我们接着跟踪。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
} else if (var1[0].equalsIgnoreCase("reach")) {
if (bystanders) {
System.out.println(">> I can't do that with people around!\n>> They'll think I'm stealing!");
} else {
var6 = var0.reach();
var0.dropped += var6;
if (var6 > 0) {
System.out.println(">> Ok, here goes... gonna reach through the door and try to knock it down...");
pause(3);
System.out.println(">> !!! I heard something fall!");
} else {
System.out.println(">> There's nothing to reach for");
}
}

只有这个reach函数能增加dropped。那么进入这个代码块的条件是bystanders为false。而且还和reach的返回值有关。我们先看看reach函数是怎么样的,

1
2
3
4
5
6
7
8
9
10
11
public int reach() {
int var1 = 0;

for(int var2 = 0; var2 < 12; ++var2) {
if (this.drinks[var2].status == soda.Drink.DrinkStatus.STUCK && this.drinks[var2].stuck == 0) {
this.drinks[var2].status = soda.Drink.DrinkStatus.DROPPED;
++var1;
}
}
return var1;
}

要求饮料必须为STUCK状态,并且stuck的值为0,我们才能返回一个一个非0的值。

这个stuck在饮料初始化的时候,被初始化为3。我们来看看哪里可以减小这个值。

1
2
3
4
5
6
7
public void tap() {
for(int var1 = 0; var1 < 12; ++var1) {
if (this.drinks[var1].status == soda.Drink.DrinkStatus.STUCK && this.drinks[var1].stuck > 0) {
--this.drinks[var1].stuck;
}
}
}

当饮料为STUCK时,并且stuck不为0则可以用tap来减小stuck。

那么我们现在就剩下最后一个bystanders没有解决了。看看哪里有引用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
} else if (var1[0].equalsIgnoreCase("wait")) {
boolean var2 = false;

try {
var6 = Integer.parseInt(var1[1]);
} catch (Exception var5) {
System.out.println(">> Not sure what you mean");
return;
}

pause(var6);
if (var6 >= 10) {
bystanders = false;
System.out.println(">> ...Looks like nobody's around...");
} else {
bystanders = true;
System.out.println(">> People are walking down the street.");
}

var6是我们输入wait后面跟的时间长度。所以我们只要wait 10秒以上就可以将bystander置为false了。

所以总体的上的流程就是:

买最贵的饮料,拍三次饮料机,够到饮料,拿取饮料即可。

这题还有个问题就是我们只有5块钱。但是有的时候能买的最贵的饮料要大于五块钱。所以我们就多开几次题目,直到遇到最贵的饮料是我们可以买的即可。

1
2
3
4
5
6
command> grab
>> Alright!! Let's see what I got!
>> WOAH!! There's a flag in here!!
buckeye{w3_c411_7h3_s7uff_"p0p"_h3r3}

# buckeye{w3_c411_7h3_s7uff_"p0p"_h3r3}

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