CTF Writeups
WebsiteDiscordSocials..
  • CryptoCat's CTF writeups
  • 2025
    • Tsuku
      • Web
        • len_len
        • Flash
        • YAMLwaf
    • CTF@CIT
      • Web
        • Breaking Authentication
        • Commit & Order: Version Control Unit
        • How I Parsed your JSON
        • Mr. Chatbot
        • Keeping Up with the Credentials
  • 2024
    • CryptoCat
      • Summar-AI-ze
    • Intigriti
      • Warmup
        • BabyFlow
        • In Plain Sight
        • IrrORversible
        • Layers
        • Rigged Slot Machine 1
      • Game
        • Bug Squash 1
        • Bug Squash 2
      • Rev
        • Secure Bank
      • Web
        • Biocorp
        • Cat Club
        • Pizza Paradise
        • SafeNotes 2.0
      • Misc
        • Quick Recovery
        • Triage Bot 2
      • Pwn
        • Floormat Sale
        • Retro2Win
        • Rigged Slot Machine 2
        • UAP
      • Crypto
        • Schrodinger's Pad
      • Mobile
        • Cold Storage
      • OSINT
        • No Comment
        • Trackdown
        • Trackdown 2
      • Forensics
        • CTF Mind Tricks
        • Hoarded Flag
        • Password Management
    • CSAW
      • Web
        • Playing on the Backcourts
        • Log Me In
        • Lost Pyramid
        • BucketWars
    • CyberSpace
      • Web
        • Feature Unlocked
    • UIU
      • Web
        • Fare Evasion
        • Log Action
    • Wani
      • Web
        • Bad Worker
        • PoW
        • One Day One Letter
    • Akasec
      • Web
    • HTB Cyber Apocalypse
      • Web
        • Flag Command
        • TimeKORP
        • KORP Terminal
        • Labyrinth Linguist
        • Locktalk
        • SerialFlow
        • Testimonial
  • 2023
    • Intigriti
      • Gamepwn
        • Dark Secrets
      • Misc
        • Triage Bot
      • OSINT
        • Photographs
      • Pwn
        • Floormat Store
      • Web
        • Bug Report Repo
        • My Music
    • Imaginary
      • Web
        • Blank
        • IDORiot
        • Inspection
        • Login
        • Perfect Picture
        • Roks
    • Google
      • Pwn
        • Write-Flag-Where
    • Cyber Apocalypse
      • AI
        • Last Hope
        • Mysterious Learning
      • Crypto
        • Perfect Synchronization
      • Pwn
        • Getting Started
        • Labyrinth
        • Pandora's Box
        • Void
      • Rev
        • Cave System
        • Hunting License
        • Needle in a Haystack
        • Shattered Tablet
        • She Sells Sea Shells
    • Sekai
      • Rev
        • Azusawa's Gacha World
    • Amateurs
      • Web
        • Sanity
        • Waiting an Eternity
    • NahamCon
      • Web
        • Hidden Figures
        • Marmalade 5
        • Obligatory
        • Star Wars
        • Stickers
    • Angstrom
      • Pwn
        • Leek
  • 2022
    • Imaginary (iCTF)
      • Crypto
        • ASE
      • Pwn
        • Links 1
        • Links 2
        • Links 3
        • Open Doors
    • SEETF
      • Pwn
        • 4mats
        • Easy Overflow
      • Rev
        • BabyReeee
      • Web
        • Super-Secure-Requests-Forwarder
    • HTB Cyber Apocalypse
      • Pwn
        • Hellbound
    • Angstrom
      • Pwn
        • Really Obnoxious Problem
        • Wah
        • Whats My Name
        • Where Am I
      • Web
        • Crumbs
        • Xtra Salty Sardines
    • NahamCon
      • Pwn
        • Baby Steps
      • Web
        • Flaskmetal Alchemist
        • Hacker Ts
        • Two for One
    • Pico
      • Forensics
        • Side Channel
      • Pwn
        • Buffer Overflow 1
        • Buffer Overflow 2
        • Buffer Overflow 3
        • Flag Leak
        • Function Overwrite
        • ROPfu
        • RPS
        • Stack Cache
        • Wine
        • X-Sixty-What
      • Rev
        • Wizardlike
      • Web
        • Noted
    • Space Heroes
      • Pwn
        • Vader
      • Web
        • Flag in Space
    • Intigriti
      • Pwn
        • Bird
        • Cake
        • Easy Register
        • Search Engine
    • Dice
      • Pwn
        • Interview Opportunity
  • 2021
    • Pico
      • Pwn
        • Unsubscriptions Are Free
    • Crusaders of Rust (COR)
      • Crypto
        • Fibinary
      • Pwn
        • Chainblock
    • HTB Cyber Santa
      • Crypto
        • Meet Me Halfway
        • Xmas Spirit
      • Pwn
        • Minimelfistic
        • Mr. Snowy
        • Naughty List
        • Sleigh
      • Rev
        • Infiltration
        • Intercept
    • K3rn3l
      • Crypto
        • Badseed
        • Twizzty Buzzinezz
    • HTB x Synack RedTeamFive
      • Misc
        • Context
        • Hotel
      • Pwn
        • Air Supplies
        • Injection Shot
        • Library
        • Recruitment
      • Rev
        • Knock Knock
        • Split
    • KillerQueen
      • Pwn
        • A Kind of Magic
        • Tweety Birb
        • Zoom2Win
    • HacktivityCon
      • Pwn
        • Retcheck
        • The Library
        • Yabo
      • Web
        • Availability
    • CSAW
      • Pwn
        • Alien Math
        • Password Checker
      • Rev
        • Checker
    • HackyHolidays
      • Crypto
        • Cute Invoice
        • Mineslazer
      • Forensics
        • Injection Traffic
        • Power Snacks
      • Pwn
        • Deleted Flag
        • Engine Control
      • Web
        • Skylark
    • HTB Cyber Apocalypse
      • Crypto
        • Phasestream
      • Misc
        • Alien Camp
        • Build Yourself In
      • Pwn
        • Controller
        • System Drop
      • Web
        • Blitzprop
        • E-Tree
        • Wild Goose Hunt
    • Angstrom
      • Pwn
        • Sanity Checks
        • Secure Login
        • Sticky Stacks
        • Tranquil
      • Rev
        • Free Flags
        • Jailbreak
      • Web
        • Jar
