Solution: "Random" Card Shuffling (PlanetPoker)

Tools Used:
After opening the link to the website, you'll see a game with some rules on the left. Inside the rules, you'll see a link to the "HSF shuffling algorithm" that is used to prove the game is fair. Clicking that link will show you the algorithm in PHP.

So now we have the shuffling algorithm. Reviewing it, you'll see that the Random Number Generator is seeded with the number of milliseconds that have elapsed since midnight UTC.

That's... not a lot of possible shuffles. Our task is to write a quick PHP script that generates every possible shuffle, and then filter down through those possibilities based on the cards we see. After a few cards, there should only be one possible shuffle left, and we can then peruse that shuffle and see what all future cards will be.

Our PHP script will take the shuffling algorithm they provided, and then use that to generate all possible decks. To give us some margin for error, we'll tell the script to use all 5 seconds (5000 milliseconds) AFTER we start it to look for deck possibilities. This means we start the script, then sometime within the next 5 seconds start the game.

One last thing to note before showing the script: As we can see from the shuffling algorithm, a card is represented by a number, 0-51. A rank is obtained using the algorithm card_number % 13 + 1, while a suit is obtained from card_number / 13 | 0. Since in the game UI we only see the resulting rank and suit, we'd normally have to reverse these algorithms to go from rank/suit to a number 0-51... however in this case, if you open up the Chrome Developer Tools and view the api call, you'll see it returns to us the card number of 0-51 that it then translates into a rank/suit to render on the page, so we can take the shortcut and just use the card number returned by the api call.

Here's the script:

  <?

  function get_shuffled_deck($seed) {
    //For a given $seed, return the deck according to the "HSF shuffling algorithm"

    srand($seed);

    // Fill the deck with unique cards
    $cards = array();
    for ($i = 0; $i < 52; $i++) {
      $cards[$i] = $i;
    }

    // Randomly rearrange each card
    for ($i = 0; $i < 52; $i++) {
      $random_number = rand(0,51);
      $tmp = $cards[$random_number];
      $cards[$random_number] = $cards[$i];
      $cards[$i] = $tmp;
    }
    return $cards;
  }

  // Generate a base seed from time t=0 (now)
  $timestamp_pieces = explode(' ', microtime());
  $milliseconds_timestamp = ($timestamp_pieces[1] * 1000) + ($timestamp_pieces[0] * 1000);
  $milliseconds_since_midnight_utc = $milliseconds_timestamp % 86400000;

  $all_decks = array();

  //Generate 5000 new seeds (and 5000 new decks), covering 5 seconds after this script started
  for ($i=0; $i < 5000; $i++) {
    $seed = $milliseconds_since_midnight_utc + $i;
    $new_possible_deck = get_shuffled_deck($seed);
    array_push($all_decks, $new_possible_deck);
  }


  //Helper function for later
  function print_decks($all_decks) {
    //For a given deck, print it's ranks for use in the High Low game

    foreach($all_decks as $deck) {
      $pretty_print_deck = array();
      foreach($deck as $card) {
        $rank = intval($card) % 13 + 1;
        $suit = intval($card) / 13 | 0;
        //Ignore suit, not relevant for this game, just print rank
        array_push($pretty_print_deck, $rank);
      }
      print "\n".json_encode($pretty_print_deck);
    }
  }

  //Now let's find the correct deck among the 5000 possibilities
  $card_number = 0;
  while(count($all_decks) > 1) {
    print "\nTotal possible decks... ".count($all_decks);
    print "\nEnter next card: ";
    $next_card = intval(fgets(STDIN));

    $new_p_decks = [];
    foreach($all_decks as $deck) {
        if ($deck[$card_number] == $next_card) {
          array_push($new_p_decks, $deck);
        }
    }
    $all_decks = $new_p_decks;
    $card_number += 1;
    if (count($all_decks) < 10) {
      print_decks($all_decks);
    }
  }

  ?>

          


Now to use the script, we invoke it in the terminal with `php solution.php`. Within 5 seconds of invoking it, we need to trigger a new game on the website while having Chrome Developer Tools open to the Network tab. You'll then see an api call return with the card number - in this case, we see a queen of spades, which is card number 11 - so we put "11" into the php script.



We can see from the 5000 deck possibilities we started with, only 107 started with a queen of spades. Far better, but still not good enough - we need to narrow it down further. Let's guess "Lower", as a queen is a fairly high card.

When we guess lower, the 8 of clubs came up, so we were successful. Again in the Chrome Developer Tools, we see the api call returned the card number 33 (which represents 8 of clubs). Let's put 33 into our php script to further reduce the possible decks.



Unfortunately that still left us with 4 possible decks. The PHP script helpfully printed them, showing their ranks of each deck. In the screenshot below, you can see each of the four decks started with a Queen (rank 12), followed by an 8. From here they diverge - one deck shows a Jack (rank 11) as the next card, one shows a 5, one shows a 2, and one shows a King (rank 13).

We have a 50/50 chance, so I just guessed higher - and was victorious. We see the returned card was a Jack of Spades (card number 10). Let's put that into our PHP script and narrow it down to a single deck.



Our script returns the below image as the final and only possible deck. Observe that the next card in the sequence is supposed to be a King (rank 13).



Assuming the script is right, we should guess higher and uncover a King!



Victory. Now we just need to follow the deck order our PHP script uncovered to get a perfect score and reveal the flag.