Here’s a program that plays rock, paper, scissors against you. I hear something good happens if you win 5 times in a row.
Connect to the program with netcat:
$ nc saturn.picoctf.net [port #]
The program’s source code with the flag redacted can be downloaded here.
We’re given a C source file to analyze. Taking a look at the play function, I noticed something interesting:
bool play () { char player_turn[100]; srand(time(0)); int r;
printf("Please make your selection (rock/paper/scissors):\n"); r = tgetinput(player_turn, 100); // Timeout on user input if(r == -3) { printf("Goodbye!\n"); exit(0); }
int computer_turn = rand() % 3; printf("You played: %s\n", player_turn); printf("The computer played: %s\n", hands[computer_turn]);
if (strstr(player_turn, loses[computer_turn])) { puts("You win! Play again?"); return true; } else { puts("Seems like you didn't win this time. Play again?"); return false; }}But, here’s what the strstr function does according to documentation:
strstrconst char * strstr ( const char * str1, const char * str2 ); char * strstr ( char * str1, const char * str2 );Locate substringReturns a pointer to the first occurrence of str2 in str1, or a null pointer if str2 is not part of str1.
The matching process does not include the terminating null-characters, but it stops there.So strstr isn’t checking if the strings are equal. Rather, it’s checking if str2 is in str1! In other words, it’s checking if loses[computer_turn] is contained within player_turn. If it is, then we win that round.
We are also given the following:
char* loses[3] = {"paper", "scissors", "rock"};Hence, the value of loses[computer_turn] can only ever be one of these. Well, what if we just input paperscissorsrock? Then, the value of loses[computer_turn] will always be a substring of the player input, and we will win every time!
Connect to the service and input paperscissorsrock 5 times in a row to get the flag!
picoCTF{50M3_3X7R3M3_1UCK_C85AF58A}