Powered by GitBook
On this page
  • Video walkthrough
  • Challenge Description
  • Solution
  • solve.py
  • server.py
  1. 2024
  2. Intigriti
  3. Game

Bug Squash 2

Writeup for Bug Squash (part 2) (Gamepwn) - 1337UP LIVE CTF (2024) 💜

PreviousBug Squash 1NextRev

Last updated 4 months ago

Video walkthrough

Challenge Description

The developers learned some important things about cheaters and now hope they've learnt their lesson. Rumour has it, if you score more than 100,000 points in this game (within the 2 min time limit), you'll get a flag. Watch out for that new anti-cheat system though!

Solution

The description indicates we need more than 100,000 points to win, but there's a 2 minute time limit on each game 🤔

My expected approach was to open Wireshark and see some network traffic when the game is running. Since the traffic is HTTPS, players have to do a little work to decrypt it.

  • Setup Windows proxy 127.0.0.1:8080

  • Setup burp cert to capture HTTPS traffic

    • Export proxy cert in PKCS format

    • Windows > Manage user certificates > Trusted Root Certification Authorities > Certificates > All Tasks > Import

    • Traffic will now show in burp

The /start_game endpoint will initialise a game.

Each time we score a point, a request is issued to the /update_score endpoint.

We can try to modify the traffic to change the points but from trial and error we will find some conditions

  • Anti-cheat resets users score if they send more then 3 request per second

  • Anti-cheat rejects any point values that aren't 1 (and resets score)

  • Anti-cheat checks that players score didn't jump to an unrealistic number (more than 4096 per request)

The game resets every 2 mins so by the anti-cheat rules, max attainable score is (120 * 3))

Since they can't change the value, I thought players might play around with the keys.

`{"5e258b4a-23cf-469c-9264-b73856d9fe20": "insert_id", "bugs_squashed": 1, "bugs_squashed": 1}`

So yes, intended solution is to send {"user_id": "insert_id", "bugs_squashed": 1, "bUgs_squashed": 1, "buGs_squashed": 1} etc, where you can send 4096 variations per request at a max speed of 3 requests per second. Here's a solve script to do that.

