Memory Safety Vulnerabilities
Part 2
CS 161 Spring 2026
Computer Science 161
Reminder: buffer overflow
2
char name[20];
char instrux[20] = "none";
void vulnerable(void) {
...
gets(name);
...
}
... |
... |
... |
... |
... |
instrux |
instrux |
instrux |
instrux |
instrux |
name |
name |
name |
name |
name |
gets starts writing here and can overwrite anything above name!
Note: name and instrux are declared in static memory (outside of the stack), which is why name is below instrux
Computer Science 161
Reminder: Integer overflow
3
void func(int len, char *data) {
char buf[64];
if (len > 64)
return;
memcpy(buf, data, len);
}
void *memcpy(void *dest, const void *src, size_t n);
This is a signed comparison, so len > 64 will be false. But when we call memcpy(), casting -1 to an unsigned type yields 0xffffffff: another buffer overflow!
Computer Science 161
Format String Vulnerabilities
4
Textbook Chapter 3.3
Computer Science 161
printf behavior
5
Computer Science 161
printf behavior
6
void func(void) {
int secret = 42;
printf("%d\n", 123);
}
printf assumes that there is 1 more argument because there is one format specifier (%d), so it will look 4 bytes up the stack for the argument
What if there is no argument?
... | |||
... | |||
... | |||
... | |||
RIP for func | |||
SFP for func | |||
secret = 42 | |||
123 (arg to printf) | |||
&"%d\n" (arg to printf) | |||
RIP for printf | |||
SFP for printf | |||
[printf frame] |
'%' | 'd' | '\n' | '\0' |
arg0
arg1
Computer Science 161
printf behavior
7
void func(void) {
int secret = 42;
printf("%d\n");
}
Because the format string contains the %d, it will still look 4 bytes up -- and print the value of secret!
... | |||
... | |||
... | |||
... | |||
RIP for func | |||
SFP for func | |||
secret = 42 | |||
&"%d\n" (arg to printf) | |||
RIP for printf | |||
SFP for printf | |||
[printf frame] |
'%' | 'd' | '\n' | '\0' |
arg0
arg1
Computer Science 161
Format String Vulnerabilities
8
char buf[64];
void vulnerable(void) {
if (fgets(buf, 64, stdin) == NULL)
return;
printf(buf);
}
What is the issue here?
Computer Science 161
Format String Vulnerabilities
9
char buf[64];
void vulnerable(void) {
if (fgets(buf, 64, stdin) == NULL)
return;
printf(buf);
}
Computer Science 161
Format String Vulnerability Walkthrough
10
Note that strings are passed by reference in C, so the argument to printf is actually a pointer to buf, which is in static memory.
char buf[64];
void vulnerable(void) {
char *secret_string = "pancake";
int secret_number = 42;
if (fgets(buf, 64, stdin) == NULL)
return;
printf(buf);
}
... | |||
RIP for vulnerable | |||
SFP for vulnerable | |||
secret_string | |||
secret_number | |||
buf (arg to printf) | |||
RIP for printf | |||
SFP for printf | |||
[printf frame] |
buf | |||
'a' | 'k' | 'e' | '\0' |
'p' | 'a' | 'n' | 'c' |
Computer Science 161
Format String Vulnerability Walkthrough
11
Input: %d%s
char buf[64];
void vulnerable(void) {
char *secret_string = "pancake";
int secret_number = 42;
if (fgets(buf, 64, stdin) == NULL)
return;
printf(buf);
}
Output:
We’re calling printf("%d%s"). printf reads its first argument (arg0), sees two format specifiers, and expects two more arguments (arg1 and arg2).
... | |||
RIP for vulnerable | |||
SFP for vulnerable | |||
secret_string | |||
secret_number | |||
buf (arg to printf) | |||
RIP for printf | |||
SFP for printf | |||
[printf frame] |
arg0
arg1
arg2
'\0' | | | |
'%' | 'd' | '%' | 's' |
'a' | 'k' | 'e' | '\0' |
'p' | 'a' | 'n' | 'c' |
Computer Science 161
Format String Vulnerability Walkthrough
12
Input: %d%s
char buf[64];
void vulnerable(void) {
char *secret_string = "pancake";
int secret_number = 42;
if (fgets(buf, 64, stdin) == NULL)
return;
printf(buf);
}
Output:
42
The first format specifier %d says to treat the next argument (arg1) as an integer and print it out.
... | |||
RIP for vulnerable | |||
SFP for vulnerable | |||
secret_string | |||
secret_number | |||
buf (arg to printf) | |||
RIP for printf | |||
SFP for printf | |||
[printf frame] |
arg0
arg1
arg2
'\0' | | | |
'%' | 'd' | '%' | 's' |
'a' | 'k' | 'e' | '\0' |
'p' | 'a' | 'n' | 'c' |
Computer Science 161
Format String Vulnerability Walkthrough
13
Input: %d%s
char buf[64];
void vulnerable(void) {
char *secret_string = "pancake";
int secret_number = 42;
if (fgets(buf, 64, stdin) == NULL)
return;
printf(buf);
}
Output:
42pancake
The second format specifier %s says to treat the next argument (arg2) as a (pointer to a) string and print it out.
%s will dereference the pointer at arg2 and print until it sees a null byte ('\0')
... | |||
RIP for vulnerable | |||
SFP for vulnerable | |||
secret_string | |||
secret_number | |||
buf (arg to printf) | |||
RIP for printf | |||
SFP for printf | |||
[printf frame] |
arg0
arg1
arg2
'\0' | | | |
'%' | 'd' | '%' | 's' |
'a' | 'k' | 'e' | '\0' |
'p' | 'a' | 'n' | 'c' |
Computer Science 161
Format String Vulnerabilities
14
void vulnerable(void) {
char buf[64];
if (fgets(buf, 64, stdin) == NULL)
return;
printf(buf);
}
Computer Science 161
Format String Vulnerability Walkthrough
15
Input: %d%n
char buf[64];
void vulnerable(void) {
char *secret_string = "pancake";
int secret_number = 42;
if (fgets(buf, 64, stdin) == NULL)
return;
printf(buf);
}
Output:
We’re calling printf("%d%n"). printf reads its first argument (arg0), sees two format specifiers, and expects two more arguments (arg1 and arg2).
... | |||
RIP for vulnerable | |||
SFP for vulnerable | |||
secret_string | |||
secret_number | |||
buf (arg to printf) | |||
RIP for printf | |||
SFP for printf | |||
[printf frame] |
arg0
arg1
arg2
'\0' | | | |
'%' | 'd' | '%' | 'n' |
'a' | 'k' | 'e' | '\0' |
'p' | 'a' | 'n' | 'c' |
Computer Science 161
Format String Vulnerability Walkthrough
16
Input: %d%n
char buf[64];
void vulnerable(void) {
char *secret_string = "pancake";
int secret_number = 42;
if (fgets(buf, 64, stdin) == NULL)
return;
printf(buf);
}
Output:
42
The first format specifier %d says to treat the next argument (arg1) as an integer and print it out.
... | |||
RIP for vulnerable | |||
SFP for vulnerable | |||
secret_string | |||
secret_number | |||
buf (arg to printf) | |||
RIP for printf | |||
SFP for printf | |||
[printf frame] |
arg0
arg1
arg2
'\0' | | | |
'%' | 'd' | '%' | 'n' |
'a' | 'k' | 'e' | '\0' |
'p' | 'a' | 'n' | 'c' |
Computer Science 161
Format String Vulnerability Walkthrough
17
Input: %d%n
char buf[64];
void vulnerable(void) {
char *secret_string = "pancake";
int secret_number = 42;
if (fgets(buf, 64, stdin) == NULL)
return;
printf(buf);
}
Output:
42
The second format specifier %n says to treat the next argument (arg2) as a pointer, and write the number of bytes printed so far to the address at arg2.
We've printed 2 bytes so far, so the number 2 gets written to secret_string.
... | |||
RIP for vulnerable | |||
SFP for vulnerable | |||
secret_string | |||
secret_number | |||
buf (arg to printf) | |||
RIP for printf | |||
SFP for printf | |||
[printf frame] |
arg0
arg1
arg2
'\0' | | | |
'%' | 'd' | '%' | 'n' |
'a' | 'k' | 'e' | '\0' |
0x02 | 0x00 | 0x00 | 0x00 |
Computer Science 161
Format Strings: Stack Diagram
18
void vulnerable(void) {
char buf[16];
char str[12];
fgets(buf, 16, stdin);
printf(buf);
}
Now, let’s try some format string vulnerabilities where the user-controlled buffer is on the stack instead of in static memory.
What does the stack diagram look like?
... | | |||
| | |||
| | |||
| | |||
| | |||
| | |||
| | |||
| | |||
| | |||
| | |||
| | |||
| | |||
| | |||
| | |||
Computer Science 161
Format Strings: Stack Diagram
19
This is the stack diagram while printf is being called.
Where does printf look for arguments?
... | | |||
RIP for vulnerable | | |||
SFP for vulnerable | | |||
buf | | |||
buf | | |||
buf | | |||
buf | | |||
str | | |||
str | | |||
str | | |||
buf (arg to printf) | | |||
RIP for printf | | |||
SFP for printf | | |||
[printf frame] | | |||
void vulnerable(void) {
char buf[16];
char str[12];
fgets(buf, 16, stdin);
printf(buf);
}
Computer Science 161
Format Strings: Stack Diagram
20
... | | |||
RIP for vulnerable | | |||
SFP for vulnerable | | |||
buf | arg7 | |||
buf | arg6 | |||
buf | arg5 | |||
buf | arg4 | |||
str | arg3 | |||
str | arg2 | |||
str | arg1 | |||
buf (arg to printf) | arg0 | |||
RIP for printf | | |||
SFP for printf | | |||
[printf frame] | | |||
We’ve labeled which values in memory printf will interpret as arguments.
For example, if buf has 4 percent formatters, printf will match the last percent formatter with arg4.
void vulnerable(void) {
char buf[16];
char str[12];
fgets(buf, 16, stdin);
printf(buf);
}
Computer Science 161
Write 100 to 0xdeadbeef
21
Attack scenario: Write the number 100 to memory address 0xdeadbeef.
What input should the attacker supply?
... | | |||
RIP for vulnerable | | |||
SFP for vulnerable | | |||
buf | arg7 | |||
buf | arg6 | |||
buf | arg5 | |||
buf | arg4 | |||
str | arg3 | |||
str | arg2 | |||
str | arg1 | |||
buf (arg to printf) | arg0 | |||
RIP for printf | | |||
SFP for printf | | |||
[printf frame] | | |||
void vulnerable(void) {
char buf[16];
char str[12];
fgets(buf, 16, stdin);
printf(buf);
}
Computer Science 161
Write 100 to 0xdeadbeef
22
Recall: When printf sees a %n, it takes the next unused argument, treats it like an address, and writes the number of bytes printed so far to that address.
When printf sees the %n, two things need to be true:
... | | |||
RIP for vulnerable | | |||
SFP for vulnerable | | |||
buf | arg7 | |||
buf | arg6 | |||
buf | arg5 | |||
buf | arg4 | |||
str | arg3 | |||
str | arg2 | |||
str | arg1 | |||
buf (arg to printf) | arg0 | |||
RIP for printf | | |||
SFP for printf | | |||
[printf frame] | | |||
void vulnerable(void) {
char buf[16];
char str[12];
fgets(buf, 16, stdin);
printf(buf);
}
Computer Science 161
Write 100 to 0xdeadbeef
23
When printf sees the %n, two things need to be true:
Consider this exploit. What does it look like in memory?
... | | |||
RIP for vulnerable | | |||
SFP for vulnerable | | |||
buf | arg7 | |||
buf | arg6 | |||
buf | arg5 | |||
buf | arg4 | |||
str | arg3 | |||
str | arg2 | |||
str | arg1 | |||
buf (arg to printf) | arg0 | |||
RIP for printf | | |||
SFP for printf | | |||
[printf frame] | | |||
void vulnerable(void) {
char buf[16];
char str[12];
fgets(buf, 16, stdin);
printf(buf);
}
Input: | 0xdeadbeef | %94c | %c | %c | %n |
Computer Science 161
Write 100 to 0xdeadbeef
24
When writing to memory, the percent formatters take up multiple bytes of memory.
For example, %94c is 4 characters and takes up 4 bytes of memory.
void vulnerable(void) {
char buf[16];
char str[12];
fgets(buf, 16, stdin);
printf(buf);
}
... | | |||
RIP for vulnerable | | |||
SFP for vulnerable | | |||
(buf) %n | arg7 | |||
(buf) %c%c | arg6 | |||
(buf) %94c | arg5 | |||
(buf) 0xdeadbeef | arg4 | |||
str | arg3 | |||
str | arg2 | |||
str | arg1 | |||
buf (arg to printf) | arg0 | |||
RIP for printf | | |||
SFP for printf | | |||
[printf frame] | | |||
Input: | 0xdeadbeef | %94c | %c | %c | %n |
# chars used: | 4 | 4 | 2 | 2 | 2 |
Computer Science 161
Write 100 to 0xdeadbeef
25
Control where we write: The next unused argument on the stack should be 0xdeadbeef.
void vulnerable(void) {
char buf[16];
char str[12];
fgets(buf, 16, stdin);
printf(buf);
}
... | | |||
RIP for vulnerable | | |||
SFP for vulnerable | | |||
(buf) %n\0 | arg7 | |||
(buf) %c%c | arg6 | |||
(buf) %94c | arg5 | |||
(buf) 0xdeadbeef | arg4 | |||
str | arg3 | |||
str | arg2 | |||
str | arg1 | |||
buf (arg to printf) | arg0 | |||
RIP for printf | | |||
SFP for printf | | |||
[printf frame] | | |||
Input: | 0xdeadbeef | %94c | %c | %c | %n |
# chars used: | 4 | 4 | 2 | 2 | 2 |
Consumes: | N/A | arg1 | arg2 | arg3 | arg4 |
Computer Science 161
Write 100 to 0xdeadbeef
26
Control what we write: The number of bytes printed so far should be 100.
Input: | 0xdeadbeef | %94c | %c | %c | %n |
# chars used: | 4 | 4 | 2 | 2 | 2 |
Consumes: | N/A | arg1 | arg2 | arg3 | arg4 |
# bytes printed: | 4 | 94 | 1 | 1 | 0 |
void vulnerable(void) {
char buf[16];
char str[12];
fgets(buf, 16, stdin);
printf(buf);
}
... | | |||
RIP for vulnerable | | |||
SFP for vulnerable | | |||
(buf) %n\0 | arg7 | |||
(buf) %c%c | arg6 | |||
(buf) %94c | arg5 | |||
(buf) 0xdeadbeef | arg4 | |||
str | arg3 | |||
str | arg2 | |||
str | arg1 | |||
buf (arg to printf) | arg0 | |||
RIP for printf | | |||
SFP for printf | | |||
[printf frame] | | |||
Computer Science 161
Write 100 to 0xdeadbeef
27
How would you modify this exploit to write to address 0xbfff1234 instead of 0xdeadbeef?
How would you modify this exploit to write 89 to 0xbfff1234 instead of writing 100?
Input: | 0xdeadbeef | %94c | %c | %c | %n |
# chars used: | 4 | 4 | 2 | 2 | 2 |
Consumes: | N/A | arg1 | arg2 | arg3 | arg4 |
# bytes printed: | 4 | 94 | 1 | 1 | 0 |
void vulnerable(void) {
char buf[16];
char str[12];
fgets(buf, 16, stdin);
printf(buf);
}
... | | |||
RIP for vulnerable | | |||
SFP for vulnerable | | |||
(buf) %n\0 | arg7 | |||
(buf) %c%c | arg6 | |||
(buf) %94c | arg5 | |||
(buf) 0xdeadbeef | arg4 | |||
str | arg3 | |||
str | arg2 | |||
str | arg1 | |||
buf (arg to printf) | arg0 | |||
RIP for printf | | |||
SFP for printf | | |||
[printf frame] | | |||
Computer Science 161
Format String Vulnerabilities: Defense
28
void vulnerable(void) {
char buf[64];
if (fgets(buf, 64, stdin) == NULL)
return;
printf("%s", buf);
}
Never use untrusted input in the first argument to printf.
Now the attacker can't make the number of arguments mismatched!
Computer Science 161
Implications
29
Computer Science 161
Heap Vulnerabilities
30
Textbook Chapter 3.6
Computer Science 161
Targeting Instruction Pointers
31
Computer Science 161
C++ vtables
32
Computer Science 161
C++ vtables
33
x is an object of type ClassX.
y is an object of type ClassY.
... |
instance variable of y |
address of vtable of y |
... |
... |
instance variable of x |
instance variable of x |
address of vtable of x |
Heap
... |
address of method bar |
address of method foo |
... |
address of method bar |
address of method foo |
... |
method bar of ClassY |
... |
method foo of ClassY |
... |
... |
method bar of ClassX |
... |
method foo of ClassX |
... |
Code
ClassX vtable
ClassY vtable
Computer Science 161
C++ vtables
34
... |
instance variable of y |
address of vtable of y |
... |
... |
instance variable of x |
instance variable of x |
address of vtable of x |
Heap
... |
address of method bar |
address of method foo |
... |
address of method bar |
address of method foo |
... |
method bar of ClassY |
... |
method foo of ClassY |
... |
... |
method bar of ClassX |
... |
method foo of ClassX |
... |
Code
To call a method of y, first follow a pointer on the heap to find the vtable…
ClassX vtable
ClassY vtable
… then follow a pointer in the vtable to find the instructions of the method.
Computer Science 161
C++ vtables
35
Suppose one of the instance variables of x is a buffer we can overflow.
... |
instance variable of y |
address of vtable of y |
... |
... |
instance variable of x |
instance variable of x |
address of vtable of x |
Heap
... |
address of method bar |
address of method foo |
... |
address of method bar |
address of method foo |
... |
method bar of ClassY |
... |
method foo of ClassY |
... |
... |
method bar of ClassX |
... |
method foo of ClassX |
... |
Code
ClassX vtable
ClassY vtable
Computer Science 161
C++ vtables
36
The attacker controls everything above the instance variable of x on the heap, including the vtable pointer for y.
... |
instance variable of y |
address of vtable of y |
... |
... |
instance variable of x |
instance variable of x |
address of vtable of x |
Heap
... |
address of method bar |
address of method foo |
... |
address of method bar |
address of method foo |
... |
method bar of ClassY |
... |
method foo of ClassY |
... |
... |
method bar of ClassX |
... |
method foo of ClassX |
... |
Code
ClassX vtable
ClassY vtable
Computer Science 161
C++ vtables
37
... |
instance variable of y |
address of vtable of y |
address of SHELLCODE |
SHELLCODE |
instance variable of x |
instance variable of x |
address of vtable of x |
Heap
The vtable for y is now a pointer to shellcode. If method foo for y is called, it will execute shellcode!
Heap
... |
address of method bar |
address of method foo |
... |
address of method bar |
address of method foo |
... |
method bar of ClassY |
... |
method foo of ClassY |
... |
... |
method bar of ClassX |
... |
method foo of ClassX |
... |
Code
ClassX vtable
ClassY vtable
Computer Science 161
Heap Vulnerabilities
38
Computer Science 161
Top 10 Most Dangerous Software Weaknesses (2023)
39
Rank | ID | Name | Score |
[1] | Out-of-bounds Write | 63.72 | |
[2] | Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') | 45.54 | |
[3] | Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection') | 34.27 | |
[4] | Use After Free | 16.71 | |
[5] | Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection') | 15.65 | |
[6] | Improper Input Validation | 15.50 | |
[7] | Out-of-bounds Read | 14.60 | |
[8] | Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal') | 14.11 | |
[9] | Cross-Site Request Forgery (CSRF) | 11.73 | |
[10] | Unrestricted Upload of File with Dangerous Type | 10.41 |
Computer Science 161
Self-study Material
Textbook Chapter 3.5
Computer Science 161
Off-by-One Exploit
Textbook Chapter 3.5
Computer Science 161
Off-by-one
42
RIP m | |||
SFP m | |||
RIP v | |||
SFP v |
name
0xbfffcd64 | |||
0xbfffcd60 | |||
0xbfffcd5c | |||
0xbfffcd58 | |||
0xbfffcd54 | |||
0xbfffcd50 | |||
0xbfffcd4c | |||
0xbfffcd48 | |||
0xbfffcd44 | |||
0xbfffcd40 |
void vulnerable(void) {
char name[20];
fread(name,21,1,stdin);
}
int main(void) {
vulnerable();
return 0;
}
vulnerable:
...
call gets� add $4, %esp
mov %ebp, %esp
pop %ebp
ret
main:
...
call vulnerable
mov %ebp, %esp
pop %ebp
ret
EIP
EBP
ESP
... | ... | ... | ... |
... | ... | ... | ... |
... | ... | ... | ... |
... | ... | ... | ... |
... | ... | ... | ... |
RIP of main | |||
SFP of main | |||
RIP of vulnerable | |||
SFP of vulnerable | |||
name | |||
name | |||
name | |||
name | |||
name | |||
... | |||
Goal: Execute shellcode located at 0xdeadbeef. What parts of memory is an attacker able to overwrite in this piece of code?
size_t fread(void *restrict ptr, size_t size, size_t nitems,
FILE *restrict stream);
The function fread() reads nitems objects, each size bytes long, from the stream pointed to by stream, storing them at the location given by ptr.
Computer Science 161
Off-by-one
43
RIP m | |||
SFP m | |||
RIP v | |||
SFP v |
name
0xbfffcd64 | |||
0xbfffcd60 | |||
0xbfffcd5c | |||
0xbfffcd58 | |||
0xbfffcd54 | |||
0xbfffcd50 | |||
0xbfffcd4c | |||
0xbfffcd48 | |||
0xbfffcd44 | |||
0xbfffcd40 |
vulnerable:
...
call gets� add $4, %esp
mov %ebp, %esp
pop %ebp
ret
main:
...
call vulnerable
mov %ebp, %esp
pop %ebp
ret
EIP
EBP
ESP
... | ... | ... | ... |
... | ... | ... | ... |
... | ... | ... | ... |
... | ... | ... | ... |
... | ... | ... | ... |
RIP of main | |||
SFP of main | |||
RIP of vulnerable | |||
\x60 | \xcd | \xff | \xbf |
name | |||
name | |||
name | |||
name | |||
name | |||
... | |||
The attacker is able to overwrite all of name and the least-significant byte of the SFP of vulnerable.
If the attacker can change where the SFP of vulnerable points to, how can they use this to execute shellcode?
void vulnerable(void) {
char name[20];
fread(name,21,1,stdin);
}
int main(void) {
vulnerable();
return 0;
}
Computer Science 161
Off-by-one
44
RIP m | |||
SFP m | |||
RIP v | |||
SFP v |
name
0xbfffcd64 | |||
0xbfffcd60 | |||
0xbfffcd5c | |||
0xbfffcd58 | |||
0xbfffcd54 | |||
0xbfffcd50 | |||
0xbfffcd4c | |||
0xbfffcd48 | |||
0xbfffcd44 | |||
0xbfffcd40 |
vulnerable:
...
call gets� add $4, %esp
mov %ebp, %esp
pop %ebp
ret
main:
...
call vulnerable
mov %ebp, %esp
pop %ebp
ret
EIP
EBP
ESP
... | ... | ... | ... |
... | ... | ... | ... |
... | ... | ... | ... |
... | ... | ... | ... |
... | ... | ... | ... |
RIP of main | |||
SFP of main | |||
RIP of vulnerable | |||
\x44 | \xcd | \xff | \xbf |
name | |||
name | |||
name | |||
name | |||
name | |||
... | |||
The SFP of vulnerable now points inside name, which the attacker controls.
What does the SFP usually point to? What will the C program interpret the first bytes of name as?
void vulnerable(void) {
char name[20];
fread(name,21,1,stdin);
}
int main(void) {
vulnerable();
return 0;
}
Computer Science 161
Off-by-one
45
RIP m | |||
SFP m | |||
RIP v | |||
SFP v |
name
0xbfffcd64 | |||
0xbfffcd60 | |||
0xbfffcd5c | |||
0xbfffcd58 | |||
0xbfffcd54 | |||
0xbfffcd50 | |||
0xbfffcd4c | |||
0xbfffcd48 | |||
0xbfffcd44 | |||
0xbfffcd40 |
vulnerable:
...
call gets� add $4, %esp
mov %ebp, %esp
pop %ebp
ret
main:
...
call vulnerable
mov %ebp, %esp
pop %ebp
ret
EIP
EBP
ESP
... | ... | ... | ... |
... | ... | ... | ... |
... | ... | ... | ... |
... | ... | ... | ... |
... | ... | ... | ... |
RIP of main | |||
SFP of main | |||
RIP of vulnerable | |||
\x44 | \xcd | \xff | \xbf |
name | |||
name | |||
name | |||
name [Fake RIP of main] | |||
name [Fake SFP of main] | |||
... | |||
The C program now thinks that the SFP of main and the RIP of main are inside name.
The attacker controls these values, so the attacker can now overwrite where the program thinks the RIP of main is.
void vulnerable(void) {
char name[20];
fread(name,21,1,stdin);
}
int main(void) {
vulnerable();
return 0;
}
Computer Science 161
Off-by-one
46
RIP m | |||
SFP m | |||
RIP v | |||
SFP v |
name
0xbfffcd64 | |||
0xbfffcd60 | |||
0xbfffcd5c | |||
0xbfffcd58 | |||
0xbfffcd54 | |||
0xbfffcd50 | |||
0xbfffcd4c | |||
0xbfffcd48 | |||
0xbfffcd44 | |||
0xbfffcd40 |
vulnerable:
...
call fread� add $16, %esp
mov %ebp, %esp
pop %ebp
ret
main:
...
call vulnerable
mov %ebp, %esp
pop %ebp
ret
EBP
ESP
... | ... | ... | ... |
... | ... | ... | ... |
... | ... | ... | ... |
... | ... | ... | ... |
... | ... | ... | ... |
RIP of main | |||
SFP of main | |||
RIP of vulnerable | |||
\x44 | \xcd | \xff | \xbf |
AAAA | |||
AAAA | |||
AAAA | |||
0xdeadbeef [Fake RIP m] | |||
AAAA [Fake SFP m] | |||
... | |||
Let’s see what happens when the vulnerable function returns.
EIP
void vulnerable(void) {
char name[20];
fread(name,21,1,stdin);
}
int main(void) {
vulnerable();
return 0;
}
Computer Science 161
Off-by-one
47
RIP m | |||
SFP m | |||
RIP v | |||
SFP v |
name
0xbfffcd64 | |||
0xbfffcd60 | |||
0xbfffcd5c | |||
0xbfffcd58 | |||
0xbfffcd54 | |||
0xbfffcd50 | |||
0xbfffcd4c | |||
0xbfffcd48 | |||
0xbfffcd44 | |||
0xbfffcd40 |
vulnerable:
...
call fread� add $16, %esp
mov %ebp, %esp
pop %ebp
ret
main:
...
call vulnerable
mov %ebp, %esp
pop %ebp
ret
EIP
EBP
ESP
... | ... | ... | ... |
... | ... | ... | ... |
... | ... | ... | ... |
... | ... | ... | ... |
... | ... | ... | ... |
RIP of main | |||
SFP of main | |||
RIP of vulnerable | |||
\x44 | \xcd | \xff | \xbf |
AAAA | |||
AAAA | |||
AAAA | |||
0xdeadbeef [Fake RIP m] | |||
AAAA [Fake SFP m] | |||
... | |||
Let’s see what happens when the vulnerable function returns.
Returned from fread, preparing to return from vulnerable.
void vulnerable(void) {
char name[20];
fread(name,21,1,stdin);
}
int main(void) {
vulnerable();
return 0;
}
Computer Science 161
Off-by-one
48
RIP m | |||
SFP m | |||
RIP v | |||
SFP v |
name
0xbfffcd64 | |||
0xbfffcd60 | |||
0xbfffcd5c | |||
0xbfffcd58 | |||
0xbfffcd54 | |||
0xbfffcd50 | |||
0xbfffcd4c | |||
0xbfffcd48 | |||
0xbfffcd44 | |||
0xbfffcd40 |
vulnerable:
...
call fread� add $16, %esp
mov %ebp, %esp
pop %ebp
ret
main:
...
call vulnerable
mov %ebp, %esp
pop %ebp
ret
EIP
EBP
ESP
... | ... | ... | ... |
... | ... | ... | ... |
... | ... | ... | ... |
... | ... | ... | ... |
... | ... | ... | ... |
RIP of main | |||
SFP of main | |||
RIP of vulnerable | |||
\x44 | \xcd | \xff | \xbf |
AAAA | |||
AAAA | |||
AAAA | |||
0xdeadbeef [Fake RIP m] | |||
AAAA [Fake SFP m] | |||
... | |||
Let’s see what happens when the vulnerable function returns.
Epilogue step 1: Move ESP back up.
void vulnerable(void) {
char name[20];
fread(name,21,1,stdin);
}
int main(void) {
vulnerable();
return 0;
}
Computer Science 161
Off-by-one
49
RIP m | |||
SFP m | |||
RIP v | |||
SFP v |
name
0xbfffcd64 | |||
0xbfffcd60 | |||
0xbfffcd5c | |||
0xbfffcd58 | |||
0xbfffcd54 | |||
0xbfffcd50 | |||
0xbfffcd4c | |||
0xbfffcd48 | |||
0xbfffcd44 | |||
0xbfffcd40 |
vulnerable:
...
call fread� add $16, %esp
mov %ebp, %esp
pop %ebp
ret
main:
...
call vulnerable
mov %ebp, %esp
pop %ebp
ret
EIP
... | ... | ... | ... |
... | ... | ... | ... |
... | ... | ... | ... |
... | ... | ... | ... |
... | ... | ... | ... |
RIP of main | |||
SFP of main | |||
RIP of vulnerable | |||
\x44 | \xcd | \xff | \xbf |
AAAA | |||
AAAA | |||
AAAA | |||
0xdeadbeef [Fake RIP m] | |||
AAAA [Fake SFP m] | |||
... | |||
Let’s see what happens when the vulnerable function returns.
Epilogue step 2: Restore EBP. Note that EBP now points inside name, instead of at the SFP of main.
ESP
EBP
void vulnerable(void) {
char name[20];
fread(name,21,1,stdin);
}
int main(void) {
vulnerable();
return 0;
}
Computer Science 161
Off-by-one
50
RIP m | |||
SFP m | |||
RIP v | |||
SFP v |
name
0xbfffcd64 | |||
0xbfffcd60 | |||
0xbfffcd5c | |||
0xbfffcd58 | |||
0xbfffcd54 | |||
0xbfffcd50 | |||
0xbfffcd4c | |||
0xbfffcd48 | |||
0xbfffcd44 | |||
0xbfffcd40 |
vulnerable:
...
call fread� add $16, %esp
mov %ebp, %esp
pop %ebp
ret
main:
...
call vulnerable
mov %ebp, %esp
pop %ebp
ret
EIP
... | ... | ... | ... |
... | ... | ... | ... |
... | ... | ... | ... |
... | ... | ... | ... |
... | ... | ... | ... |
RIP of main | |||
SFP of main | |||
RIP of vulnerable | |||
\x44 | \xcd | \xff | \xbf |
AAAA | |||
AAAA | |||
AAAA | |||
0xdeadbeef [Fake RIP m] | |||
AAAA [Fake SFP m] | |||
... | |||
Let’s see what happens when the vulnerable function returns.
Epilogue step 3: Restore EIP. We never changed the RIP of vulnerable, so execution returns to main as normal.
ESP
EBP
void vulnerable(void) {
char name[20];
fread(name,21,1,stdin);
}
int main(void) {
vulnerable();
return 0;
}
Computer Science 161
Off-by-one
51
RIP m | |||
SFP m | |||
RIP v | |||
SFP v |
name
0xbfffcd64 | |||
0xbfffcd60 | |||
0xbfffcd5c | |||
0xbfffcd58 | |||
0xbfffcd54 | |||
0xbfffcd50 | |||
0xbfffcd4c | |||
0xbfffcd48 | |||
0xbfffcd44 | |||
0xbfffcd40 |
vulnerable:
...
call fread� add $16, %esp
mov %ebp, %esp
pop %ebp
ret
main:
...
call vulnerable
mov %ebp, %esp
pop %ebp
ret
EIP
... | ... | ... | ... |
... | ... | ... | ... |
... | ... | ... | ... |
... | ... | ... | ... |
... | ... | ... | ... |
RIP of main | |||
SFP of main | |||
RIP of vulnerable | |||
\x44 | \xcd | \xff | \xbf |
AAAA | |||
AAAA | |||
AAAA | |||
0xdeadbeef [Fake RIP m] | |||
AAAA [Fake SFP m] | |||
... | |||
Let’s see what happens when the main function returns, now with the EBP in the wrong place.
Epilogue step 1: Move ESP back up.
ESP
EBP
void vulnerable(void) {
char name[20];
fread(name,21,1,stdin);
}
int main(void) {
vulnerable();
return 0;
}
Computer Science 161
Off-by-one
52
RIP m | |||
SFP m | |||
RIP v | |||
SFP v |
name
0xbfffcd64 | |||
0xbfffcd60 | |||
0xbfffcd5c | |||
0xbfffcd58 | |||
0xbfffcd54 | |||
0xbfffcd50 | |||
0xbfffcd4c | |||
0xbfffcd48 | |||
0xbfffcd44 | |||
0xbfffcd40 |
vulnerable:
...
call fread� add $16, %esp
mov %ebp, %esp
pop %ebp
ret
main:
...
call vulnerable
mov %ebp, %esp
pop %ebp
ret
EIP
... | ... | ... | ... |
... | ... | ... | ... |
... | ... | ... | ... |
... | ... | ... | ... |
... | ... | ... | ... |
RIP of main | |||
SFP of main | |||
RIP of vulnerable | |||
\x44 | \xcd | \xff | \xbf |
AAAA | |||
AAAA | |||
AAAA | |||
0xdeadbeef [Fake RIP m] | |||
AAAA [Fake SFP m] | |||
... | |||
Let’s see what happens when the main function returns, now with the EBP in the wrong place.
Epilogue step 2: Restore EBP. The program looks at our fake SFP to restore EBP, and points EBP to garbage AAAA.
EBP
ESP
void vulnerable(void) {
char name[20];
fread(name,21,1,stdin);
}
int main(void) {
vulnerable();
return 0;
}
Computer Science 161
Off-by-one
53
RIP m | |||
SFP m | |||
RIP v | |||
SFP v |
name
0xbfffcd64 | |||
0xbfffcd60 | |||
0xbfffcd5c | |||
0xbfffcd58 | |||
0xbfffcd54 | |||
0xbfffcd50 | |||
0xbfffcd4c | |||
0xbfffcd48 | |||
0xbfffcd44 | |||
0xbfffcd40 |
vulnerable:
...
call fread� add $16, %esp
mov %ebp, %esp
pop %ebp
ret
main:
...
call vulnerable
mov %ebp, %esp
pop %ebp
ret
EIP
... | ... | ... | ... |
... | ... | ... | ... |
... | ... | ... | ... |
... | ... | ... | ... |
... | ... | ... | ... |
RIP of main | |||
SFP of main | |||
RIP of vulnerable | |||
\x44 | \xcd | \xff | \xbf |
AAAA | |||
AAAA | |||
AAAA | |||
0xdeadbeef [Fake RIP m] | |||
AAAA [Fake SFP m] | |||
... | |||
Let’s see what happens when the main function returns, now with the EBP in the wrong place.
Epilogue step 3: Restore EIP. The program looks at our fake RIP to restore EIP, and redirects execution to 0xdeadbeef.
EBP
ESP
sh # _
void vulnerable(void) {
char name[20];
fread(name,21,1,stdin);
}
int main(void) {
vulnerable();
return 0;
}
Computer Science 161
Writing Robust Exploits
54
Computer Science 161
NOP Sleds
55
nop�nop�nop�nop�nop�nop�nop�nop�nop�nop�nop�nop�nop�nop�xor %eax, %eax�push %eax�push $0x68732f2f�push $0x6e69622f�mov %esp, %ebx�mov %eax, %ecx�mov %eax, %edx�mov $0xb, %al�int $0x80
Computer Science 161
Summary: Memory Safety Vulnerabilities
56
Computer Science 161