CMSC 414: Computer and Network Security (Univ. of Maryland) 1
Project #1: Buffer Overflows On-time Due Date: Monday September 28, 11:59 PM EDT
1 Project Overview
This project will give you first-hand experience with buffer overflow attacks. This attack exploits a buffer overflow vulnerability in a program to make the program bypass its usual execution and instead jump to alternative code (which typically starts a shell). There are several defenses against this attack (other than fixing the overflow vulnerability itself), such as address space randomization, compiling with stack guard, and making the stack non-executable.
The learning objective of this lab is for students to gain first-hand experience of the buffer-overflow attack. This attack exploits a buffer-overflow vulnerability in a program to make the program by- pass its usual execution sequence and instead jump to alternative code (which typically starts a shell). Specifically, the attack overflows the vulnerable buffer to introduce the alternative code on the stack and appropriately modify the return address on the stack (to point to the alternative code). There are several defenses against this attack (other than fixing the overflow vulnerability), such as address space randomization, compiling with stack-guard, dropping root privileges, etc.
In this lab, students are given a set-root-uid program with a buffer-overflow vulnerability for a buffer allocated on stack. They are also given shellcode, i.e., binary code that starts a shell. Their task is to exploit the vulnerability to corrupt the stack so that when the program returns, instead of going to where it was called from, it calls the shellcode, thereby creating a shell with root privilege. Students will also be guided through several protection schemes implemented in Ubuntu to counter this attack.
Note: There is a lot of helpful information in Section 10; be sure to read it before you get started. Also, if you get stuck, “Smashing the Stack for Fun and Profit” and the lecture notes and slides will help.
CMSC 414: Computer and Network Security (Univ. of Maryland) 2
2 Getting Set Up
Use the preconfigured Ubuntu machine we have given you, available here:
https://www.cs.umd.edu/class/fall2020/cmsc414-0201/resources.html
This is the machine we will use for testing your submissions. If it doesn’t work on that machine, you will get no points. It makes no difference if your submission works on another Ubuntu version (or another OS).
The amount of code you have to write in this lab is small, but you have to understand the stack. Using gdb (or some equivalent) is essential. The article, Smashing The Stack For Fun And Profit, is very helpful and gives ample details and guidance. Read it if you’re stuck.
Throughout this document, the prompt for an ordinary (non-root) shell is “$”, and the prompt for a root shell is “#”.
2.1 Starter files
Starter files are available at the class projects page:
https://www.cs.umd.edu/class/fall2020/cmsc414-0201/projects.html
3 Compiling
We will be using gcc to compile all of the programs in this project. There are a few non-standard ways we will be using gcc, like turning off some protection mechanisms, with some command line arguments. We are providing a makefile that handles all of this for you (just type “make”), but it’s a good idea to look at the options we provided and read about the options we chose.
3.1 Working with a debugger
gdb will be your best friend in this project. To get useful information from gdb regarding the names of functions and variables, include the -g commandline argument to gcc.
3.2 Compiling to 32-bit
This semester, we will be using the latest, 64-bit version of Ubuntu. For the sake of this project, however, we will be running in 32-bit. Our makefile handles this for you.
3.3 Disabling compiler protections
The gcc compiler implements two security mechanisms that help protect against buffer overflows and code injection. Also, our version by default turns on a protection to help randomize addresses. For the sake of this project, we will be turning off canaries and in one case, a non-executable stack..
1. Canaries: gcc implements the idea in the “Stack Guard” paper by introducing canaries in each stack frame. You can disable this protection by compiling with the commandline argument -fno-stack-protector.
CMSC 414: Computer and Network Security (Univ. of Maryland) 3
2. Non-executable stack: In the updated VM we are using, gcc by default will make the stack non-executable, thereby making it more difficult to launch arbitrary code. You can disable this withthecommandlineargument-z execstack.
4 Task 0: Impossible Game
To get things started, consider the following simple program (provided in the start-up files as task0a.c):
#include
#include
#include
int your_fcn()
{
/*
* You must write THREE different versions of this function,
* each of which result in you “winning” the impossible game.
* This is the ONLY part of the file you are allowed to modify.
*/
return 0; }
int main() {
// Seed our random number generator
struct timeval tv;
gettimeofday(&tv, NULL);
srand(tv.tv_usec);
// Get your guess
int your_guess = your_fcn();
// We’ll guess at random
int my_guess = rand();
// Um.. nothing to see here..
if(my_guess > your_guess)
my_guess = your_guess – 1; // who are you calling a cheater?
// Biggest guess wins
if(your_guess > my_guess)
puts(“You lose!”);
else
puts(“You win!”);
return EXIT_SUCCESS;
}
This program is a small game that seems impossible to win. Your function your_fnc() guesses a number and then tests it against a random choice by the rest of the program. If you look, the program doesn’t necessarily play fairly.
CMSC 414: Computer and Network Security (Univ. of Maryland) 4
Your task is to write not one but three different versions of the function your_fnc that each win the impossible game every time. As a slight hint, note that the only way that we determine whether or not youwinisiftheprogramprints“You win!”(followedbyanewline)attheend.
We will be compiling them with address space randomization and stack protection turned off and the stack executable. Address space randomization can be disabled by executing the following command:
$ sudo sysctl -w kernel.randomize_va_space=0
To re-enable ASLR, you simply run the above command but with a two instead of a zero:
$ sudo sysctl -w kernel.randomize_va_space=2
Note: This is the only part of this project that will be tested with randomization disabled.
Caveats. While you are allowed to set the body of your_fcn() as you wish, you are not allowed to modify main() itself. Also, this task permits an exception to the syllabus: hardcoding is allowed, if you think it will help you win! All of your solutions must be fundamentally distinct. You do not have to use a buffer overflow as one of your three solutions, but it is certainly one way to go!
Submitting. Create three copies of the impossible game: task0a.c, task0b.c, and task0c.c, each of which has a different implementation of your_fcn(). (There are general submission instruc- tions in Section 9.)
5 Task 1: A Vulnerable Program
In the remainder of the tasks, you will be exploiting a program that has a buffer overflow vulnerability. Unlike Task 0, you are not allowed to modify the program itself; instead, you will be attacking it by cleverly constructing malicious inputs to the program.
#include
#include
#include
/* this header exports functions to execute the exploit and read/write
* to/from it */
#include “comms.h”
void sensitive_function()
{
puts(“Full points! (Hey wait a minute.. How did you get here?)”);
exit(EXIT_SUCCESS);
}
void buffer_overflow()
{
char secret[32];
char buffer[32];
sprintf(secret, “authorized personnel only”);
CMSC 414: Computer and Network Security (Univ. of Maryland) 5
read_from_exploit(buffer, 64);
if(strcmp(secret, “let me in… LET ME INNNN!!!”) == 0)
sensitive_function();
}
static char greeting[128];
int main() {
int local = 5;
exec_exploit(“./exploit1.x”);
read_from_exploit(greeting, sizeof(greeting)-1);
write_to_exploit(greeting);
puts(“Waiting for input…”);
buffer_overflow();
puts(“Zero points. (Program terminated successfully; overflow failed.)”);
return EXIT_SUCCESS;
}
The vulnerable program for Task 1, vulnerable1.c, is given above. Use the makefile to compile it, generating two executables, “exploit1.x” and “vulnerable1.x.”
The above program prints a greeting supplied from the exploit program and later, has a buffer-overflow vulnerability in function buffer_overflow(). The vulnerable program, which runs as root, exe-
cutes an unpriviledged instance of exploit1 and communicates with it via the write_to_exploit
and read_from_exploit. The function signatures for write_to_exploit and read_from_exploit are in comms.h. You do not need to interact with comms.c or comms.h. write_to_exploit has
the same function signature and behavior as printf, except that it writes to exploit’s stdin instead of to the console. read_from_exploit reads the specified number of bytes into the buffer provided. The exploit, shown below, sends and receives messages to the vulnerable program through stdin and stdout.
#include
#include
#include
#include
#define BUFFER_SIZE 64
int main() {
CMSC 414: Computer and Network Security (Univ. of Maryland) 6
char buffer[BUFFER_SIZE];
memset(buffer, 0, BUFFER_SIZE); // Initialized to all zeroes
fprintf(stderr, “send debug statements to stderr\n”);
/* you may change this string */
char greeting[128] = “Exploit initiated communication with parent\n”;
write(STDOUT_FILENO, greeting, sizeof(greeting) – 1);
/* you may do whatever you want with the response */
char response[512];
fgets(response, sizeof(response), stdin);
// TODO: Populate the buffer here, as you see fit
write(STDOUT_FILENO, buffer, BUFFER_SIZE);
return EXIT_SUCCESS;
}
Because stdin and stdout are now redirect to the vulnerable program, the write and fgets calls write to and read from the vulnerable program. To print out debugging information, print to stderr. The final write in the exploit program is read by the vulnerable program in the buffer_overflow function, which results in a buffer overflow.
An attacker can exploit vulnerabilities in this program and potentially launch a shell. Moreover, because the program is a set-root-uid program (compiled as root using sudo), the attacker may be able to get a root shell. Doing so is your next task, but we will do so as a series of smaller steps.
For this task:
5.1 Part 1
Writeaprogram,exploit1.c,thatusesabufferoverflowtodefeatthestringcomparisoninvulnerable1.c. The goal of this part is to see where local variables are stored on the stack and how they may be modified
via a buffer overflow.
5.2 Part 2
Write a program, exploit2.c, that exploits vulnerable2.c with a buffer overflow and printf dataleaktooverwritetheinstructionpointersavedinthestacktojumpintosensitive function. There are two concepts here that you will want to consider. The first is whether useful information may be obtained by carelessly printing user information with printf like functions (hint: yes). The second is how to use a buffer overflow to overwrite the saved instruction pointer on the stack.
5.3 Part 3
Write a program, exploit3.c, that exploits vulnerable3.c with a buffer overflow and printf dataleaktooverwritetheinstructionpointersavedinthestacktojumpintosensitive function. Thisislargelysimilartopart2.However,sensitive functionrequiresaparticularargumentto be passed to it.
CMSC 414: Computer and Network Security (Univ. of Maryland) 7
5.4 Part 4
Write a program, exploit4.c, that exploits vulnerable4.c with a buffer overflow and printf dataleak to push shellcode onto the stack and execute it. This part requires all of the machinery as part 2, but now you must introduce new executable instructions into the process’s address space and correctly set the instruction pointer so that it jumps to that location. The makefile compiles vulnerable4.c with an executable stack.
6 Task 2: Writing a Secure Program
The majority of the project thus far has dealt with attacking existing code. In this task, you will write some secure code of your own. At face value, this is a simple program, so use this as an opportunity to pay close attention to every line of your code to ensure that it is not vulnerable to any of the attacks we’ve discussed.
In this task, you will be writing a secure program from scratch, with a focus on ensuring that it is not susceptible to attack. The program is a ROP gadget finder.
6.1 What is a ROP gadget finder?
Recall from lecture that return-oriented programming (ROP) finds small snippets of machine code in- struction ending in a ret (return) call. In the x86 instruction set, all instructions are represented by a single byte followed some variable number of bytes corresponding to the instructions’ inputs. For example, the ret command is represented by byte 0xc3 with no arguments. Machine code 0x35 represents a particular form of xor that takes one 4-byte argument and xor that argument with the reg- ister eax. Thus, if a file contained the bytes 0x35 0x00 0x00 0x00 0x00 0xc3, that would correspond to xor’ing 0x0 with eax and then returning.
Implementing a ROP attack involves having to identify small gadgets in a piece of data (such as a common library) and then piecing the gadgets together to do something that they do not do alone. Your task in this project is not to piece gadgets together, but rather to find the gadgets in the first place.
When you have a ROP gadget finder, you can run it on arbitrary pieces of data (programs, data files, etc.) and you will likely be amazed at just how many gadgets there are!
6.2 Details of what we are looking for
Which instructions? You do not need to support the entire x86 instruction set. You must implement precisely only what is in Table 1, which provides the set of instructions, along with their machine code representation (one byte), how many bytes they take as inputs, and their ASCII representation. Even though this is a vastly limited set, you may be surprised at how many gadgets you can find!
CMSC 414: Computer and Network Security (Univ. of Maryland) 8
Machine code
instruction Argument 0x01 0xcd 0x01 0xd8 0x01 0xf7
0x04 Any 1 byte
0x05 Any 4 bytes
0x31 0xcd 0x31 0xd8 0x31 0xf7
0x34 Any 1 byte
0x35 Any 4 bytes
0x89 0xcd 0x89 0xd8 0x89 0xf7 0xb0 Any 1 byte 0xb8 Any 4 bytes 0x68 Any 4 bytes 0xcd Any 1 byte 0xc3 none
ASCII representation
add ebp,ecx
add eax,ebx
add edi,esi
add al,0x(arg in hex) add eax,0x(arg in hex) xor ebp,ecx
xor eax,ebx
xor edi,esi
xor al,0x(arg in hex) xor eax,0x(arg in hex) mov ebp,ecx
mov eax,ebx
mov edi,esi
mov al,0x(arg in hex) mov eax,0x(arg in hex) push 0x(arg in hex) int 0x(arg in hex)
ret
Table 1: The restricted set of x86 instructions that your ROP finder must support. Note that while the inputs you read (the machine code instructions and the arguments) are provided in a non-ASCII binary file, you must output in a human-readable ASCII representation, provided in the third column.
What do we output? Output the ASCII representation of the gadgets, along with an ASCII repre- sentation of the bytes corresponding to the inputs, with each instruction of the gadget separated by a newline, and a blank line between each gadget. For instance, with the (binary) input 0x35 0x02 0x03 0x04 0x05 0xc3,thereareactuallytwogadgets:
add al,0x05
ret
xor eax,0x05040302
ret
The first gadget (add al,0x05) comes from starting the gadget at the 0x04 byte (note that there are no valid instructions with valid arguments preceding that). The second gadget (xor) comes from consuming all of the bytes (starting with 0x05, which uses up the remaining 4 bytes as an argument before the ret).
How large can gadgets be? Your program must take a single commandline argument: a number be- tween 1 and 10 corresponding to the maximum number of machine code instructions each gadget can be (this number does not include the ret itself: we consider any gadget consisting only of ret as being of size zero, and thus invalid).
CMSC 414: Computer and Network Security (Univ. of Maryland) 9
What inputs does the program take? The program should take exactly two command-line argu- ments: (1) a number between 1 and 10 (inclusive), and (2) the name of a file. If there are too few argu- ments, too many arguments, or an invalid argument, your program should print “Error: invalid command-line arguments”(followedbyanew-line)andexitwithreturncodeEXIT_FAILURE.
If any other errors occur (e.g., you cannot read the file), your program should print “Error: quitting” (followed by a new-line) and exit with return code EXIT_FAILURE. Otherwise, successful runs should ultimately exit with value EXIT_SUCCESS.
How large can the input file be? We do not impose any particular limit on the size of the file.
In what order should gadgets be printed? Gadgets should be printed in the order the first byte of the
gadget appears.
Do subsets of gadgets count as distinct? No. For example, if you have a gadget such as:
xor eax,0x05040302
add al,0x05
ret
then you would not print: add al,0x05
ret
6.3 A high-level ROP gadget-finding algorithm
There are multiple ways to find ROP gadgets in a file. One natural way is to iterate through the file, find ret calls (i.e., look for byte 0xc3) and then work backwards to find other instructions. However, note that this is not required and there are multiple ways to approach this task.
In the above example, once we identify the ret, we would walk backwards, find four bytes of 0x0 (which does not correspond to any instructions, and then the byte 0x35, which takes four bytes as an argument. We would thus be able to determine that there is a gadget of xor eax,0x0000000 followed by the ret.
To summarize, here is some pseudocode:
1. char *p = beginning of file 2. Until p == end of the file
(a) If p is pointing to a byte equal to ret (byte 0xc3) then backtrack from p. (b) Increment p
The idea is that there would then be a backtrack function that walks backwards from p to find valid commands that constitute a gadget.
CMSC 414: Computer and Network Security (Univ. of Maryland) 10
Hint: To get all of the possible gadgets, we recommend that you implement backtrack as a recur- sive function. But also please note that there are other viable solutions, as well.
What to submit: Submit a task2/ directory that contains a file called rop-finder.c, along with any other files you need. However, we should be able to build everything necessary by simply running gcc -Wallonrop-finder.c.
Sample inputs: We have provided you with several example inputs and outputs in the task2/ direc- tory. The diff of our output with the output of your program on a given input should be completely empty (please be sure to get all white-space and capitalizations correct).
The input files are binary files, so you will not be able to simply open them in most text editors. To view their contents, you can simply run the UNIX command hexdump. For example:
% hexdump task2/examples/in1.txt
0000000 35 11 11 11 00 c3
0000006
This prints two lines: the first column of each line is the number of bytes thus far in the file, and then byte by byte in the file. In this example, in1.txt consists of six bytes: 0x35 0x11 0x11 0x11 0x00 0xc3.
You are highly encouraged to create your own sample input files. You can easily generate your own bi- nary files right from the UNIX command-line, using the printf command. Here is how we generated in1.txt:
% printf ’\x35\x11\x11\x11\x00\xc3’ > task2/examples/in1.txt
6.4 Goal: Write this securely
Your overall task here is to write this code in a secure manner, that is not susceptible to the various memory-safety attacks we’ve discussed. We will be issuing various tests and code analyses to detect various potential attacks.
7 Task 3 (Extra credit): Re-enabling common defenses
As mentioned in Section 3.3, gcc by default puts some protective measure in place to mitigate buffer overflows and code injections. We got around these with commandline options to turn off canaries (-fno-stack-protector)andtomakethestackexecutable(-z execstack).Forextracredit, get Task 1 Part 4 to work without one or both of these options (more credit for both).
If you choose to do this task, submit all relevant files in a subdirectory called task3/ and include a file named task3/readme.txt that describes which defense(s) you attacked and how you went about launching your attack. Place your exploit code in a file named task3/exploit.c and include any other files that were necessary in launching this attack, if there were any.
CMSC 414: Computer and Network Security (Univ. of Maryland) 11
8 Task 4 (Extra credit): Format String Attacks
The goal of this task is to develop an exploit that, when executed by the given vulnerable program, should pop a shell.
There are a few differences between the given vulnerable program and the programs provided in Task 1. Notably, there is no longer a buffer overflow vulnerability when reading the input from your exploit. This means that in order to pop a shell, you will have to exploit the program using only the format string bug.
There are a couple of other differences in the given program. For example, the greeting is now placed on the stack. Additionally, the binary is compiled with the -no-pie flag, which disables ASLR only for the program itself.
That is, the address of global variables and code of the initial program is not randomized, even with ASLR enabled. However, other memory regions such as the stack, heap, and other libraries are still randomized.
One small clarification about what files can be present: While your submission should include an ex- ploit.c file and a makefile, the exploit itself should be entirely generated from within exploit.c. That is, there should not be additional files that must be present when beginning to run the program.
If you choose to do this task, submit the relevant file(s) along with your other files, by the assigned due date. Place all of them in a subdirectory called task4/, and include a file named task4/readme.txt that describes how you went about launching this attack.
9 What to submit
Submit the following files (We have provided a subcheck.pl file that will check to make sure that your submission directory contains these files; however, it does not automatically test your code, compile it, etc.). You can test to see if your directory dir has all of the required files by running ./subcheck.pl dirfromthecommandline.
Required:
1. task0/task0a.c
2. task0/task0b.c
3. task0/task0c.c
4. task1/exploit1.c
5. task1/exploit2.c
6. task1/exploit3.c
7. task2/rop-finder.c:
Optional (extra credit):
9. Files for extra credit Task 4 stored in a task4/ directory:
• task3/exploit.c
• task3/readme.txt:Yourdescriptionofwhichdefensesyougotaround(non-executable stack and/or canaries) and how.
• Any additional files you need to launch this attack.
CMSC 414: Computer and Network Security (Univ. of Maryland) 12
10. Files for launching your music attack, stored within a task5/ directory: • task4/exploit.c
• task4/readme.txt: Your description of how you exploited the format string bug. • Any other files used to launch this attack.
Note: Only the latest submission counts.
CMSC 414: Computer and Network Security (Univ. of Maryland) 13
10 Some extra background information
This section contains additional background on creating shell code and injecting code. We have in- cluded the files discussed here in the background/ directory in the starter files.
10.1 Shellcode
A shellcode is binary code that launches a shell. Consider the following C program:
/* start_shell.c */
#include
int main( ) {
char *name[2];
name[0] = “/bin/sh”;
name[1] = NULL;
execve(name[0], name, NULL);
}
The machine code obtained by compiling this C program can serve as a shellcode. However it would typically not be suitable for a buffer-overflow attack (e.g., it would not be compact, it may contain 0x00) entries). So one usually writes an assembly language program, and assembles that to get a shellcode.
We provide the shellcode that you will use in the stack. It is included in call_shellcode.c, but let’s take a quick divergence into it now:
/* call_shellcode.c */
/* A program that executes shellcode stored in a buffer */
#include
#include
#include
const char code[] =
“\x31\xc0”
“\x50”
“\x68″”//sh”
“\x68″”/bin”
“\x89\xe3”
“\x50”
“\x53”
“\x89\xe1”
“\x99”
“\xb0\x0b”
“\xcd\x80”
/* xorl
/* pushl
/* pushl
/* pushl
/* movl
/* pushl
/* pushl
/* movl
/* cdql
/* movb
/* int
%eax,%eax */
%eax */
$0x68732f2f */
$0x6e69622f */
%esp,%ebx */
%eax */
%ebx */
%esp,%ecx */
*/
$0x0b,%al */
$0x80 */
;
int main(int argc, char **argv)
{
char buf[sizeof(code)];
strcpy(buf, code);
((void(*)( ))buf)( );
}
CMSC 414: Computer and Network Security (Univ. of Maryland) 14
This program contains the shellcode in a char[] array. Compile this program, run it, and see whether a shell is invoked. Also, compare this shellcode with the assembly produced by gcc -S start_shell.c.
A few places in this shellcode are worth noting:
•
•
10.2
First, the third instruction pushes “//sh”, rather than “/sh” into the stack. This is because we need a 32-bit number here, and “/sh” has only 24 bits. Fortunately, “//” is equivalent to “/”, so we can get away with a double slash symbol.
Second, before calling the execve() system call, we need to store name[0] (the address of the string), name (the address of the array), and NULL to the %ebx, %ecx, and %edx registers, respectively. Line 5 stores name[0] to %ebx; Line 8 stores name to %ecx; Line 9 sets %edx to zero. There are other ways to set %edx to zero (e.g., xorl %edx, %edx); the one used here (cdql) is simply a shorter instruction. Third, the system call execve() is called when weset%alto11,andexecute“int $0x80”.
Guessing runtime addresses for vulnerable program
Consider an execution of our vulnerable program, vuln. For a successful buffer-overflow attack, we need to guess two runtime quantities concerning the stack at bof()’s invocation.
1. The distance, say R, between the overflowed buffer and the location where bof()’s return address is stored. The target address should be positioned at offset R in badfile.
2. The address, say T , of the location where the shellcode starts. This should be the value of the target address.
See Figure 1 for a pictorial example.
str (a pointer to a string)
Return Address
Previous Frame Pointer (FP)
buffer[0] … buffer[11]
variable_a
void func (char *str) { char buffer[12];
int variable_a; strcpy (buffer, str);
}
Int main() {
char *str = “I am greater than 12 bytes”; func (str);
}
High Address
Current FP
Low Address
(a) A code example (b) Active Stack Frame in func()
Figure 1: Buffer overflow stack example.
If the source code for a program like vuln is available, it is easy to guess R accurately, as illustrated in the previous figure. Another way to get R is to run the executable in a (non-root) debugger. The value obtained for R by these methods should be close, if not the same as, as the value when the vulnerable program is run during the attack.
Current Frame
CMSC 414: Computer and Network Security (Univ. of Maryland) 15
If neither of these methods is applicable (e.g., the executable is running remotely), one can always guess a value for R. This is feasible because the stack is usually not very deep: most programs do not push more than a few hundred or a few thousand bytes into the stack at any one time. Therefore the range of R that we need to guess is actually quite small. Furthermore, we can cover the entire range in a single attack by overwriting all its locations (instead of just one) with the target address.
Guessing T , the address of the shellcode, can be done in the same way as guessing R. If the source of the vulnerable program is available, one can modify it to print out T (or the address of an item a fixed offset away, e.g., buffer or stack pointer). Or one can get T by running the executable in a debugger. Or one can guess a value for T .
If address space randomization is disabled, then the guess would be close to the value of T when the vulnerable program is run during the attack. This is because (1) the stack of a process starts at the same address (when address randomization is disabled); and (2) the stack is usually not very deep.
Here is a program to print out the value of the stack pointer (source).
/* SeeSP.c */
#include
#include
int main(void)
{
register uintptr_t sp asm (“sp”);
printf(“SP: 0x%016” PRIxPTR “\n”, sp);
return 0;
}
10.3
Improving the odds
To improve the chance of success, you can add a number of NOPs to the beginning of the malicious code; jumping to any of these NOPs will eventually get execution to the malicious code. Figure 2 depicts the attack.
10.4 Storing a long integer in a buffer
In your exploit program, you may need to store a long integer (4 bytes) at position i of a char buffer buffer[]. Since each buffer entry is one byte long, the integer will occupy positions i through i+3 in buffer[]. Because char and long are of different types, you cannot directly assign the integer to buffer[i]; instead you can cast buffer+i into a long pointer and then assign the integer, as shown below:
char buffer[20];
long addr = 0xFFEEDD88;
long *ptr = (long *) (buffer + i);
*ptr = addr;
Bibliography
1. Aleph One. Smashing The Stack For Fun And Profit. Phrack 49, Volume 7, Issue 49. Available here:
CMSC 414: Computer and Network Security (Univ. of Maryland) 16
Malicious Code
str
Return Address
Previous FP
buffer [0] …… buffer [11]
(a) Jump to the malicious code (b) Improve the chance
Malicious Code
NOP NOP
…… (many NOP’s)
NOP
str
Return Address
Previous FP
buffer [0] …… buffer [11]
Figure 2: NOP sled example.
http://www.cs.umd.edu/class/spring2019/cmsc414/papers/stack-smashing.pdf
Stack’s growing direction