solve.py

import requests
import itertools
import time

BASE_URL = 'https://bugsquash.ctf.intigriti.io'

def generate_variations(s):
    """Generate all case variations of a string."""
    return [''.join(variant) for variant in itertools.product(*([letter.lower(), letter.upper()] for letter in s))]

def start_game(session):
    """Start a new game session and return the user_id."""
    response = session.post(f'{BASE_URL}/start_game')
    response_data = response.json()
    user_id = response_data['user_id']
    score = response_data['score']
    print(f"Game started! User ID: {user_id}, Initial Score: {score}")
    return user_id, score

def update_score(session, user_id, variations):
    """Send score updates to the server using all variations of 'bugs_squashed'."""
    json_data = {"user_id": user_id}
    json_data.update({variation: 1 for variation in variations})

    response = session.post(
        f'{BASE_URL}/update_score', json=json_data)
    response_data = response.json()

    if "error" in response_data:
        print(f"Error: {response_data['error']}")
    elif "message" in response_data:
        print(
            f"Message: {response_data['message']}, Current Score: {response_data['score']}")

    return response_data.get('score', 0)

def play_game(variations, target_score=100000):
    """Play the game until the target score is reached."""
    with requests.Session() as session:
        user_id, score = start_game(session)

        print(len(variations))

        while score < target_score:
            score = update_score(session, user_id, variations)
            time.sleep(0.333)  # 3 requests per second

        print(f"Target score reached! Final Score: {score}")

if __name__ == "__main__":
    variations = generate_variations("bugs_squashed")
    play_game(variations)

Run the solve script.

python solve.py
Game started! User ID: 700d9b33-1eef-42d0-bf37-afcc41a857cf, Initial Score: 0
8192
Message: Score updated by 4096 points., Current Score: 4096
Message: Score updated by 4096 points., Current Score: 8192
Message: Score updated by 4096 points., Current Score: 12288
Message: Score updated by 4096 points., Current Score: 16384
Message: Score updated by 4096 points., Current Score: 20480
Message: Score updated by 4096 points., Current Score: 24576
Message: Score updated by 4096 points., Current Score: 28672
Message: Score updated by 4096 points., Current Score: 32768
Message: Score updated by 4096 points., Current Score: 36864
Message: Score updated by 4096 points., Current Score: 40960
Message: Score updated by 4096 points., Current Score: 45056
Message: Score updated by 4096 points., Current Score: 49152
Message: Score updated by 4096 points., Current Score: 53248
Message: Score updated by 4096 points., Current Score: 57344
Message: Score updated by 4096 points., Current Score: 61440
Message: Score updated by 4096 points., Current Score: 65536
Message: Score updated by 4096 points., Current Score: 69632
Message: Score updated by 4096 points., Current Score: 73728
Message: Score updated by 4096 points., Current Score: 77824
Message: Score updated by 4096 points., Current Score: 81920
Message: Score updated by 4096 points., Current Score: 86016
Message: Score updated by 4096 points., Current Score: 90112
Message: Score updated by 4096 points., Current Score: 94208
Message: Score updated by 4096 points., Current Score: 98304
Message: INTIGRITI{64m3_h4ck1n6_4n71ch347_15_4l50_fun!}, Current Score: 102400
Target score reached! Final Score: 102400

Flag: INTIGRITI{64m3_h4ck1n6_4n71ch347_15_4l50_fun!}

This challenge didn't get a lot of solves and people found it guessy. Thinking back on it, it was! I wish I did something different 😞 Here's the server-side code for those interested.

server.py

from flask import Flask, request, jsonify
import uuid
import os
import logging
import time
from redis import Redis

flag = os.getenv('FLAG', 'INTIGRITI{fake_flag}')

app = Flask(__name__)

# Set up logging
logging.basicConfig(level=logging.INFO)

# Configure Redis connection using environment variables
redis_host = os.getenv('HOST', 'localhost')
redis_port = os.getenv('PORT', '6379')
redis_user = os.getenv('USERNAME', '')
redis_pass = os.getenv('PASSWORD', '')

# Construct Redis URL (e.g., redis://username:password@hostname:port/0)
redis_url = f"rediss://{redis_user}:{redis_pass}@{redis_host}:{redis_port}" if redis_user and redis_pass else f"redis://{redis_host}:{redis_port}"
# Connect to Redis
redis_client = Redis.from_url(redis_url)

