Skip to content

PicoCTF 2022: RPS

TL;DR

  • The server seeds rand() using the current time.
  • Client and server share the same libc PRNG behavior.
  • Re-seed libc locally with the same timestamp.
  • Predict the server’s move and always play the winning hand.
  • Win five rounds in a row to get the flag.

Video Walkthrough

PicoCTF 2022 RPS pwn video walkthrough showing libc rand prediction to beat rock paper scissors

Description

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

Solution

from pwn import *
from time import time
from ctypes import CDLL

io = remote('saturn.picoctf.net', 53865)
context.log_level = 'debug'

libc = CDLL('/lib/x86_64-linux-gnu/libc.so.6')

for i in range(5):
    io.recvuntil(b'program')
    io.sendline(b'1')

    sleep(1)  # Compensate for client-server

    libc.srand(int(time()))  # Call srand() with current time as seed
    computer_turn = libc.rand() % 3  # Predict computers turn

    # Computer players hand
    hands = ["rock", "paper", "scissors"]
    # Calculate winning move
    if hands[computer_turn] == 'rock':
        payload = b'paper'
    elif hands[computer_turn] == 'paper':
        payload = b'scissors'
    elif hands[computer_turn] == 'scissors':
        payload = b'rock'

    # Submit turn
    io.recvuntil(b':')
    io.sendline(payload)

io.interactive()  # Flag