hack.lu-wp

Played with Project Sekai for fun and solved the last 2 rev challs. Here’s a quick writeup for them.

Ghost

A lot of information can be found in the binary.

1
2
3
4
5
Usage: ./ghost_no_flag <total_numer_of_games> <minimax_search_depth>
Typical examples values would be:
total_numer_of_games: 1000
minimax_search_depth: 32
Exiting due to incorrect command-line arguments.

Apparently it’s a game using minmax search algorithm. After a quick analysis, I found it’s a tic-tac-toe game against a AI using minmax search. So it’s easy to solve.

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
from pwn import *
p = remote('flu.xxx', 10140)
# p = process(["./ghost_no_flag", "50", "32"])
# context.log_level = "debug"
p.recvuntil(" | ")
rand_number = int(p.recvline().strip())
print(rand_number)

def gen_noises_lut_array(curr_step, rand_number, curr_idx_of_game):
noises_lut_array = [i for i in range(0, 13)]
curr_rand = curr_step ^ rand_number ^ curr_idx_of_game
for i in range(12):
noises_lut_array[12-i], noises_lut_array[curr_rand %
(13-i)] = noises_lut_array[curr_rand % (13-i)], noises_lut_array[12-i]

return noises_lut_array

res_table = ['*OoooOOOoooo*', '*Booooo-hoooo*', '*Eeeeek*', '*Hoooowl*', '*Sliiither*', '*Waaail*',
'*Woooosh*', '*Eeeerie*', '*Creeeeeeak*', '*Haauuunt*', '*Woooo-woooo*', '*Gaaaasp*', '*Shiiivver*']
print(len(res_table))

class Board(object):
def __init__(self):
self._board = ['-' for _ in range(9)]
self._history = []

def _move(self, action, take):
if self._board[action] == '-':
self._board[action] = take

self._history.append((action, take))

def _unmove(self, action):
self._board[action] = '-'

self._history.pop()

def get_board_snapshot(self):
return self._board[:]

def get_legal_actions(self):
actions = []
for i in range(9):
if self._board[i] == '-':
actions.append(i)
return actions

def is_legal_action(self, action):
return self._board[action] == '-'

def teminate(self):
board = self._board
lines = [board[0:3], board[3:6], board[6:9], board[0::3],
board[1::3], board[2::3], board[0::4], board[2:7:2]]

if ['X']*3 in lines or ['O']*3 in lines or '-' not in board:
return True
else:
return False

def get_winner(self):
board = self._board
lines = [board[0:3], board[3:6], board[6:9], board[0::3],
board[1::3], board[2::3], board[0::4], board[2:7:2]]

if ['X']*3 in lines:
return 0
elif ['O']*3 in lines:
return 1
else:
return 2

def print_b(self):
board = self._board
for i in range(len(board)):
print(board[i], end='')
if (i+1) % 3 == 0:
print()

def print_history(self):
print(self._history)

class Player(object):
def __init__(self, take='X'):
self.take = take

def think(self, board):
pass

def move(self, board, action):
board._move(action, self.take)

class HumanPlayer(Player):
def __init__(self, take):
super().__init__(take)

def think(self, board):
pass

class AIPlayer(Player):
def __init__(self, take):
super().__init__(take)

def think(self, board):
take = ['X', 'O'][self.take == 'X']
player = AIPlayer(take)
_, action = self.minimax(board, player)
return action

def minimax(self, board, player, depth=0):
if self.take == "O":
bestVal = -10
else:
bestVal = 10

if board.teminate():
if board.get_winner() == 0:
return -10 + depth, None
elif board.get_winner() == 1:
return 10 - depth, None
elif board.get_winner() == 2:
return 0, None

for action in board.get_legal_actions():
board._move(action, self.take)
val, _ = player.minimax(board, self, depth+1)
board._unmove(action)

if self.take == "O":
if val > bestVal:
bestVal, bestAction = val, action
else:
if val < bestVal:
bestVal, bestAction = val, action

