In this article, we will begin by solving a simple challenge to leak a secret from memory. In the next article, we will discuss another example, where we will chain a format string vulnerability and Buffer Overflow vulnerability to create better impact.
How can format string vulnerabilities be exploited?
As mentioned in the previous article, the following are some of the attacks possible using format string vulnerabilities.
Leaking secrets Denial of Service Leaking memory addresses Overwriting memory addresses
In this article, let us discuss the first two items.
Leaking secrets from stack
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. As we can notice, the program is vulnerable to format string vulnerability since the printf function receives user input and prints it. It should be noted that there is no format specifier used in the printf function thus leaving the program vulnerable. char *secret = “p@ssw0rD”; printf(argv[1]); } Let us run the program using gdb, check the disassembly of the main function and set up a breakpoint at the address of printf call. As we can see in the preceding excerpt, we have started the program and the breakpoint should hit. Dump of assembler code for function main: 0x0000000000401136 <+0>: endbr64 0x000000000040113a <+4>: push rbp 0x000000000040113b <+5>: mov rbp,rsp 0x000000000040113e <+8>: sub rsp,0x20 0x0000000000401142 <+12>: mov DWORD PTR [rbp-0x14],edi 0x0000000000401145 <+15>: mov QWORD PTR [rbp-0x20],rsi 0x0000000000401149 <+19>: lea rax,[rip+0xeb4] # 0x402004 0x0000000000401150 <+26>: mov QWORD PTR [rbp-0x8],rax 0x0000000000401154 <+30>: mov rax,QWORD PTR [rbp-0x20] 0x0000000000401158 <+34>: add rax,0x8 0x000000000040115c <+38>: mov rax,QWORD PTR [rax] 0x000000000040115f <+41>: mov rdi,rax 0x0000000000401162 <+44>: mov eax,0x0 0x0000000000401167 <+49>: call 0x401040 printf@plt 0x000000000040116c <+54>: mov eax,0x0 0x0000000000401171 <+59>: leave 0x0000000000401172 <+60>: ret End of assembler dump. gef➤ gef➤ b *0x0000000000401167 gef➤ run If the breakpoint is hit, that means we are about to execute the printf function. If we examine the stack at this point of time, we should notice the address of the string p@ssw0rD on the stack as highlighted below. As we can see in the preceding excerpt, there is an address (0x0000000000402004) on the stack, which is pointing to the string “p@ssw0rD”. 0x00007fffffffdf48│+0x0008: 0x0000000100401050 0x00007fffffffdf50│+0x0010: 0x00007fffffffe050 → 0x0000000000000001 0x00007fffffffdf58│+0x0018: 0x0000000000402004 → “p@ssw0rD” 0x00007fffffffdf60│+0x0020: 0x0000000000000000 ← $rbp 0x00007fffffffdf68│+0x0028: 0x00007ffff7ded0b3 → <__libc_start_main+243> mov edi, eax 0x00007fffffffdf70│+0x0030: 0x00007ffff7ffc620 → 0x0005043700000000 0x00007fffffffdf78│+0x0038: 0x00007fffffffe058 → 0x00007fffffffe380 → Our objective is to leak this string using the format string vulnerability existing in the vulnerable program. 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. The output looks as follows. As we can notice in the preceding output, the address 402004 is leaked. This is the same address we noticed on the stack earlier. This means, we are able to leak the address of the string p@ssw0rD. We specified multiple %llx strings to be able to dump the address and the rest of the entries dumped from the stack are not useful for us. So, we can choose to dump only the address that we want by using Direct Parameter Access. This can be done by specifying the distance at which the address is printed. In this case, the 9th value is the address. We can use %9$11x to directly access this address. This looks as follows. As we can notice, we managed to leak just the address that we wanted. However, we have leaked the address and not the actual string value. To be able to leak the actual string instead of the address, we can use %9$11s. This will ask the printf function to print the value pointed by the 9th position on the stack. This looks as follows. $ We managed to successfully leak the secret string from the stack by using a format string vulnerability in the target binary. $
Crashing the program
In the previous section, we used %9$s as our format specifier and dumped the secret string from the stack. This technique worked because 9th value is a valid address that is pointing to our secret string. If we try to access an invalid memory location in a similar fashion, that will cause a segmentation fault leading to crashing the program. 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.
Conclusion
Format String vulnerabilities clearly can create great damage, when exploited. One can easily read data from arbitrary memory locations and even crash the applications using them. The impact can be more if these vulnerabilities are chained with other vulnerabilities such as buffer overflow. In the next article, we will discuss how one can chain format string vulnerabilities and buffer overflows to bypass memory protections such as stack canaries.
Sources
Source Code Analysis Tools Format string attack Format string vulnerabilities