picoCTF 2022 RSP Writeup

条件反射的にブルートフォースしてしまったが。。。

Here's a program that plays rock, paper, scissors against you. I hear something good happens if you win 5 times in a row.

# 問題
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1163572/2fc6ffa5-043d-079c-f0d9-6fa29c536294.png)

# ソースコード

```c:game-redacted.c
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/types.h>


#define WAIT 60



static const char* flag = "[REDACTED]";

char* hands[3] = {"rock", "paper", "scissors"};
char* loses[3] = {"paper", "scissors", "rock"};
int wins = 0;



int tgetinput(char *input, unsigned int l)
{
    fd_set          input_set;
    struct timeval  timeout;
    int             ready_for_reading = 0;
    int             read_bytes = 0;
    
    if( l <= 0 )
    {
      printf("'l' for tgetinput must be greater than 0\n");
      return -2;
    }
    
    
    /* Empty the FD Set */
    FD_ZERO(&input_set );
    /* Listen to the input descriptor */
    FD_SET(STDIN_FILENO, &input_set);

    /* Waiting for some seconds */
    timeout.tv_sec = WAIT;    // WAIT seconds
    timeout.tv_usec = 0;    // 0 milliseconds

    /* Listening for input stream for any activity */
    ready_for_reading = select(1, &input_set, NULL, NULL, &timeout);
    /* Here, first parameter is number of FDs in the set, 
     * second is our FD set for reading,
     * third is the FD set in which any write activity needs to updated,
     * which is not required in this case. 
     * Fourth is timeout
     */

    if (ready_for_reading == -1) {
        /* Some error has occured in input */
        printf("Unable to read your input\n");
        return -1;
    } 

    if (ready_for_reading) {
        read_bytes = read(0, input, l-1);
        if(input[read_bytes-1]=='\n'){
        --read_bytes;
        input[read_bytes]='\0';
        }
        if(read_bytes==0){
            printf("No data given.\n");
            return -4;
        } else {
            return 0;
        }
    } else {
        printf("Timed out waiting for user input. Press Ctrl-C to disconnect\n");
        return -3;
    }

    return 0;
}


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;
  }
}


int main () {
  char input[3] = {'\0'};
  int command;
  int r;

  puts("Welcome challenger to the game of Rock, Paper, Scissors");
  puts("For anyone that beats me 5 times in a row, I will offer up a flag I found");
  puts("Are you ready?");
  
  while (true) {
    puts("Type '1' to play a game");
    puts("Type '2' to exit the program");
    r = tgetinput(input, 3);
    // Timeout on user input
    if(r == -3)
    {
      printf("Goodbye!\n");
      exit(0);
    }
    
    if ((command = strtol(input, NULL, 10)) == 0) {
      puts("Please put in a valid number");
      
    } else if (command == 1) {
      printf("\n\n");
      if (play()) {
        wins++;
      } else {
        wins = 0;
      }

      if (wins >= 5) {
        puts("Congrats, here's the flag!");
        puts(flag);
      }
    } else if (command == 2) {
      return 0;
    } else {
      puts("Please type either 1 or 2");
    }
  }

  return 0;
}
```

じゃんけんで５回連続で勝てば， flag がとれそうだ。

# 動きの確認

```
$ nc saturn.picoctf.net 51420
Welcome challenger to the game of Rock, Paper, Scissors
For anyone that beats me 5 times in a row, I will offer up a flag I found
Are you ready?
Type '1' to play a game
Type '2' to exit the program
```

じゃんけんするので， 1 を入力

```
1
1


Please make your selection (rock/paper/scissors):
```

ここでグーを出す。

```
rock
rock
You played: rock
The computer played: rock
Seems like you didn't win this time. Play again?
Type '1' to play a game
Type '2' to exit the program
```

勝てなかった。
次

```
1
1


Please make your selection (rock/paper/scissors):
scissors
scissors
You played: scissors
The computer played: paper
You win! Play again?
Type '1' to play a game
Type '2' to exit the program
```

勝った。

# ５連勝するまで，無限じゃんけんプログラム

仕様１
Type '2' to exit the program　に対して　1　を入力する

仕様２
勝っても負けても結果表示の末尾は　Play again?　と出る

仕様３
勝った時は，　You win!　が表示される

仕様４
５連勝したときフラグ　picoCTF{xxxxxx}　が表示される。

```python:solver.py
# coding: UTF-8
# python 2.7

import socket
import string
import time

start = time.time()

def recvuntil(s, tail):
    data = ''
    while True:
        if tail in data:
            return data
        data += s.recv(1)

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('saturn.picoctf.net',51420))

win = 0

# ブルートフォース
while True:

    # Type '2' to exit the programまでを受信，出力
    data = recvuntil(s, 'to exit the program')
    print(data)

    # 1を送信
    s.sendall('1\n')

    # じゃんけんの準備
    data = recvuntil(s, 'Please make your selection (rock/paper/scissors):')
    print(data)

    # グーを送信
    s.sendall('rock\n')
    
    # 終了判定　"You win! Play again?"　か"Seems like you didn't win this time. Play again?" を受信したら
    data = recvuntil(s, 'again?')

    # 勝敗は？
    if data.find('win!') > 0:
        # 勝利数加算
        win=win+1
    else:
        # 勝利数クリア
        win=0

    print(data)
    
    # ５連勝ならフラグを受け取って終了
    if win==5:
        data = recvuntil(s, '}')
        print(data)
        break
        

elapsed_time = time.time() - start
print ("speed:{0}".format(elapsed_time) + "[sec]")
```

実行結果

```
（略）

Please make your selection (rock/paper/scissors):

rock
You played: rock
The computer played: scissors
You win! Play again?

Congrats, here's the flag!
picoCTF{50M3_3X7R3M3_1UCK_58F0F41B}
speed:171.631999969[sec]
```

もし，６連勝を求められていたら，この方法でフラグ取れただろうか？