2_MemoryCorruption
2_MemoryCorruption
Memory corruption
Erik Poll
Digital Security
Radboud University Nijmegen
1
Security in the development lifecycle
2.1
week 3: exercise week4: group project
Security
Static in
analysis the developmentfuzzing
lifecycle
afl
PREfast memory sanitizers Asan, Msan
Memory Corruption
2.2
week 3: exercise week4: group project
Security
Static in
analysis the developmentfuzzing
lifecycle
afl
PREfast memory sanitizers Asan, Msan
Memory Corruption
2.3
Overview (next 2 weeks)
1. How do memory corruption flaws work?
2. What can be the impact?
3. How can we spot such problems in C(++) code?
Next weeks: tool-support for this
• SAST: PREfast individual project
• DAST: Fuzzing group project
4. What can ‘the platform’ do about it?
ie. the compiler, system libraries, hardware, OS, ..
5. What can the programmer do about it?
3
Reading material
• SoK article: ‘Eternal War in Memory’ S&P 2013
– Excl. Section VII.
– This article is quite dense. You are not expected to be able to
reproduce or remember all the discussion here. It’s good
enough if you can follow the article, with a steady supply of
coffee while googling if the terminology is not clear.
4
Essence of the problem
Suppose in a C program you have an array of length 4
char buffer[4];
What happens if the statement below is executed?
buffer[4] = 'a';
5.1
Essence of the problem
Suppose in a C program you have an array of length 4
char buffer[4];
What happens if the statement below is executed?
buffer[4] = 'a';
We don’t know!
This is defined to be
5.2
undefined behaviour: anything can happen
6
undefined behaviour: anything can happen
7
undefined behaviour: anything can happen
Suppose in a C program you have an array of length 4
char buffer[4];
What happens if the statement below is executed?
buffer[4] = 'a';
8
undefined behaviour: anything can happen
Suppose in a C program you have an array of length 4
char buffer[4];
What happens if the statement below is executed?
buffer[4] = 'a';
9
Solution to this problem
10.1
Solution to this problem
• Check array bounds at runtime
– Algol 60 proposed this back in 1960!
10.2
Solution to this problem
• Check array bounds at runtime
– Algol 60 proposed this back in 1960!
10.3
Solution to this problem
• Check array bounds at runtime
– Algol 60 proposed this back in 1960!
10.4
Solution to this problem
• Check array bounds at runtime
– Algol 60 proposed this back in 1960!
10.5
Tony Hoare on design principles of ALGOL 60
12
More memory corruption problems
Errors with pointers and with dynamic memory (the heap)
13.1
More memory corruption problems
Errors with pointers and with dynamic memory (the heap)
13.2
More memory corruption problems
Errors with pointers and with dynamic memory (the heap)
13.3
More memory corruption problems
Errors with pointers and with dynamic memory (the heap)
13.4
More memory corruption problems
Errors with pointers and with dynamic memory (the heap)
13.5
More memory corruption problems
Errors with pointers and with dynamic memory (the heap)
13.6
Memory corruption problems
Typical causes
• access outside array bounds
• buggy pointer arithmetic
• dereferencing null pointer
• using a dangling pointer or stale pointer, caused by
• use-after-free
• double-free
• forgetting to check for failures in allocation
• forgetting to de-allocate, aka memory leaks
• not a memory corruption issue,
but a memory availability issue
14
Spot all (potential) defects
1000 …
1001 void f (){
1002 char* buf, buf1;
1003 buf = malloc(100);
1004 buf[0] = ’a’;
...
2001 free(buf1);
2002 buf[0] = ’b’;
...
3001 free(buf);
3002 buf[0] = ’c’;
3003 buf1 = malloc(100);
3004 buf[0] = ’d’
3005 }
15.1
Spot all (potential) defects
1000 …
1001 void f (){
1002 char* buf, buf1;
1003 buf = malloc(100);
1004 buf[0] = ’a’;
...
2001 free(buf1);
2002 buf[0] = ’b’;
use-after-free; buf[0] points
... to de-allocated memory
3001 free(buf);
3002 buf[0] = ’c’;
3003 buf1 = malloc(100);
3004 buf[0] = ’d’
3005 }
15.2
Spot all (potential) defects
1000 …
1001 void f (){
1002 char* buf, buf1;
1003 buf = malloc(100);
1004 buf[0] = ’a’;
...
2001 free(buf1);
2002 buf[0] = ’b’;
use-after-free; buf[0] points
... to de-allocated memory
3001 free(buf);
3002 buf[0] = ’c’;
3003 buf1 = malloc(100);
3004 buf[0] = ’d’
3005 } use-after-free, but now buf[0]
might point to memory that
has now been re-allocated
15.3
Spot all (potential) defects
1000 …
1001 void f (){
possible null dereference
1002 char* buf, buf1; (if malloc failed)
1003 buf = malloc(100);
1004 buf[0] = ’a’;
...
2001 free(buf1);
2002 buf[0] = ’b’;
use-after-free; buf[0] points
... to de-allocated memory
3001 free(buf);
3002 buf[0] = ’c’;
3003 buf1 = malloc(100);
3004 buf[0] = ’d’
3005 } use-after-free, but now buf[0]
might point to memory that
has now been re-allocated
15.4
Spot all (potential) defects
1000 …
1001 void f (){
possible null dereference
1002 char* buf, buf1; (if malloc failed)
1003 buf = malloc(100);
1004 buf[0] = ’a’;
... potential use-after-free
if buf & buf1 are aliased
2001 free(buf1);
2002 buf[0] = ’b’;
use-after-free; buf[0] points
... to de-allocated memory
3001 free(buf);
3002 buf[0] = ’c’;
3003 buf1 = malloc(100);
3004 buf[0] = ’d’
3005 } use-after-free, but now buf[0]
might point to memory that
has now been re-allocated
15.5
Spot all (potential) defects
1000 …
1001 void f (){
possible null dereference
1002 char* buf, buf1; (if malloc failed)
1003 buf = malloc(100);
1004 buf[0] = ’a’;
... potential use-after-free
if buf & buf1 are aliased
2001 free(buf1);
2002 buf[0] = ’b’;
use-after-free; buf[0] points
... to de-allocated memory
3001 free(buf);
memory leak; pointer buf1
3002 buf[0] = ’c’;
to this memory is lost &
3003 buf1 = malloc(100); memory is never freed
3004 buf[0] = ’d’
3005 } use-after-free, but now buf[0]
might point to memory that
has now been re-allocated
15.6
How does classic buffer overflow work?
aka smashing the stack
16
Process memory layout
Unused Memory
Low
addresses
Program Code .text
17
Stack layout
The stack consists of Activation Records:
AR main()
AR main()
AR f()
x
AR main() return address
AR f() buf[4..7]
buf[0..3]
x
AR main() return address
AR f() buf[4..7]
buf[0..3]
x
AR main() return address
AR f() buf[4..7]
buf[0..3]
x
AR main() return address
AR f() buf[4..7]
buf[0..3]
void f(int x) {
char[8] buf;
gets(buf);
}
void main() {
f(…); …
}
void format_hard_disk(){…}
19.1
Stack overflow attack - case 1
What if gets() reads more than 8 bytes ?
Attacker can jump to arbitrary point in the code!
x
AR main() return address
AR f() buf[4..7]
buf[0..3]
void f(int x) {
char[8] buf;
gets(buf);
}
void main() {
f(…); …
}
void format_hard_disk(){…}
19.2
Stack overflow attack - case 2
What if gets() reads more than 8 bytes ?
Attacker can jump to his own code (aka shell code)
x
AR main() return address
AR f() /bin/sh
exec
void f(int x) {
char[8] buf;
gets(buf);
}
void main() {
f(…); …
}
void format_hard_disk(){…}
20
Stack overflow attack - case 2
What if gets() reads more than 8 bytes ?
Attacker can jump to his own code (aka shell code)
x
AR main() return address
AR f() /bin/sh
exec
void
never
f(int x) {
use gets!
char[8] buf;
gets(buf);
}
gets has been removed from
the{ C standard in 2011
void main()
f(…); …
}
void format_hard_disk(){…}
21
Code injection vs code reuse
void f(void(*error_handler)(int),...) {
int diskquota = 200;
bool is_super_user = false;
char* filename = "/tmp/scratchpad";
char[8] username;
int j = 12;
...
}
23.1
What to attack? More fun on the stack
void f(void(*error_handler)(int),...) {
int diskquota = 200;
bool is_super_user = false;
char* filename = "/tmp/scratchpad";
char[8] username;
int j = 12;
...
}
23.2
What to attack? Fun on the heap
struct BankAccount {
int number;
char username[20];
int balance;
}
24.1
What to attack? Fun on the heap
struct BankAccount {
int number;
char username[20];
int balance;
}
24.2
Spotting the problem
Reminder: C chars & strings
• A char in C is always exactly one byte
• A string is a sequence of chars terminated by a NULL byte
• String variables are pointers of type char*
str strlen(str) = 5
h e l l o \0
26
Example: gets
char buf[20];
gets(buf); // read user input until
// first EoL or EoF character
27
Example: strcpy
char dest[20];
strcpy(dest, src); // copies string src to dest
28.1
Example: strcpy
char dest[20];
strcpy(dest, src); // copies string src to dest
28.2
Spot the defect!
char buf[20];
char prefix[] = "http://";
char* path;
...
strcpy(buf, prefix);
// copies the string prefix to buf
strncat(buf, path, sizeof(buf));
// concatenates path to the string buf
29
Spot the defect! (1)
char buf[20];
char prefix[] = "http://";
char* path;
...
strcpy(buf, prefix);
// copies the string prefix to buf
strncat(buf, path, sizeof(buf));
// concatenates path to the string buf
30.1
Spot the defect! (1)
char buf[20];
char prefix[] = "http://";
char* path;
...
strcpy(buf, prefix);
// copies the string prefix to buf
strncat(buf, path, sizeof(buf));
// concatenates path to the string buf
30.2
Spot the defect! (2)
char src[9];
char dest[9];
31
Spot the defect! (2)
char src[9]; base_url is 10 chars long, incl.
char dest[9]; its null terminator, so src will not
be null-terminated
char* base_url = "www.ru.nl";
strncpy(src, base_url, 9);
// copies base_url to src
strcpy(dest, src);
// copies src to dest
32
Spot the defect! (2)
char src[9]; base_url is 10 chars long, incl.
char dest[9]; its null terminator, so src is now
not null-terminated
char* base_url = ”www.ru.nl”;
strncpy(src, base_url, 9);
// copies base_url to src
strcpy(dest, src);
// copies src to dest
33
Example: strcpy and strncpy
Don’t replace
strcpy(dest, src)
with
strncpy(dest, src, sizeof(dest))
but with
strncpy(dest, src, sizeof(dest)-1)
dst[sizeof(dest)-1] = '\0';
if dest should be null-terminated!
34
Spot the defect! (3)
char *buf;
int len;
...
35
Spot the defect! (3)
char *buf;
int len;
...
(At the exam, you’re not expected to remember that read treats
its 3rd argument as an unsigned int)
36
Spot the defect! (3)
char *buf;
int len;
...
if (len < 0)
{error ("negative length"); return; }
buf = malloc(MAX(len,1024));
read(fd,buf,len);
37
Spot the defect! (3)
char *buf;
int len;
...
if (len < 0)
{error ("negative length"); return; }
buf = malloc(MAX(len,1024));
read(fd,buf,len);
38.1
Spot the defect! (3)
char *buf;
What if the malloc() fails,
int len;
because we ran out of memory ?
...
if (len < 0)
{error ("negative length"); return; }
buf = malloc(MAX(len,1024));
read(fd,buf,len);
38.2
Spot the defect! (3)
char *buf;
int len;
...
if (len < 0)
{error ("negative length"); return; }
buf = malloc(MAX(len,1024));
if (buf==NULL) { exit(-1);}
// or something a bit more graceful
read(fd,buf,len);
39
Better still
char *buf;
int len;
...
if (len < 0)
{error ("negative length"); return; }
buf = calloc(MAX(len,1024));
//to initialise allocate memory to 0
if (buf==NULL) { exit(-1);}
// or something a bit more graceful
read(fd,buf,len);
40
Spot the defect!
#define MAX_BUF 256
len = strlen(in);
41
Spot the defect!
#define MAX_BUF 256
len = strlen(in);
42.1
Spot the defect!
#define MAX_BUF 256
len = strlen(in);
42.2
Spot the defect!
#define MAX_BUF 256
42.3
Spot the defect!
#define MAX_BUF 256
42.4
Spot the defect!
#define MAX_BUF 256
See https://cve.mitre.org/cgi-bin/cvekey.cgi?keyword=integer+overflow
42.5
Spot the defect!
bool CopyStructs(InputFile* f, long count)
{ structs = new Structs[count];
for (long i = 0; i < count; i++)
{ if !(ReadFromFile(f,&structs[i])))
break;
}
}
43.1
Spot the defect!
bool CopyStructs(InputFile* f, long count)
{ structs = new Structs[count];
for (long i = 0; i < count; i++)
{ if !(ReadFromFile(f,&structs[i])))
break;
}
}
effectively does a
malloc(count*sizeof(type))
which may cause integer overflow
43.2
Spot the defect!
bool CopyStructs(InputFile* f, long count)
{ structs = new Structs[count];
for (long i = 0; i < count; i++)
{ if !(ReadFromFile(f,&structs[i])))
break;
}
}
effectively does a
malloc(count*sizeof(type))
which may cause integer overflow
43.3
NB absence of language-level security
44
Spot the defect!
1. void* f(int start)
2. if (start+100 < start) return SOME_ERROR;
3. // checks for overflow
4. for (int i=start; i < start+100; i++) {
5. . . . // i will not overflow
6. } }
45.1
Spot the defect!
1. void* f(int start)
2. if (start+100 < start) return SOME_ERROR;
3. // checks for overflow
4. for (int i=start; i < start+100; i++) {
5. . . . // i will not overflow
6. } }
Integer overflow is undefined behaviour! This means
45.2
Spot the defect!
1. void* f(int start)
2. if (start+100 < start) return SOME_ERROR;
3. // checks for overflow
4. for (int i=start; i < start+100; i++) {
5. . . . // i will not overflow
6. } }
Integer overflow is undefined behaviour! This means
• You cannot assume that overflow produces a negative number;
so line 2 is not a good check for integer overflow.
45.3
Spot the defect!
1. void* f(int start)
2. if (start+100 < start) return SOME_ERROR;
3. // checks for overflow
4. for (int i=start; i < start+100; i++) {
5. . . . // i will not overflow
6. } }
Integer overflow is undefined behaviour! This means
• You cannot assume that overflow produces a negative number;
so line 2 is not a good check for integer overflow.
• Worse still, if integer overflow occurs, behaviour is undefined, and
ANY compilation is ok
45.4
Spot the defect!
1. void* f(int start)
2. if (start+100 < start) return SOME_ERROR;
3. // checks for overflow
4. for (int i=start; i < start+100; i++) {
5. . . . // i will not overflow
6. } }
Integer overflow is undefined behaviour! This means
• You cannot assume that overflow produces a negative number;
so line 2 is not a good check for integer overflow.
• Worse still, if integer overflow occurs, behaviour is undefined, and
ANY compilation is ok
• So compiled code can do anything if start+100 overflows
45.5
Spot the defect!
1. void* f(int start)
2. if (start+100 < start) return SOME_ERROR;
3. // checks for overflow
4. for (int i=start; i < start+100; i++) {
5. . . . // i will not overflow
6. } }
Integer overflow is undefined behaviour! This means
• You cannot assume that overflow produces a negative number;
so line 2 is not a good check for integer overflow.
• Worse still, if integer overflow occurs, behaviour is undefined, and
ANY compilation is ok
• So compiled code can do anything if start+100 overflows
• So compiled code can do nothing if start+100 overflows
45.6
Spot the defect!
1. void* f(int start)
2. if (start+100 < start) return SOME_ERROR;
3. // checks for overflow
4. for (int i=start; i < start+100; i++) {
5. . . . // i will not overflow
6. } }
Integer overflow is undefined behaviour! This means
• You cannot assume that overflow produces a negative number;
so line 2 is not a good check for integer overflow.
• Worse still, if integer overflow occurs, behaviour is undefined, and
ANY compilation is ok
• So compiled code can do anything if start+100 overflows
• So compiled code can do nothing if start+100 overflows
• This means the compiler may remove line 2
45.7
Spot the defect!
1. void* f(int start)
2. if (start+100 < start) return SOME_ERROR;
3. // checks for overflow
4. for (int i=start; i < start+100; i++) {
5. . . . // i will not overflow
6. } }
Integer overflow is undefined behaviour! This means
• You cannot assume that overflow produces a negative number;
so line 2 is not a good check for integer overflow.
• Worse still, if integer overflow occurs, behaviour is undefined, and
ANY compilation is ok
• So compiled code can do anything if start+100 overflows
• So compiled code can do nothing if start+100 overflows
• This means the compiler may remove line 2
Modern C compilers are clever enough to know x+100 < x is
always false, and optimise code accordingly
45.8
Spot the defect! (code from Linux kernel)
1. unsigned int tun_chr_poll( struct file *file,
2. poll_table *wait)
3. { ...
4. struct sock *sk = tun->sk; // take sk field of tun
5. if (!tun) return POLLERR; // return if tun is NULL
6. ...
7. }
46.1
Spot the defect! (code from Linux kernel)
1. unsigned int tun_chr_poll( struct file *file,
2. poll_table *wait)
3. { ...
4. struct sock *sk = tun->sk; // take sk field of tun
5. if (!tun) return POLLERR; // return if tun is NULL
6. ...
7. }
If tun is a null pointer, then tun->sk is undefined
46.2
Spot the defect! (code from Linux kernel)
1. unsigned int tun_chr_poll( struct file *file,
2. poll_table *wait)
3. { ...
4. struct sock *sk = tun->sk; // take sk field of tun
5. if (!tun) return POLLERR; // return if tun is NULL
6. ...
7. }
If tun is a null pointer, then tun->sk is undefined
What this function does if tun is null is undefined:
ANYTHING may happen then.
46.3
Spot the defect! (code from Linux kernel)
1. unsigned int tun_chr_poll( struct file *file,
2. poll_table *wait)
3. { ...
4. struct sock *sk = tun->sk; // take sk field of tun
5. if (!tun) return POLLERR; // return if tun is NULL
6. ...
7. }
If tun is a null pointer, then tun->sk is undefined
What this function does if tun is null is undefined:
ANYTHING may happen then.
So compiler can remove line 5, as the behaviour when tun is NULL
is undefined anyway, so this check is 'redundant'.
46.4
Spot the defect! (code from Linux kernel)
1. unsigned int tun_chr_poll( struct file *file,
2. poll_table *wait)
3. { ...
4. struct sock *sk = tun->sk; // take sk field of tun
5. if (!tun) return POLLERR; // return if tun is NULL
6. ...
7. }
If tun is a null pointer, then tun->sk is undefined
What this function does if tun is null is undefined:
ANYTHING may happen then.
So compiler can remove line 5, as the behaviour when tun is NULL
is undefined anyway, so this check is 'redundant'.
Standard compilers (gcc, clang) do this 'optimalisation' !
This is actually code from the Linux kernel, and removing line 5 led
to a security vulnerability [CVE-2009-1897]
46.5
Spot the defect! (code from Windows kernel)
// TCHAR is 1 byte ASCII or multiple byte UNICODE
#ifdef UNICODE
# define TCHAR wchar_t
# define _sntprintf _snwprintf
#else
# define TCHAR char
# define _sntprintf _snprintf
#endif
TCHAR buf[MAX_SIZE];
_sntprintf(buf, sizeof(buf), input);
48.1
Spot the defect!
#include <stdio.h>
48.2
Format string attacks
New type of memory corruption discovered in 2000
49
Format string attacks
Suppose attacker can feed malicious input string s to
printf(s). This can
50.1
Format string attacks
Suppose attacker can feed malicious input string s to
printf(s). This can
• read the stack
%x reads and prints bytes from stack so the input
%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x
%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x
%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x...
dumps the stack ,including passwords, keys,… stored on
the stack
50.2
Format string attacks
Suppose attacker can feed malicious input string s to
printf(s). This can
• read the stack
%x reads and prints bytes from stack so the input
%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x
%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x
%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x...
dumps the stack ,including passwords, keys,… stored on
the stack
• corrupt the stack
%n writes the number of characters printed to the stack,
so input 12345678%n writes value 8 to the stack
50.3
Format string attacks
Suppose attacker can feed malicious input string s to
printf(s). This can
• read the stack
%x reads and prints bytes from stack so the input
%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x
%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x
%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x...
dumps the stack ,including passwords, keys,… stored on
the stack
• corrupt the stack
%n writes the number of characters printed to the stack,
so input 12345678%n writes value 8 to the stack
• read arbitrary memory
a carefully crafted format string of the form
\xEF\xCD\xCD\xAB %x%x...%x%s
print the string at memory address ABCDCDEF
50.4
-Wformat-overflow
Eg gcc has (far too many?) command line options for this:
-Wformat –Wformat-no-literal –Wformat-security ...
Check https://cve.mitre.org/cgi-bin/cvekey.cgi?keyword=format+string
to see how depressingly common format strings still are
51.1
-Wformat-overflow
Eg gcc has (far too many?) command line options for this:
-Wformat –Wformat-no-literal –Wformat-security ...
Check https://cve.mitre.org/cgi-bin/cvekey.cgi?keyword=format+string
to see how depressingly common format strings still are
51.2
Recap: buffer overflows
• Buffer overflow is #1 weakness in C and C++ programs
– because these language are not memory-safe
• Tricky to spot
• Typical cause: programming with arrays, pointers, and
strings
– esp. library functions for null-terminated strings
• Related attacks
• Format string attack: another way of corrupting stack
• Integer overflows: often a stepping stone to getting a
buffer to overflows
• just the integer overflow can already have a security
impact, eg think of banking software
52
Platform-level defences
Platform-level defences
54
Platform-level defenses
1. Stack canaries
now standard
2. Non-executable memory (NX, WX) on many
platforms
3. Address space layout randomization (ASLR)
55
1. Stack canaries
• A dummy value - stack canary or cookie - is written on the stack
in front of the return address and checked when function returns
• A careless stack overflow will overwrite the canary, which can
then be detected
56
Stack canaries
x
x return address
return address canary value
buf[4..7] buf[4..7]
buf[0..3] buf[0..3]
57
Further improvements
• More variation in canary values: eg not a fixed values hardcoded
in binary but a random values chosen for each execution
• Better still, XOR the return address into the canary value
• Include a null byte in the canary value, because C string
functions cannot write nulls inside strings
58.1
Further improvements
• More variation in canary values: eg not a fixed values hardcoded
in binary but a random values chosen for each execution
• Better still, XOR the return address into the canary value
• Include a null byte in the canary value, because C string
functions cannot write nulls inside strings
return return
eg changing to
canary value canary value
char* ptr char* ptr
buf[4..7] buf[4..7]
buf[0..3] buf[0..3]
58.2
Further improvements
• Re-order elements on the stack to reduce the potential impact of
overruns
• swapping parameters buf and fp on stack changes whether
overrunning buf can corrupt fp
• which is especially dangerous if fp is a function pointer
• hence it is safer to allocated array buffers ‘above’ all other
local variables
First introduced by IBM’s ProPolice.
60
Windows 2003 Stack Protection
Nice example of the ways in which things can go wrong...
• Enabled with /GS command line option in Visual Studio
• When canary is corrupted, control is transferred to an exception
handler
61.1
Windows 2003 Stack Protection
Nice example of the ways in which things can go wrong...
• Enabled with /GS command line option in Visual Studio
• When canary is corrupted, control is transferred to an exception
handler
• Exception handler information is stored ...
61.2
Windows 2003 Stack Protection
Nice example of the ways in which things can go wrong...
• Enabled with /GS command line option in Visual Studio
• When canary is corrupted, control is transferred to an exception
handler
• Exception handler information is stored ...
on the stack!
61.3
Windows 2003 Stack Protection
Nice example of the ways in which things can go wrong...
• Enabled with /GS command line option in Visual Studio
• When canary is corrupted, control is transferred to an exception
handler
• Exception handler information is stored ...
on the stack!
• Attacker can corrupt the exception handler info on the stack, in
the process corrupt the canaries, and then let Stack Protection
mechanism transfer control to a malicious exception handler
61.4
Windows 2003 Stack Protection
Nice example of the ways in which things can go wrong...
• Enabled with /GS command line option in Visual Studio
• When canary is corrupted, control is transferred to an exception
handler
• Exception handler information is stored ...
on the stack!
• Attacker can corrupt the exception handler info on the stack, in
the process corrupt the canaries, and then let Stack Protection
mechanism transfer control to a malicious exception handler
[http://www.securityfocus.com/bid/8522/info]
61.5
Windows 2003 Stack Protection
Nice example of the ways in which things can go wrong...
• Enabled with /GS command line option in Visual Studio
• When canary is corrupted, control is transferred to an exception
handler
• Exception handler information is stored ...
on the stack!
• Attacker can corrupt the exception handler info on the stack, in
the process corrupt the canaries, and then let Stack Protection
mechanism transfer control to a malicious exception handler
[http://www.securityfocus.com/bid/8522/info]
• Countermeasure: only allow transfer of control to registered
exception handlers
61.6
2. ASLR (Address Space Layout Randomisation)
62.1
2. ASLR (Address Space Layout Randomisation)
• Attacker needs detailed info about memory layout
– eg to jump to specific piece of code
– or to corrupt a pointer at known position on the stack
62.2
2. ASLR (Address Space Layout Randomisation)
• Attacker needs detailed info about memory layout
– eg to jump to specific piece of code
– or to corrupt a pointer at known position on the stack
• Attacks become harder if we randomise the memory layout every
time we start a program
• ie. change the offset of the heap, stack, etc, in memory by
some random value
62.3
2. ASLR (Address Space Layout Randomisation)
• Attacker needs detailed info about memory layout
– eg to jump to specific piece of code
– or to corrupt a pointer at known position on the stack
• Attacks become harder if we randomise the memory layout every
time we start a program
• ie. change the offset of the heap, stack, etc, in memory by
some random value
62.4
2. ASLR (Address Space Layout Randomisation)
• Attacker needs detailed info about memory layout
– eg to jump to specific piece of code
– or to corrupt a pointer at known position on the stack
• Attacks become harder if we randomise the memory layout every
time we start a program
• ie. change the offset of the heap, stack, etc, in memory by
some random value
62.5
2. ASLR (Address Space Layout Randomisation)
• Attacker needs detailed info about memory layout
– eg to jump to specific piece of code
– or to corrupt a pointer at known position on the stack
• Attacks become harder if we randomise the memory layout every
time we start a program
• ie. change the offset of the heap, stack, etc, in memory by
some random value
62.6
3. Non-eXecutable memory (NX , WX,DEP)
Distinguish
• X: executable memory (for storing code)
• W: writeable, non-executable memory (for storing data)
and let processor refuse to execute non-executable code
63.1
3. Non-eXecutable memory (NX , WX,DEP)
Distinguish
• X: executable memory (for storing code)
• W: writeable, non-executable memory (for storing data)
and let processor refuse to execute non-executable code
Limitation: this technique does not work for JIT (Just In Time)
compilation, where e.g. JavaScript is compiled to machine code
at run time.
63.2
Defeating NX: return-to-libc attacks
With NX, code injection attacks no longer possible,
but code reuse attacks still are...
64
(ROP)
Next stage in evolution of attacks, as people removed or protected
dangerous libc calls such as system()
65
More advanced defences
[See SoK Eternal War in Memory paper]
66
Types of (building blocks for) attacks
• Code corruption attack
Overwrite the original program code in memory;
impossible with WX
• Control-flow hijack attack
Overwrite a code pointer, eg return address, jump address,
function pointer, or pointer in vtable of C++ object
• Data-only attack
Overwrite some data, eg bool isAdmin;
• Information leak
Only reading some data; recall Heartbleed attack on TLS
67
Control flow hijack via code pointers
• A compiler translates function calls in source code to
call <address> or JSR <address> in machine code
where <address> is the location of the code for the function.
68.1
Control flow hijack via code pointers
• A compiler translates function calls in source code to
call <address> or JSR <address> in machine code
where <address> is the location of the code for the function.
68.2
Control flow hijack via code pointers
• A compiler translates function calls in source code to
call <address> or JSR <address> in machine code
where <address> is the location of the code for the function.
68.3
Classification of defences [SoK paper]
• Probabilistic methods
Basic idea: add randomness to make attacks harder
– in location where certain data is located (eg ASLR),
or in the way data is represented in memory (eg pointer
encryption)
• Memory Safety
Basic idea: do additional bookkeeping & add runtime checks to
prevent some illegal memory access
69
More randomness: Pointer Encryption (PointGuard)
• Many buffer overflow attacks involve corrupting pointers,
pointers to data or code pointers
70.1
More randomness: Pointer Encryption (PointGuard)
• Many buffer overflow attacks involve corrupting pointers,
pointers to data or code pointers
• To complicate this: store pointers encrypted in main memory,
unencrypted in registers
– simple & fast encryption scheme: eg. XOR with a fixed value,
randomly chosen when a process starts
70.2
More randomness: Pointer Encryption (PointGuard)
• Many buffer overflow attacks involve corrupting pointers,
pointers to data or code pointers
• To complicate this: store pointers encrypted in main memory,
unencrypted in registers
– simple & fast encryption scheme: eg. XOR with a fixed value,
randomly chosen when a process starts
• Attacker can still corrupt encrypted pointers in memory,
but these will not decrypt to predictable values
70
More randomness: Pointer Encryption (PointGuard)
• Many buffer overflow attacks involve corrupting pointers,
pointers to data or code pointers
• To complicate this: store pointers encrypted in main memory,
unencrypted in registers
– simple & fast encryption scheme: eg. XOR with a fixed value,
randomly chosen when a process starts
• Attacker can still corrupt encrypted pointers in memory,
but these will not decrypt to predictable values
– This uses encryption to ensure integrity.
Normally NOT a good idea, but here it works.
70
More memory safety
Additional book-keeping of meta-data
& extra runtime checks to prevent illegal memory access
ptr
Different possibilities
• add information to pointer about size of memory chunks it points
to (fat pointers)
• add information to memory chunks about their size (Spatial
safety with object bounds)
• …
71
Fat pointers
The compiler
• records size information for all pointers
• adds runtime checks for pointer arithmetic & array indexing
A pointer p
s o m e d a t a
A fat pointer p size
Downsides
• Considerable execution time overhead
• Not binary compatible – ie all code needs to be compiled to add
this book-keeping for all pointers
72
More memory safety
Additional book keeping of meta-data
& extra runtime checks to prevent illegal memory access
Different possibilities ptr
• add information to pointer about size of memory chunks it points
to (fat pointers)
• add information to memory chunks about their size (Spatial
safety with object bounds)
• keep a shadow administration of this meta-data, separate from
the pointers & the existing memory (SoftBounds)
• keep a shadow administration of which memory cells have been
allocated (Valgrind, Memcheck, AddressSanitizer or ASan)
– to also spot temporal bugs, ie. malloc/free bugs
73
Object-based temporal safety (Valgrind, Memcheck, ASan)
1 1 1 1 1 1 1 1
Shadow admin
0 0 0 0 0 0 0 0
0 0 1 1 1 1 1 1
of allocated memory s o m e d a t a
o l d j u n k X
Y Z h e l l o \0
74
Guard pages to improve memory safety
Allocate chunks with the end at a page boundary with a
non-readable, non-writeable page between them
p
s o m e d a t a
q h e l l o \0
75
Control Flow Integrity (CFI)
Extra bookkeeping & checks to spot unexpected control flow
• Dynamic return integrity
Stack canaries, or shadow stack that keeps copies of all return
addresses, providing extra check against corruption of return
addresses
• Static control flow integrity
Idea: determine the control flow graph (cfg) and monitor jumps
in the control flow to spot deviant behavior
If f() never calls g(),
because g()does not even occur in the code of f(),
then call from f() to g() is suspicious,
as is a return from g() to f()
We could interrupt execution when this happens
This can detect Return-to-libc and ROP attacks
76
Static control flow integrity: example code & CFG
g()
void f() { f()
... ; g(); call g
call h
... ; g();
... ; h(); return h()
call g
...
}
void g(){ ..h();} call h return
void h(){ ... }
78
New(er) features ofinput
Typical main problem
OS [not exam material]
• Pointer
Input problems encryption
always in iOS (2018)
follow the same pattern:
1)attacker supplies some malicious input
2)application 'processes' the input
• Hardware-enforced Stack Protection in Windows 10 (2020)
a)by itself and/or
b)using
• withexternal
a shadow tools
stack, (OS, file system, SQL database, …)
3)processing 'goes
using Intel of the rails'
Control-flow Enforcement Technology (CET)
which unintentionally exposes dangerous functionality
https://techcommunity.microsoft.com/t5/windows-kernel-internals/understanding-
to the attacker
hardware-enforced-stack-protection/ba-p/1247815
79
Exam questions: you should be able to
• Explain how simple buffer overflows work & what root causes are
• Spot a simple buffer overflow, memory-allocation problem,
format string attack, or integer overflow in some C code
• Explain how countermeasures - such as stack canaries, non-
executable memory, ASLR, CFI, bounds checkers, pointer
encryption, … - work
• Explain why they might not always work