Skip to content

FaustCTF 2024 Scoring Formula

Scoring formula for FaustCTF 2024 organized by FAUST.

Summary

The total score of each team is calculated from offense, defense and sla components of every team for each of their services and rounds played.

The checker returns one of three results for each service: up, recovering and down. A service is considered recovering if SLA checks both suceeded and failed for one round (each) in the recovery period.

The following python pseudo-code captures the team score calculation:

def score(rnd, team):
    attack = 0
    for r in range(rnd+1):
        for flag in flags_captured_by(r, team):
            if flag_owner(f) != "NOP":
                attack += 1 + 1 / len(total_captures_of(flag))

    defense = 0
    for r in range(rnd+1):
        for flag in flags_deployed_in(r, team):
            defense -= len(total_captures_of(flag)) ** 0.75

    teams_up = [t for t in teams if checker_status(rnd, t) == 'up']
    teams_rec = [t for t in teams if checker_status(rnd, t) == 'recovering']
    sla = (len(teams_up) + 0.5 * len(teams_rec)) * sqrt(len(teams))

    return attack + defense + sla

def scores():
    return {rnd: {team: score(rnd, team) for team in teams} for rnd in rounds}

Review

  • Scoring is easy to understand and reason about
  • Since total_captures_of(flag) is independent of round time, it may change after a previous attack score has been calculated, meaning efficient recalculation of team score each round is somewhat non-trivial. Additionally, ccore recalculation may cause total attack points to decrease from one round to the next, which tends to confuse new players.
  • In the worst-case, when all teams capture flags of a service, the defense points \((N_\texttt{Teams} - 1)^{0.75}\) lost outweigh the SLA points \((N_\texttt{Teams})^{0.5}\) gained for that service, thus violating Tenet 4