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!