return bestVal, bestAction

class Game(object):
def __init__(self):
self.board = Board()
self.current_player = None

def mk_player(self, p, take='X'):
if p == 0:
return HumanPlayer(take)
else:
return AIPlayer(take)

def switch_player(self, player1, player2):
if self.current_player is None:
return player1
else:
return [player1, player2][self.current_player == player1]

def print_winner(self, winner):
print(['Winner is player1', 'Winner is player2', 'Draw'][winner])

def run(self):
global rand_number, curr_idx_of_game
player1, player2 = self.mk_player(
1, 'X'), self.mk_player(0, 'O')

for i in range(50):
print('\nGame start!\n')
self.board.print_b()
curr_step = 0
while True:
self.current_player = self.switch_player(player1, player2)
if self.current_player == player2:
lut = gen_noises_lut_array(
curr_step, rand_number, i)
# print(curr_step, rand_number, curr_idx_of_game)
p.recvuntil(" / ̄ ̄  ̄ ̄ ̄ ̄ ̄\n | ")
option = p.recvline().strip().decode()
# print(option)
point = lut.index(res_table.index(option))
action = point
print("remote", action)
else:
if curr_step == 0:
action = 5
else:
action = self.current_player.think(self.board)
print("local", action)
p.sendlineafter("Spell:", str(action+1))

self.current_player.move(self.board, action)
curr_step += 1

self.board.print_b()
if self.board.teminate():
winner = self.board.get_winner()
break

# self.print_winner(winner)
self.board = Board()
print('Game over!')
p.recvuntil("SUCCESS!")
print(p.recv())

# self.board.print_history()

Game().run()

Password Game

A quick analysis, the css “animation” is triggered many times until a condition is reached. Each cycle represents an instruction and the whole thing works like a VM. It’s complicated to totally understand what’s going on in the “animation” part, but I can change the source to log the “regs” to “guess” what’s going on.

Here’s a conclusion of the rules.

  • Rule 1: The password is 36 characters long.
  • Rule 2: The password needs to contain a digit.
  • Rule 3: The password needs to contain an uppercase letter.
  • Rule 4: The password needs to contain a punctuation.
  • Rule 5: The numbers in the password add up to 9
  • Rule 6: The password needs to end with }
  • Rule 7: The password starts with flag{
  • Rule 8: All characters are printable.

Rule 9-11 are much more complicated. Rule 9 defines a certain character must followed by another certain character, It only uses xor so it’s easy to solve. And Rule 10 defines something like a checksum.I solved it using z3 with the following script.

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

s=Solver()
M =[...]
x=BitVec("x",16)
y=BitVec("y",16)
cond=x==M[401]
for i in range(402,len(M)):
cond=Or(cond,x==M[i])
s.add(cond)

cond=y==M[401]
for i in range(402,len(M)):
cond=Or(cond,y==M[i])
s.add(cond)

iv=x^0xff00
x1=(iv>>8)&0xff
y1=(iv<<8)&0xff00
s.add(y^y1==M[384])
s.check()
m=s.model()

y = m[y].as_long()
x = m[x].as_long()
idx=((x^0xff00)>>8)&0xff
print(M[401:].index(x))
print(chr(255-(M[401:].index(x)))) # x
print(chr((M[401:].index(y))^idx)) # y

Now I have a flag like flag{th1s_is_truly_h0r??????mBxdW8K} .The last rule is to make a jump at a right instruction. But we have already had enough information to “guess” the flag. The pairs in Rule 9 only have one left - {"e":"_"} . So looks like they are the last 2 characters missing. Apparently (or not), the missing word is horrible and there left only lower and upper ascii characters. So I just have to try a few times to find the right flag flag{th1s_is_truly_h0rrIbLe_mBxdW8K} .

CVE-2023-4427分析与复现 balsn-ctf-2023

Comments

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×