Comma-Club (Revenge)

One-Byte Overwrite over Function Pointer

Analysis

Both challenges (Revenge/Non Revenge) have the same (unintended?) solve.

Lets analyze the binary.

void __noreturn main_menu()
{
  int v0; // [rsp+4h] [rbp-Ch]
  unsigned __int64 i; // [rsp+8h] [rbp-8h]

  puts("Welcome to the Wyoming Vote Tallying Software\nPresented by Jeff!");
  while ( 1 )
  {
    while ( 1 )
    {
      v0 = menu(
             "Please select an option:\n"
             "1) Enter votes for a candidate\n"
             "2) View current vote totals\n"
             "3) Close voting and display the winner (requires password)\n"
             "4) Change password (requires password)\n"
             "> ",
             "Incorrect value, try again\n> ",
             5LL);
      if ( v0 != 4 )
        break;
      if ( (unsigned int)check_password() )
        set_new_password();
    }
    if ( v0 > 4 )
    {
LABEL_18:
      puts("This should be unreachable, so there's nothing here. Congrats for finding it though!");
    }
    else if ( v0 == 3 )
    {
      if ( (unsigned int)check_password() )
        close_voting();
    }
    else
    {
      if ( v0 > 3 )
        goto LABEL_18;
      if ( v0 == 1 )
      {
        add_votes_menu();
      }
      else
      {
        if ( v0 != 2 )
          goto LABEL_18;
        for ( i = 0LL; i < num_cands; ++i )
        {
          print_status(cand_array + (i << 6));
          puts(byte_3449);
        }
      }
    }
  }
}

To summarize, there is a close_voting() function which will give us a shell but we first require to pass the check_password().

Three structs of 64 bytes each are also initialized upon start of the program which roughly have this format.

The main part is to focus on the votes and vote_printer_selection ptr.

Upon manually debugging, I noticed that if my total votes is above 1,000,000, i would get a segfault.

Upon further inspection, we see that we have successfully override the lsb of the function ptr in total!

Lets break it down to how that happens

  • Votes are converted using strtol, which interprets each byte as an integer value. The 8-byte space can hold up to 8 digits.

  • print_status function calls print_int_with_commas to calculate and display the total votes by adding both candidates' votes.

  • Commas are added between every 3 digits in the total. This formatting causes the displayed total to exceed the 8-byte limit when the vote count is high.

  • Since the formatted output (with commas) can exceed 8 characters, the overflow can overwrite the least significant byte (LSB) of the total structโ€™s vote_printor_selector.

  • By manipulating the vote inputs to exceed the digit limit, we can potentially call a different function by overwriting the last byte to a nearby function (change_password)

We can see this in action where if we change the last byte to 0x39 which is equivalent to '9' in string, we can redirect execuution to change_password!

We can see that by default, the password that will be changed is the value in $RDI which is the string 'Total'.

Below is the solve.

Last updated