Fish Game Writeup
Challenge Overview
Category: Reversing
Flag: kaspersky{g1v3_m4n_4_f1sh1ng_r0d_h3_w0u1d_b3_f4d}
Challenge Description
I want to play a game together. I also like fish.
This challenge is a Godot-based fish game where players control a fish and tries to get a high score.
Step 1: Game Analysis and Reverse Engineering
Initial Setup
First, we need to understand the game and extract its source code:
- Download and run the game to understand basic mechanics
- Install GDRETools from: https://github.com/GDRETools/gdsdecomp
- Extract game files using GDRETools to reveal the complete source code
Analysis
After decompilation, the important files are:
score.gd / score.tscn # Score submission system
registry.gd # Server URLs and configuration
player.gd / player.tscn # Player controller and token handling
enemy.gd / enemy.tscn # Enemy AI and score calculationStep 2: Understanding Game Mechanics
Analyzing enemy.gd:
func _on_body_entered(body: Node2D) -> void :
if body is Player:
var x = 0
for xx in body.get_children():
if xx is Enemy:
x += 1
Registry.asd = x * 123 # Score stored as (fish_count * 123)
get_tree().change_scene_to_file("res://lose.tscn")The score is calculated as number_of_fish_caught × 123, this is noted for future use.
Step 3: Network Communication Analysis
Identifying Server Endpoints
From registry.gd:
var base_url = "https://fishgame.task.sasc.tf"From player.gd and score.gd, we find these endpoints:
GET /get_token- Get authentication tokenPOST /set_score- Submit score to leaderboardGET /get_prize/{token}- Get reward/flag
Token System
From player.gd:
func _ready() -> void :
$HTTPRequest.request_completed.connect(_on_request_completed)
$HTTPRequest.request(get_token) # Request fresh token on startup
Registry.player = self
func _on_request_completed(result, response_code, headers, body):
if response_code != 200:
return
var json = JSON.parse_string(body.get_string_from_utf8())
Registry.token = json["token"] # Store token for later useThe game requests a fresh token each time it starts, and stores it for later use when interacting with the various API endpoints.
Step 4: Understanfing how the scoring system actually works
Score Verification System
score.gd contains the score verification function.
func calculate(st: String) -> String:
var x = 0
for i in range(len(st)):
x += st.unicode_at(i) # Add ASCII value of each character
x *= 2 # Multiply by 2
x %= 2 ** 64 # Modulo 2^64
return "%x" % x # Return as hexadecimalThis is understood as the verification function as it is later called during score submission to create the JSON:
From score.gd:
func _on_send_pressed() -> void :
# ... validation checks ...
var data_json = JSON.stringify({
"token": Registry.token,
"username": $Control/VBoxContainer/TextEdit.text,
"score": Registry.asd / 123, # Displayed score
"verify": calculate(Registry.token + "_" + str(Registry.asd)) # Verification hash
})The calculate function is called here as a part of verify in the form of a hash function. This is used to ensure the score is not easily tampered with just directly changing the score value, but the hash function is also visible, allowing us to compute the hash value of any score.
Step 5: Testing the API Endpoints
Manual API Testing
Let’s test each endpoint manually:
# 1. Get a token
curl -s "https://fishgame.task.sasc.tf/get_token"
# Response: {"token":"fe714afd515bea2bff8dd30cafc2d441"}
# Note: The token is different each time, this is just a placeholder for demonstration
# 2. Check current leaderboard
curl -s "https://fishgame.task.sasc.tf/get_scores"
# Shows current high scores
# 3. Test prize endpoint (without valid score)
curl -s "https://fishgame.task.sasc.tf/get_prize/fe714afd515bea2bff8dd30cafc2d441"
# Returns a message stating a score above 5000 is required to get the prize.From testing, we discover that we need a very high score to get the flag, which would be very hard to get from actually playing the game (from experience the most i could get was 10, before i tried to figure another way out)
Step 6: Implementing the Exploit
Recreate the Hash Function
First, we implement the verification hash in Python:
def calculate_hash(input_string):
"""
Python implementation of the GDScript calculate() function
"""
x = 0
for char in input_string:
x += ord(char) # ASCII value
x *= 2 # Multiply by 2
x %= (2 ** 64) # Modulo 2^64
return format(x, 'x') # Return as hexCalculate Valid Verification
# Example calculation
token = "fe714afd515bea2bff8dd30cafc2d441"
score = 100000 # High score we want
asd_value = score * 123 # Convert to internal representation
# Create verification string exactly as the game does
verify_string = f"{token}_{asd_value}"
verify_hash = calculate_hash(verify_string)
print(f"Score: {score}")
print(f"ASD Value: {asd_value}")
print(f"Verify String: {verify_string}")
print(f"Verify Hash: {verify_hash}")Submit the Forged Score
payload = {
"token": token,
"username": "user",
"score": score, # The display score
"verify": verify_hash # Our calculated verification
}
response = requests.post(
"https://fishgame.task.sasc.tf/set_score",
headers={"Content-Type": "application/json"},
json=payload
)
print(f"Status: {response.status_code}")
print(f"Response: {response.text}")Step 7: Getting the Flag
Request the Prize
After successfully submitting a high score:
prize_response = requests.get(f"https://fishgame.task.sasc.tf/get_prize/{token}")
if prize_response.status_code == 200:
data = prize_response.json()
prize = data.get("prize", "No prize found")
print(f"Prize: {prize}")
if "kaspersky{" in prize:
print(f"FLAG FOUND: {prize}")The server returns: kaspersky{g1v3_m4n_4_f1sh1ng_r0d_h3_w0u1d_b3_f4d}
Step 8: Complete Exploit
The python script is located at: soln.py
Initially i did it without a script by:
# 1. Get token
curl -s "https://fishgame.task.sasc.tf/get_token"
# 2. Calculate hash in Python
python3 -c "
token = 'YOUR_TOKEN_HERE'
score = 100000
asd = score * 123
verify_str = f'{token}_{asd}'
x = 0
for c in verify_str:
x += ord(c)
x *= 2
x %= (2**64)
print(f'Hash: {format(x, \"x\")}')
"
# 3. Submit score
curl -X POST "https://fishgame.task.sasc.tf/set_score" \
-H "Content-Type: application/json" \
-d '{
"token": "YOUR_TOKEN",
"username": "user",
"score": 100000,
"verify": "YOUR_HASH"
}'
# 4. Get flag
curl -s "https://fishgame.task.sasc.tf/get_prize/YOUR_TOKEN"