π¬π§ HTBA | Maze Of Power [Insane]
Table of Contents
MazeOfPower
This challenge was a insane reverse engineering challenge from the Hack The Box Apocalypse 2024 CTF
Initial Recon
We are facing a ELF x86_64 binary with debug symbols.
main: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=246eec7fd33acbeba185edfe290af7cb632dec84, for GNU/Linux 3.2.0, with debug_info, not stripped
This binary was obviously written in golang, this might add some spice to the challenge.
The binary is asking for the pow solution, I donβt have time to solve a PoW each time, so I just patched the condition.
We also have the pow code source AND the maze code source in addition of the debug symbols already in the binary, what better can we hope for ?
cVar1 = github.com/redpwn/pow.(*Challenge).Check();
if ((lVar2 != 0) || (cVar1 == '\0')) {
return;
}
runtime.stringtoslicebyte();
hash/crc32.ChecksumIEEE();
math/rand.Seed();
The PoW input is then hashed using the CRC32 algotihm and a PRNG is initalised from the checksum.
The challenge will then create a random maze (created from the PRNG), the flag will be given when we have finished it (in 20 seconds remotely) we quickly understand the problem, the walls are invisible, We will have to find a way to display and then get an idea of how to win.
Patching the challenge
We can quicly patch the binary by patching the addreses of the wall string by a pointer to a another string in the binary (ex: $$)
We now have the walls, so βtechnicallyβ the challenge is solvable, I can script a way to follow the left wall and pray
(After a too long time searching for a presented backdoor). I followed my solve idea. When we finally arrive at the end of the maze, the function github.com/itchyny/maze.(*Maze).Solve
is called.
For each move ( k/j/h/l
) [RSP + 0x21a]
is checked, if zero the maze is not solved, else the maze is solved and the binary is now printing the right path.
So, I just patched the JZ
instruction into a JNZ
instruction, now for each move, I will have the solution of the maze.
I didnt figured any backdoor in this challenge, but now, remember the seed from our PoW solution above ? By using the same input locally and remotely, I will have the same maze, so the same solution.
The parsing of the maze into a concatenation of moves is a bit handy but nothing out of our mind.
import pwn
import subprocess
def getsolution(string):
p1 = pwn.process(["./main_direct_solution"]) # using the solve pow into the patched main
p1.recv()
p1.sendline(string)
p1.recvline()
p1.recvline()
p1.recvuntil(b"EE")
p1.sendline(b"l") # send a first input to draw the solution
for _ in range(8):
p1.recvline()
maze = [] # now parse the output
for _ in range(0x19*2):
maze.append(p1.recvline())
p1.kill()
p1.close()
start = maze[0].index(b"::")
pos = [0,start]
visited = []
visited.append(pos)
sequence = []
while maze[pos[0]][pos[1]+1:pos[1]+1+2] != b":E":
if maze[pos[0]][pos[1]+1:pos[1]+1+2] == b"::" and [pos[0], pos[1]+4] not in visited:
# print("right !!")
visited.append([pos[0], pos[1]])
pos[1] = pos[1]+4
sequence.append("l")
if maze[pos[0]][pos[1]-1:pos[1]-1+2] == b"::" and [pos[0], pos[1]-4] not in visited:
# print("left")
visited.append([pos[0], pos[1]])
pos[1] = pos[1]-4
sequence.append("h")
if maze[pos[0]-1][pos[1]:pos[1]+2] == b"::" and [pos[0]-2, pos[1]] not in visited:
# print("up")
visited.append([pos[0], pos[1]])
pos[0] = pos[0]-2
sequence.append("k")
if maze[pos[0]+1][pos[1]:pos[1]+2] == b"::" and [pos[0]+2, pos[1]] not in visited:
# print("down !!")
visited.append([pos[0], pos[1]])
pos[0] = pos[0]+2
sequence.append("j")
return "".join(sequence)
p2 = pwn.process(["./main"])
# p2 = pwn.connect("94.237.63.128", 50542)
commande = p2.recvline()
result = subprocess.check_output(commande[15:-1], shell=True).replace(b"\n",b"") # solving the POW...
p2.recv()
solution = getsolution(result)
print(solution)
p2.sendline(result)
for c in solution:
p2.recvuntil(b"EE")
p2.sendline(c.encode())
p2.interactive() # get the flag in puissance
Flag: HTB{by_th3_p0w3r_0f_th3_m4z3!!1}
Conclusion
It was surprisingly not a very complicated challenge, I was a little surprised not to find a backdoor like crossing the wall to go around the maze, this solution but also the untended is mainly based on a weakness in the prng seed. It remains a nice challenge which requires scripting a nice algorithm