Programming lesson
Format String Attack Lab: Viewing and Writing Memory on CSCI 180
Step-by-step tutorial on exploiting format string vulnerabilities to view and modify memory, with ASLR disabled, for CSCI 180 computer security exercises.
Introduction to Format String Vulnerabilities
In computer security, format string vulnerabilities arise when user input is passed directly as the format string argument to functions like printf(). This lab guides you through exploiting such a vulnerability in a C program compiled with -m32 on a 64-bit system. By disabling ASLR and using carefully crafted inputs, you can read and write arbitrary memory locations. This knowledge is foundational for understanding memory corruption attacks.
Disabling ASLR (Task 0)
Address Space Layout Randomization (ASLR) is a defense that randomizes memory addresses, making attacks harder. For this exercise, we disable it temporarily using:
$ sudo sysctl -w kernel.randomize_va_space=0Verify with: $ sysctl -a --pattern randomize. A value of 0 means randomization is off. Remember, changes are lost on reboot.
Understanding the Vulnerable Code (Task 1)
Compile the provided vul.c with the -m32 flag to produce a 32-bit binary:
$ gcc -m32 vul.c -o vulExamine the code. The vulnerability lies in the printf() call that directly uses user input as the format string. For example:
printf(user_input); // vulnerableThis allows an attacker to read and write stack memory using format specifiers like %x and %n.
Q1-A: Identify the Vulnerable Line
The vulnerable line is typically a printf() call that takes user input as its only argument. In the provided code, it's inside myprint() function. Draw the stack frame showing myprint() and the vulnerable printf() call. The stack will contain return addresses, saved frame pointers, local variables, and the format string pointer.
Q1-B: Crash the Program
To crash the program, supply a large number of format specifiers. For example, input %s%s%s%s%s%s%s%s will cause printf() to interpret stack values as pointers and dereference them, leading to a segmentation fault. Use:
$ echo '%s%s%s%s%s%s%s%s' | ./vulExpected output: Segmentation fault. This confirms the vulnerability.
Viewing Memory Values (Task 2)
Your goal is to leak stack values using %x or %p. The target variable (initialized to 0x11111111) helps locate the offset to the format string on the stack. Craft input like:
$ echo 'AAAA.%x.%x.%x.%x.%x.%x.%x.%x' | ./vulLook for the 41414141 (hex for 'AAAA') in the output. The position of 41414141 tells you the stack offset. For example, if it appears as the 5th %x, then the offset is 5. The target value 0x11111111 will appear as 11111111 in the output. Count its position relative to your input to determine the exact offset. This offset is crucial for writing.
Q2: Screenshot and Stack Frame
Run the command and capture the output. Update your stack frame drawing to include all values between myprint() and printf() frames. Typically, there are 4-8 extra values (like saved registers, alignment padding). The target variable's address is known from the source code (e.g., 0xffffd0a0).
Writing an Integer to Memory (Task 3)
Now, modify the target variable using %n. %n writes the number of characters printed so far to the memory address pointed to by the corresponding argument. Since we need the address of target on the stack, we include it as part of the input. Because the address contains non-printable bytes, we use a file.
Creating the Input String
Assume target address is 0xffffd0a0 (little-endian: a0 d0 ff ff). To write a value like 0x00000005 (5), we need to print 5 characters before %n. Use:
$ echo $(printf '\xa0\xd0\xff\xff')'%x%x%x%x%n' > inputfileBut we need to adjust the offset. Suppose the offset to our address is 5. Then we use %5$n to directly reference the 5th argument. The input becomes:
$ echo $(printf '\xa0\xd0\xff\xff')'%x%x%x%x%5$n' > inputfileRun: $ ./vul < inputfile. The program will print some hex values and then change target to the number of characters printed before %n. If you want to write a specific value, you need to print that many characters using format specifiers like %100x to pad output. For example, to write 0x500 (1280 decimal), print 1280 characters before %n:
$ echo $(printf '\xa0\xd0\xff\xff')'%1280x%5$n' > inputfileThis prints the address (4 bytes) plus 1280 characters from a hex value, totaling 1284 characters, so target becomes 0x00000500? Actually, 1284 = 0x504, so adjust padding to get exact value. Use %1276x to get total 1280 (4 + 1276 = 1280 = 0x500).
Q3: Result and Explanation
After running, the output shows target before: 0x11111111 and target after: 0x00000500 (or whatever you wrote). %n writes the number of characters output so far (as an integer) to the address pointed to by the corresponding argument. In this case, it writes to target's address. The value written is the count of characters printed, which you control via padding. This demonstrates arbitrary memory write.
Conclusion
By completing these tasks, you've exploited a format string vulnerability to read and write memory. This skill is vital for understanding low-level security and developing exploits. Always use such knowledge ethically, such as in penetration testing or secure coding.