English [HeroCTF 2024] [Prog 864 – Antwarz PVE – Easy & Medium] Write Up

Description

These challenges are categorized as easy and medium difficulty. In these challenges, you will need to code your own bot to control an ant colony. In the first two challenges (Easy and Medium), you compete against pre-configured bots. If you succeed, you enter the PVP arena, where you will compete against other players.

Scoring ends 10 minutes before the CTF concludes, and the team with the most points will be awarded a flag worth 10 points. While this flag might not seem like much, it could be the deciding factor between second or first place! You can find more information about the game in the documentation section of the dedicated platform.

It’s worth noting that the goal of these challenges is not to exploit the platform, but to create the best possible bot. Any detected security vulnerabilities should be reported, as exploiting them to solve a challenge would invalidate the submission.

You can access the source code of the game engine through the first challenge of the series if you wish to set it up locally.

nc antwarz.heroctf.fr 8080

Format: Hero{flag} Author: Log_s

Resolution

The objective of these challenges was to develop a Python bot to control an ant colony in the game “Antwarz.” The game required careful planning of ant movements and resource gathering to outplay opponent bots. We wrote a bot that focused on optimizing movements, predicting opponent moves, and efficient resource gathering to beat the opponent bots. This solution was effective in both the Easy and Medium versions of the challenge.

Here is our bot implementation:

import base64
import socket
import os
import time
import logging

logging.basicConfig(level=logging.INFO, format=’%(asctime)s – %(levelname)s – %(message)s’)

def encode_make_move():
make_move_code = ”’
def make_move(game_state):
moves = []
base_x = 0
grid_size = game_state[“grid_size”]
occupied_positions = set()

for ant in game_state[“your_ants”] + game_state[“opponent_ants”]:
occupied_positions.add(ant[“pos”])

def predict_opponent_move(ant):
current_pos = ant[“pos”]
last_pos = ant.get(“last_pos”, current_pos)
predicted_pos = (current_pos[0] + (current_pos[0] – last_pos[0]),
current_pos[1] + (current_pos[1] – last_pos[1]))
return predicted_pos if predicted_pos != current_pos else last_pos

targeted_sugars = set()
for ant in game_state[“opponent_ants”]:
if not ant[“carrying”]:
predicted_target = predict_opponent_move(ant)
targeted_sugars.add(predicted_target)

for ant in game_state[“your_ants”]:
ant_x, ant_y = ant[“pos”]
carrying = ant[“carrying”]
new_pos = ant[“pos”]
move = “stay”

if carrying:
if ant_x == base_x:
if (ant_x, ant_y) in occupied_positions and (ant_x, ant_y) != ant[“pos”]:
if ant_y > 0 and (ant_x, ant_y – 1) not in occupied_positions:
new_pos = (ant_x, ant_y – 1)
move = “up”
elif ant_y < grid_size – 1 and (ant_x, ant_y + 1) not in occupied_positions:
new_pos = (ant_x, ant_y + 1)
move = “down”
else:
new_pos = ant[“pos”]
move = “stay”
carrying = False
else:
if (ant_x – 1, ant_y) not in occupied_positions:
new_pos = (ant_x – 1, ant_y)
move = “left”
else:
if ant_y > 0 and (ant_x, ant_y – 1) not in occupied_positions:
new_pos = (ant_x, ant_y – 1)
move = “up”
elif ant_y < grid_size – 1 and (ant_x, ant_y + 1) not in occupied_positions:
new_pos = (ant_x, ant_y + 1)
move = “down”
else:
sugar_here = next((cube for cube in game_state[“discovered_cubes”] if cube[“pos”] == ant[“pos”] and cube[“sugar”] > 0), None)
if sugar_here:
new_pos = ant[“pos”]
move = “stay”
carrying = True
else:
closest_sugar = None
min_distance = float(‘inf’)
for cube in game_state[“discovered_cubes”]:
sugar_x, sugar_y = cube[“pos”]
if cube[“sugar”] > 0:
distance = abs(sugar_x – ant_x) + abs(sugar_y – ant_y)
if distance < min_distance and (sugar_x, sugar_y) not in targeted_sugars:
min_distance = distance
closest_sugar = cube

if closest_sugar:
sugar_x, sugar_y = closest_sugar[“pos”]
if sugar_x != ant_x:
if sugar_x > ant_x and (ant_x + 1, ant_y) not in occupied_positions:
new_pos = (ant_x + 1, ant_y)
move = “right”
elif sugar_x < ant_x and (ant_x – 1, ant_y) not in occupied_positions:
new_pos = (ant_x – 1, ant_y)
move = “left”
elif sugar_y != ant_y:
if sugar_y > ant_y and (ant_x, ant_y + 1) not in occupied_positions:
new_pos = (ant_x, ant_y + 1)
move = “down”
elif sugar_y < ant_y and (ant_x, ant_y – 1) not in occupied_positions:
new_pos = (ant_x, ant_y – 1)
move = “up”

if new_pos not in occupied_positions:
occupied_positions.add(new_pos)
moves.append({“pos”: ant[“pos”], “carrying”: carrying, “move”: move})
else:
moves.append({“pos”: ant[“pos”], “carrying”: carrying, “move”: “stay”})

return {
“your_ants”: moves,
“player_data”: game_state[“player_data”]
}
”’
encoded_code = base64.b64encode(make_move_code.encode(‘utf-8’)).decode(‘utf-8’)
return encoded_code + “nEOF”

def submit_bot(token, submit_as_test=False):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect((‘antwarz.heroctf.fr’, 8080))
logging.info(“Connected to antwarz.heroctf.fr:8080″)

while True:
initial_response = s.recv(4096).decode(‘utf-8’)
if not initial_response:
break
logging.info(f”Server message:n{initial_response}”)

if “Enter your CTFd token:” in initial_response:
s.sendall((token + “n”).encode(‘utf-8’))
logging.info(“Token sent.”)

if “Enter your base64-encoded Python code:” in initial_response:
encoded_code = encode_make_move()
s.sendall((encoded_code + “n”).encode(‘utf-8’))
logging.info(“Encoded code sent.”)
time.sleep(1)

if “[?] Would you like to submit this as a test ?” in initial_response:
response = ‘yn’ if submit_as_test else ‘nn’
s.sendall(response.encode(‘utf-8’))
logging.info(“Submission confirmation sent.”)
time.sleep(1)

while True:
response = s.recv(4096).decode(‘utf-8’)
if not response:
break
logging.info(f”Server response:n{response}”)
time.sleep(1)

def main_menu():
token = os.getenv(“CTFD_TOKEN”, “your_token_here”)
while True:
print(“n=== Antwarz – Observation Menu ===”)
print(“1. Launch a new submission (standard mode)”)
print(“2. Launch a test submission”)
print(“3. Quit”)
choice = input(“Choose an option: “)

if choice == ‘1’:
submit_bot(token, submit_as_test=False)
elif choice == ‘2’:
submit_bot(token, submit_as_test=True)
elif choice == ‘3’:
print(“Goodbye!”)
break
else:
print(“Invalid option, please try again.”)

if name == “main“:
main_menu()

With this implementation, we successfully solved both the Easy and Medium challenges using the same bot, demonstrating the bot’s effectiveness in adapting to both levels of difficulty.

Flag was Hero{00ace8b68a3879022ed7b05349276445} (Easy) and Hero{4a2f05023630b7501944632bad98c103} (Medium).

 

Leave a Reply

Your email address will not be published. Required fields are marked *