Format Strings

Format Strings is a bug in which user input is passed to a formatting function such as printf or scanf as the input format.

printf(format, user_input); // not vulnerable
printf(user_input);         // vulnerable

πŸ“š They could allow a malicious user to read/write variables, which may lead to sensitive data exposure.

πŸͺ¦ Format String bugs are uncommon nowadays, mostly due to compilers notifying the developer.


C printf() function

When a function is called, its parameters are stored in memory at a location called the stack. Given printf("%s", str):

  • ...
  • 0x00002: address of the "%s" string parameter
  • 0x00003: address of the str string parameter
  • ...

The printf function is a variadic function. It will know how many parameters it has from the format string (e.g., %s=>n=1).

✍️ If we have the following code: printf("%s"), the function will still try to read one string from the stack, which may crash the program.

We can use that to read arbitrary values. Refer to C Types.

  • %c: arbitrary read a char
  • %s: arbitrary read a string
  • %x: arbitrary read an integer and display it as hexadecimal
  • %p: arbitrary expose an address

πŸ“š To read the n-th parameter that is a char %c, we would use: %nth$c.

πŸ€– You can use rax2 0xFFFF to convert from hexa to decimal. You can write a number with $n/$hn like Hello%nth$n, %4294967295x%nth$n or %65535d%nth$hn. The number is equals to the number of written chars.


Python format() function

python_format_string python_format_string

The python format function may be exploited to access variables from the code and display them. In the example below, we access the method __init__ while any methods of the class would work. We can then relatively access other members of the class, or move up and access members of the parent global namespace.

from secret import X # in another file
X = "xxx"            # in the same file

class X:
   pass

instance = X()
"{0.__init__}".format(x)
"{x.__init__}".format(x=x)
"{x.__init__.__globals__}".format(x=x)
"{x.__init__.__globals__[X]}".format(x=x)
"{x.__init__.__globals__[X][index]}".format(x=x) # one by one

We can alternatively use error messages.

  • Since Python 3.10, an additional message body was added when using an invalid string after ":".
>>> "{x:toto}".format(x=5)
# ValueError: Invalid format specifier 'toto' for object of type 'int'
  • For every version of python, an exception is raised when we use an unknown format code (extraction letter by letter).
>>> "{x:s}".format(x=5)
# ValueError: Unknown format code 's' for object of type 'int'

πŸ“š Refer to my cheatsheet for more payloads.


πŸ‘» To-do πŸ‘»

Stuff that I found, but never read/used yet.

  • axcheron, codearcana, learn-cyber
  • for i in seq 1 1000; do ./example "%$i$s" 2> /dev/null | grep -v "Segmentation fault"; done.
  • ./xxx $(python2 -c 'print "A" * 400 + "%145$p"'): found where my string is stored