Welcome, challenger, to an intriguing analysis of a seemingly simple Rock-Paper-Scissors game. Our objective was to uncover a hidden flag, promised to anyone who could beat the computer five times in a row. What we found was a clever vulnerability stemming from string manipulation in C.
Understanding the Game’s Core
The game is implemented in C, featuring standard libraries for input/output, string handling, and time. Key arrays define the game’s logic:
– char* hands[3] = {"rock", "paper", "scissors"};
– char* loses[3] = {"paper", "scissors", "rock"};
The loses
array is crucial: loses[0]
is “paper” (what beats rock), loses[1]
is “scissors” (what beats paper), and loses[2]
is “rock” (what beats scissors). The game’s main loop allows players to either play a round or exit, tracking consecutive wins to award the flag after five victories.
The play()
Function: Where the Magic (and Flaw) Happens
The heart of each round lies within the play()
function. Here’s a breakdown:
1. Player Input: The game prompts the user for their selection (“rock”, “paper”, or “scissors”) and reads it into player_turn
.
2. Computer’s Move: A random number generator (srand(time(0))
and rand() % 3
) determines the computer’s move, stored in computer_turn
.
3. Determining the Winner: The crucial line that decides victory is:
if (strstr(player_turn, loses[computer_turn])) { puts("You win! Play again?"); return true; }
The strstr()
Vulnerability
The strstr()
function in C searches for the first occurrence of a substring within another string. In this game, strstr(player_turn, loses[computer_turn])
checks if the player_turn
string contains the string that would beat the computer’s choice.
Here lies the vulnerability: if the computer plays “rock” (computer_turn = 0
), then loses[computer_turn]
is “paper”. The strstr()
function will then check if player_turn
contains “paper”. It doesn’t verify if player_turn
is exactly “paper” or “rock” or “scissors”. It simply looks for the substring.
Crafting the Winning Exploit
Given this behavior, we can construct an input that always results in a win, regardless of the computer’s random choice. Consider the string “rockpaperscissors”.
– If the computer plays “rock” (loses[0]
is “paper”), “rockpaperscissors” contains “paper”. Player wins.
– If the computer plays “paper” (loses[1]
is “scissors”), “rockpaperscissors” contains “scissors”. Player wins.
– If the computer plays “scissors” (loses[2]
is “rock”), “rockpaperscissors” contains “rock”. Player wins.
By inputting “rockpaperscissors”, we essentially provide an input that satisfies the win condition for any possible computer move!
Achieving Victory and Retrieving the Flag
With this exploit in hand, the path to the flag was clear. We simply needed to repeatedly enter “rockpaperscissors” as our move. After successfully winning five consecutive rounds, the game recognized our persistent victory and revealed the elusive flag.
This exercise serves as a great reminder of the importance of precise string comparisons in programming, especially in contexts where game logic or security might be at stake. A simple strcmp()
or exact match could have prevented this loophole, but strstr()
opened the door for an unexpected win.