# Session duration and rate limit settings
SESSION_DURATION = 120  # 2 minutes
RATE_LIMIT = 3  # requests
RATE_LIMIT_WINDOW = 1  # second

@app.route('/start_game', methods=['POST'])
def start_game():
    """Initialize a new game session for the user."""
    user_id = str(uuid.uuid4())
    session_key = f"user_session:{user_id}"
    session_data = {
        'score': 0,
        'created_at': time.time()
    }
    # Store session data in Redis with expiration
    redis_client.hset(session_key, mapping=session_data)
    redis_client.expire(session_key, SESSION_DURATION)
    logging.info(f"New game started with user_id: {user_id}")
    return jsonify({
        "user_id": user_id,
        "score": 0,
        "session_duration": SESSION_DURATION
    })

@app.route('/update_score', methods=['POST'])
def update_score():
    """Update the user's score."""
    data = request.get_json()
    if not data:
        return jsonify({"error": "Invalid request data.", "score": 0}), 400

    user_id = data.get('user_id')
    if not user_id:
        return jsonify({"error": "User ID not provided.", "score": 0}), 400

    session_key = f"user_session:{user_id}"
    # Check if session exists
    if not redis_client.exists(session_key):
        return jsonify({
            "error": "Session expired or invalid. Please restart the game.",
            "score": 0,
            "session_expired": True
        }), 401

    # Retrieve session data
    session_data = redis_client.hgetall(session_key)
    if not session_data:
        return jsonify({
            "error": "Session data missing. Please restart the game.",
            "score": 0,
            "session_expired": True
        }), 401

    # Check session expiration
    created_at = float(session_data.get(b'created_at', 0))
    session_age = time.time() - created_at
    if session_age > SESSION_DURATION:
        redis_client.delete(session_key)
        return jsonify({
            "error": "Session expired. Please restart the game.",
            "score": 0,
            "session_expired": True
        }), 401

    # Implement rate limiting per user_id
    rate_limit_key = f"rate_limit:{user_id}"
    current_count = redis_client.incr(rate_limit_key)
    if current_count == 1:
        # Set expiration on rate limit key
        redis_client.expire(rate_limit_key, RATE_LIMIT_WINDOW)
    elif current_count > RATE_LIMIT:
        # Reset score
        redis_client.hset(session_key, 'score', 0)
        return jsonify({
            "error": "Too many requests. Anti-cheat triggered! Your score has been reset.",
            "score": 0
        }), 429

    # Points validation logic
    score = int(session_data.get(b'score', 0))
    total_valid_points = 0
    for key, value in data.items():
        if key.lower() == "bugs_squashed":
            try:
                point_value = float(value)
            except ValueError:
                redis_client.hset(session_key, 'score', 0)
                return jsonify({
                    "error": "Invalid points value detected. Anti-cheat is investigating.",
                    "score": 0
                }), 400
            if point_value == 1:
                total_valid_points += 1
            else:
                redis_client.hset(session_key, 'score', 0)
                return jsonify({
                    "error": f"Invalid point value ({point_value}). Anti-cheat is suspicious.",
                    "score": 0
                }), 400

    if total_valid_points > 4096:
        redis_client.hset(session_key, 'score', 0)
        return jsonify({
            "error": f"Anomaly detected: {total_valid_points} points in 1 second. Score reset!",
            "score": 0
        }), 400
    else:
        # Update the user's score in Redis
        score += total_valid_points
        redis_client.hset(session_key, 'score', score)
        if score >= 100000:
            return jsonify({"message": flag, "score": score})
        else:
            return jsonify({
                "message": f"Score updated by {total_valid_points} points.",
                "score": score,
                "session_expired": False
            })

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8080, debug=False)

We'll struggle to decompile the game as we did in part 1 due to it being compiled with IL2CPP instead of Mono. You could still attach cheat engine and reverse the code as DavidP did in (he actually reconstructed the C# code from assembly!)

This would fail because the keys are duplicate. The thing about JSON is the , so I hoped players would try to send BUGS_SQUASHED as well asbugs_squashed and see they score points.

this video
keys are non case-sensitive