0% found this document useful (0 votes)
9 views87 pages

Part4 SW SEC Memory Corruption

Uploaded by

Harene
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPTX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
9 views87 pages

Part4 SW SEC Memory Corruption

Uploaded by

Harene
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPTX, PDF, TXT or read online on Scribd
You are on page 1/ 87

SOFTWARE

SECURITY
MEMORY CORRUPTION
Part 1: Essence of the problem

• Buffer overflow
• Other memory corruption problems
• Code injection vs code reuse
Outlines • Spotting the problem
• Format string attacks
• Preventing format string attack

Part 2: Platform-level defences


• Stack canaries
• ASLR (Address Space Layout Randomisation)
• Non-eXecutable memory (NX , W ⊕X, DEP)

Part 3: Attacks
• Return-to-libc attacks
• Return-Oriented Programming (ROP)

Part 4: Defences against attacks in Part 3


• Probabilistic methods
• Memory safety
• Control flow integrity

2
Overview
1. How do memory corruption flaws work?
2. What can be the impact?
3. How can we spot such problems in C(++) code?
4. What can ‘the platform’ do about it?
ie. the compiler, system libraries, hardware, OS, ..
5. What can the programmer do about it?

3
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’;

This is defi ned to be

ANYTHING can happen

4
undefined behaviour: Anything can
happen
// assign values to
array elements
- In C, array indexing starts from 0, so: buffer[0] = a;
buffer[4] buffer[1] = z;
refers to the 5th element of the array buffer, that doesn’t exist because buffer[2] = b;
the array has a length of 4. buffer[3] = c;
- Attempting to modify this element , i.e., buffer[4] will result in
accessing memory beyond the bounds of the array, leading to
undefined behavior.

Therefore,
assigning a value to buffer can overwrite memory locations
adjacent to the array buffer to cause memory corruption or
segmentation faults

5
undefined behaviour: Anything can
happen
Suppose in a C program you have an array of length 4 Segmentation faults in
char buffer[4]; C++ are runtime errors
that happen when a
What happens if the statement below is executed? program attempts to
buffer[4] = ‘a’; access memory that it is
not allowed to access.
If the att acker can control the value ‘a’
then anything that the att acker wants may happen

• If you are lucky , you only get a SEGMENTATION FAULT


– and you’ll know that something went wrong
• If you are unlucky , there is remote code execution (RCE)
– and you won’t know
6
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’;

A compiler could remove the statement above,


ie. do nothing
• This would be correct compilation by the C standard
because anything includes nothing
• This may be unexpected, but compilers actually do this (as
part of optimizations) and this has caused security problems;
examples later.

7
Solution to this problem

• Unfortunately, C and C++ have not adopted this solution.


• Why?

• For e f f i c i e n c y

Regrettably, people often choose performance over


security
• As a result, buffer overflows have been the no.1 security
problem in software ever since.

• Fortunately, Perl, Python, Java, C#, PHP, Javascript, and


Visual Basic do check array bounds.

8
Buff er overfl ow

• The most common security problem in (machine code


compiled from) C and C++
• ever since the first Morris Worm in 1988

• Check out CVEs mentioning buffer (or buffer%20overflow)


https://cve.mitre.org/cgi-bin/cvekey.cgi?keyword=buffer

• Ongoing arms race of attacks & defences: attacks are


getting cleverer, defeating ever better countermeasures

9
Other memory corruption problems

memory c orruption
Errors with pointers and with dynamic memory (the heap) problems; C
1. Buffer overflows
2. Pointers related errors
• Who has writt en a C(++) program that uses pointers?
• Who ever had such a program crashing?
• Who has ever writt en a C(++) program that uses dynamic
memory, i.e., malloc & free?
• Who ever had such a program crashing?

In C/C++, the programmer is responsible for


memory management, and this is very error-prone
– Technical term: C and C++ do not off er memory-
safety
10
Other memory corruption problems
Typical causes
• access outside array bounds
• 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, causing a memory leak
• not really a memory corruption issue, but rather a memory
DoS issue

11
Spot all (potential) defects

1000 …
1001 void f (){ "Dereference" refers to
1002 char* buf, buf1; the act of accessing the
possible null dereference value stored at the
1003 buf = malloc(100); (if malloc failed) memory location pointed
1004 buf[0] = ’a’;
to by a pointer.
... use-after-free; buf[0] points to de-
allocated memory
3001 free(buf);
3002 buf[0] = ’b’; memory leak; pointer buf1
3003 buf1 = malloc(100); to this memory is lost &
3004 buf[0] = ’c’ memory is never freed
3005 }

12
Spot all (potential) defects

NB: The following sequence of events must occur in the memory


in the same order as described below:

1. Allocate Memory: Use malloc to dynamically allocate


memory space.

2. Dereference: Access and manipulate the allocated memory by


dereferencing the pointer returned by malloc.

3. Free Memory: When you are done using the allocated memory,
there is a need to free it using the free function to prevent
memory leaks.

13
How does classic buff er overfl ow work?
aka smashing the stack

14
Process memory layout
Process memory layout

Stack (buffer)
Stack-vs-heap layout inside memory
Process memory layout

High Arguments/ Environment Stack grows


add resses down,
Stack by procedure
calls
Unused Memory

Heap (dynamic data) Heap grows


up,
eg. by
Static Data .data malloc

Low
add resses
Program Code .text
Stack layout
The stack consists of Activation Records:

Stack Buffer
x
AR main() return address
AR f() buf[4..7]
buf[0..3]

Stack grows void f(int x) {


Buffer grows
downwards char[8] buf;
upwards
gets(buf);
}
void main() {
Heap
f(…); …
}
void format_hard_disk(){…} 18
Stack overfl ow att ack - 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
Stack overfl ow att ack - 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(){…} 20
21
Stack overfl ow att ack - 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(){…} 22
Stack overfl ow att ack - case 2
What if gets() reads more than 8 bytes ?
Attacker can jump to his own code (aka shell code)

23
Code injection vs code reuse
The two attack scenarios in these examples
(2) is a code injection attack
attacker inserts his own shell code in a buffer and corrupts
return addresss to point to this code
In the example, exec('/bin/sh')
This is the classic buffer overflow attack
[Smashing the stack for fun and profit, Aleph One, 1996]
(1) is a code reuse attack
attacker corrupts return address to point to existing code In
the example, format_hard_disk

Lots of details to get right!


• knowing precise location of return address and other data on
stack, knowing address of code to jump to, ....
24
What to att ack? 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;
...
}

Suppose the attacker can overflow username


In addition to corrupting the return address, this might corrupt
• pointers, eg filename
• other data on the stack, eg is_super_user,diskquota
• function pointers, eg error_handler
But not j, unless the compiler chooses to allocate variables in a different order, which
the compiler is free to do.
25
What to att ack? More fun on the
heap
struct BankAccount {
int number;
char username[20];
int balance;
}

Suppose attacker can


overflow username

This can corrupt other


fields in the struct.

Which field(s) can be corrupted depends on the order of the fields in


memory, which the compiler is free to choose .

26
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*

char* str = "hello"; // a string str

str

h e l l o \0

28
Example: gets

char buf[20];
gets(buf); // read user input until
// first EoL or EoF character

• Never use gets


• gets has been removed from the C library
• Use fgets(buf, size, file) instead

29
Example: strcpy
char dest[20];
strcpy(dest, src); // copies string src to dest

• strcpy assumes dest is long enough ,


and assumes src is null-terminated
• Use strncpy(dest, src, size)
instead

Beware of difference between sizeof and strlen sizeof(dest)


= 20 // size of an array strlen(dest) = number of
chars up to first null byte
// length of a string

30
Spot the defect!
(1)
char src[9];
char dest[9];

char* base_url = "www.ab.cd";


strncpy(src, base_url, 9);
// copies base_url to src
strcpy(dest, src);
// copies src to dest

31
Spot the defect!
(1)
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.ab.cd";
strncpy(src, base_url, 9);
// copies base_url to src
strcpy(dest, src);
// copies src to dest

32
Spot the defect!
(1)
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.ab.cd”;
strncpy(src, base_url, 9); NB: a strongly typed
// copies base_url to src programming
strcpy(dest, src); language would
guarantee that
// copies src to dest strings are always
null-terminated,
without the
programmer having
so strcpy will overrun the buffer dest to worry about this...

33
Spot the defect!
(2)
char *buf;
int len;
...

buf = malloc(MAX(len,1024)); // allocate buffer


read(fd,buf,len); // read len bytes into buf

The function read(fd, buf, len) is a system


call in the C programming language used to read
data from a file descriptor (fd) -eg. an open file or
an input/output resource- into a buffer (buf) with a
specified length (len).

34
Spot the defect!
(2)
char *buf;
int len;
...

buf = malloc(MAX(len,1024)); // allocate buffer


read(fd,buf,len); // read len bytes into buf

What happens if len is negative?

The length parameter of read system call is unsigned! So


negative len is interpreted as a big positive one!

35
Spot the defect!
(2)
char *buf;
What if the malloc() fails?
int len;
(because we are out of memory)
...

if (len < 0)
{
error ("negative length");
return;
}
buf = malloc(MAX(len,1024));
read(fd,buf,len);

36
Are memory address spaces allocated? Yes
Are memory address spaces deallocated? No
Are memory address spaces full? Yes
Are memory address spaces being used at the current moment? Yes

37
Spot the defect!
(2)
char *buf;
int len;
...

if (len <
0)
{error
("negat
ive
length"
);
return;
}
buf =
malloc(MAX
(len,1024) 38
);
Bett er still
char *buf;
int len;
...

if (len <
0)
{error
("negat
ive
length"
);
return;
}
buf =
calloc(MAX
(len,1024) 39
);
Spot the defect! (3)
1. Buffer overflow
#define MAX_BUF 256 if length of (in) => (buf),
strcpy function will cause a
void BadCode (char* in) buffer overflow. This leads
{ short len; to security vulnerabilities.
char buf[MAX_BUF];

len = strlen(in);

if (len < MAX_BUF) strcpy(buf,in);


}
2. Lack of error handling
The code does not handle cases
if (strlen) >= (MAX_BUF).
This lead to unexpected
behavior or crashes.

40
Spot the defect! (3)
#define MAX_BUF 256

void BadCode (char* in)


{ short len;
char buf[MAX_BUF];

len = strlen(in);

if (len < MAX_BUF) strcpy(buf,in);


else //i.e., if len >= MAX_BUF

printf("Error: Input string is too long.\n");


// Print an error message
}

41
Exercise 1
#include <stdlib.h>

int main() {
int *arr;
int size = 10;
• Spot ONE (1) defect.
• What is the root // Allocate memory for an array of 10 integers, initialized to zero
cause? arr = (int *)malloc(size, sizeof(int));
• Propose a solution.
// Use the allocated memory
// ...

return 0;
}

42
Exercise 1
#include <stdlib.h>
memory c orruption
problems; C
int main() { 1. Buffer overflows
int *arr; 2. Pointers related errors
int size = 10; 3. Memory leakage

// Allocate memory for an array of 10 integers, initialized to zero


arr = (int *)malloc(size, sizeof(int));
• The malloc(…) is used
and it wasn’t freed. This // Use the allocated memory
leads to memory // ...
leakage.
• There is no a // Free the allocated memory when done
deallocation free(arr);
instruction to free the return 0;
allocated space. }
• As seen in the code
segment.
43
Exercise 2
#define MAX_BUF 10

void vulnerableFunction(char* input) {


char buffer[MAX_BUF];
strcpy(buffer, input);
• Spot ONE (1) defect. }
• What is the root
cause?
• Propose a solution.

44
Exercise 2
#define MAX_BUF 10

void safeFunction(char* input) {


char buffer[MAX_BUF];
// Check input length before copying to prevent buffer overflow
if (strlen(input) < MAX_BUF) {
strcpy(buffer, input);
} else {
• The defect is buffer printf("Input string is too long!\n");
overflow. }
• The root cause is the lack of }
bounds checking before
copying the input string
into the buffer.
• The solution is to
implement bounds
checking; as seen in the
code segment.
45
Exercise 3 void allocateMemory() {
int* ptr = (int*)malloc(sizeof(int));
*ptr = 10;
// Memory is not freed, leading to a memory leak
• Spot ONE (1) defect. }
• What is the root
cause?
• Propose a solution.

46
Exercise 3 void allocateMemory() {
int* ptr = (int*)malloc(sizeof(int));
*ptr = 10;
// Free allocated memory to prevent memory leak
free(ptr);
}

• The defect is a memory


leak.
• The root cause is the
absence of a deallocation
instruction.
• The solution is to add a
deallocation instruction.

47
Exercise 4
#include <stdio.h>

void dereferencePointer(int* ptr) {


// Attempting to dereference a null pointer

printf("Value at pointer: %d\n", *ptr);


• Spot ONE (1) defect.
}
• What is the root
cause?
• Propose a solution. int main() {
int* ptr = NULL;
dereferencePointer(ptr);
return 0;
}

48
Exercise 4
#include <stdio.h>

void dereferencePointer(int* ptr) {


// Check if pointer is not null before dereferencing
if (ptr != NULL) {
printf("Value at pointer: %d\n", *ptr);
} else {
printf("Pointer is null, cannot dereference.\n");
• The defect is a null pointer }
dereference. }
• The root cause is the absence of a
Null pointer
check to ensure the pointer ptr is int main() { dereference occurs
not null before dereferencing it. int* ptr = NULL; when a program
• The solution is to add a null dereferencePointer(ptr); attempts to access
or dereference a
pointer check before return 0; memory address
dereferencing ptr to prevent } that is set to null
accessing memory at a null (0), so that runtime
address. error will result.

49
NB absence of language-level security
In a safer p rogramming language than C/C++,
the programmer would not have to worry about
• writing past array bounds
(because you will get an IndexOutOfBoundsException
instead)
• implicit conversions from signed to unsigned
integers
(because the type system/compiler would forbid this
or warn)
• malloc possibly returning null
(because you'd get an OutOfMemoryException
instead)
• malloc not initialising memory
(because language could always ensure default
initialisation)
• ...
50
Spot the defect! (4)
#include <stdio.h>

int main(int argc, char* argv[])


{ if (argc > 1)
printf(argv[1]);
return 0;
}

This program is vulnerable to format string attacks, where


calling the program with strings containing special
characters can result in a buffer overflow attack.

51
Exercise 5
#include <stdio.h>
memory c orruption
problems; C
int main(int argc, char* argv[]) 1. Buffer overflows
{ if (argc > 1) 2. Pointers related errors
printf(argv[1]); 3. Memory leakage
return 0; 4. Format string attacks
• Spot ONE (1) defect.
• What is the root }
cause?
• Propose a solution.

52
memory c orruption

Exercise 5
problems; C
#include <stdio.h> 1. Buffer overflows
2. Pointers related errors
int main(int argc, char* argv[]) { 3. Memory leakage
if (argc > 1) { 4. Format string attacks
// Use printf with a format specifier (%s) to print user-supplied input as a string
printf("%s\n", argv[1]);
}
return 0;
• The defect is the code is vulnerable }
to format string attacks.
• The root cause is directly using the
printf function with the user-
supplied input argv[1] without any
formatting specifier.
• The solution is to use printf with a
format specifier such as %s to
specify that the input should be
treated as a string.
53
Format string att acks
New type of memory corruption discovered in 2000

No attack
• Strings can contain special characters, eg %s in
printf(“Cannot find file %s”, filename);
Such strings are called format strings

• What happens if we execute the code below?

An attack
printf("Cannot find file %s");
• What can happen if we execute
printf(string)
where string is user-supplied ?
Esp. if it contains special characters, eg %s, %x, %n, %hn?

54
Format string att acks
Suppose attacker can feed malicious input string s to printf(s). This can:
• Read the stack
%x reads and prints bytes from stack, so input as:
%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..., 
will dump the stack, including passwords, keys,… stored on the
stack, as an output

• Corrupt the stack


%n writes the number of characters p rinted to the stack, so input as:
12345678%n  will write value 8 to the stack as an output

• Read arbitrary memory


a carefully crafted format string of the form
\xEF\xCD\xCD\xAB %x%x...%x%s, 
will print string at memory address ABCDCDEF as an output

55
To exploit format string attacks and read arbitrary memory,
what output does a carefully crafted format string produce
when the input is “\xEF\xCD\xCD\xAB %x%x...%x%s”?

A) Print the memory address ABCDCDEF


B) Print string at memory address ABCDCDEF
C) Print string at memory address EFCDCDAB
D) None of the above
B

Which of the following actions is not possible in a format string


attack using printf(s)?
a) Reading the stack using %x format specifier
b) Corrupting the stack using %n format specifier
c) Reading arbitrary memory using a crafted format string
d) None of the above

d
Preventing format string att acks
• Always replace printf(str)
with printf("%s", str)

• Compiler or static analysis tool could warn if the number of


arguments does not match the format string, eg in
printf ("x is %i and y is %i", x); 2 %i vs 1 arg

eg gcc has (far too many?) command line options for this:
-Wformat –Wformat-no-literal –Wformat-
security ...

If the format string is not a compile-time constant, we cannot


decide this at compile time, so compiler has to give false
positives or false negatives

See https://cve.mitre.org/cgi-bin/cvekey.cgi?keyword=format+string
to see how common format strings still are
57
Recap: buff er overfl ows
• 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 overflow
• but just the integer overflow can already have a
security impact; e.g., think of banking software

58
Platform-level defences
Platform-level defences
Platform-level defenses – PLDs: are protective measures
implemented at the level of the underlying platform or
operating system (OS) to mitigate vulnerabilities and
potential attacks.

PLDs include features such as:


• address space layout randomization (ASLR),
• data execution prevention (DEP) W⊕X
• stack canaries, and
• checks on control flow.

60
Platform-level defences A library is
• PLDs are defences where the compiler, hardware, or binary
compatible,
OS can take, without the programmer having to if a program
know. linked
dynamically
• Some defenses may need OS & hardware support to a former
• Some defenses cause overhead version of
the library
– if the overhead is unacceptable in production code, we continues
can running with
still use it when testing newer
• Some defenses may break binary compatibility versions of
the library
– eg if a compiler adds extra book-keeping & checks, without the
then all need to
libraries may need to be re-compiled with that compiler recompile.

61
PLDs
1. Stack canaries
now standard
2. Non-executable memory (NX, W⊕X) on many
platforms
3. Address space layout randomization (ASLR)

More advanced defenses


1. More randomisation: eg. pointer & memory encryption
2. More memory safety checks:
eg. checks on bounds (spatial) or on allocation (temporal)
3. Checks on control flow
4. Execution-aware memory protection
History shows that all new defenses are eventually defeated...

62
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

• First introduced in as StackGuard in gcc


• Only very small runtime overhead

63
Stack canaries

Stack without canary Stack with canary


x
x return address
return address canary value
buf[4..7] buf[4..7]
buf[0..3] buf[0..3]

64
Further improvements
1. More variation in canary values: eg not fixed values hardcoded in
binary but a random value chosen for each execution.
2. Better still, XOR the return address into the canary value
3. Include a null byte in the canary value, because C string
functions cannot write nulls inside strings

A careful attacker can still defeat canaries, by


• overwriting the canary with the correct value
• corrupting a pointer ptr (Figure A) to make it point to the return
address first (Figure B), then to change value inside the return address
(Figure C) without killing the canary.
return return return
eg changing canary value to
canary value canary value
char* ptr char* ptr char* ptr
buf[4..7] buf[4..7] buf[4..7]
Figure A buf[0..3] buf[0..3] Figure B buf[0..3] Figure C

65
Further improvements
4. 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 allocate array buffers ‘above’ all other
local variables
First introduced by IBM’s ProPolice.

5. A separate shadow stack (SS)


Interesting

• with copies of return addresses, used to check for corrupted


return addresses
• of course, the attacker should not be able to corrupt the
shadow stack (SS)

66
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

• Countermeasure: only allow transfer of control to registered


exception handlers

67
Q: What countermeasure is proposed to mitigate the exploitation
of corrupted exception handler information and canaries in the
stack protection mechanism?
A: Only allow transfer of control to registered exception handlers.

Q: Platform-level defenses (PLDs): are protective measures implemented at


the underlying platform level or operating system to mitigate vulnerabilities.
Give TWO (2) more examples and define each of other PLDs.
• Data execution prevention (DEP): DEP lets processor refuse to execute non-
executable code so that attackers can no longer jump to their own attack
code.
• Address space layout randomization (ASLR). In ASLR, attacks become
harder, if we randomize the memory layout every time we start a program.
• Stack canaries: A dummy value - stack canary or cookie - is written on the
stack in front of the return address and checked.
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

• Attackers can still analyse memory layout on their own laptop, but
will have to determine the offsets used on the victim’s machine
to carry out an attack

• NB security by obscurity, is a really great defense mechanism to


annoy attackers!
• Once the offset leaks, we are back to square one…

69
3. Non-eXecutable memory (NX , W⊕X, 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

Attackers can then no longer jump to their own attack code, as


any input is considered as an attack code will be non-executable

Aka DEP (Data Execution Prevention).


Intel calls it eXecute-Disable (XD)
AMD calls it Enhanced Virus Protection

Limitation: this technique does not work for JIT (Just In Time)
compilation, where e.g. JavaScript is compiled to machine code at
run time.

70
Q: How does the use of a shadow stack (SS) enhance platform-level defenses
against buffer overflow attacks?
A: Use of SS enhances PLDs against buffer overflow attacks by providing copies of
return addresses to check for corruption.

Q: Which of the following actions reduces Q: Which enhancement variation serves as


the potential impact of overruns on the a protective measure alongside canaries,
stack? involving copies of return addresses to
a) Swapping parameters buffers and function check for corruption?
pointer (fp) a) Data execution prevention (DEP)
b) Allocating array buffers below all other b) Address space layout randomization
local variables (ASLR)
c) Allocating array buffers above all other c) Stack canaries
local variables d) Shadow stack (SS)
d) Rearranging the order of parameters in
function calls A: d

A: c
Att acks, Defeating NX
Defeating NX: Return-to-libc att acks
With NX, code injection attacks no longer possible,
but code reuse attacks still are... (see slide 24).

• Attackers can no longer corrupt code or insert


their own code,
but can still corrupt code pointers
• Called control-flow hijack

So instead of jumping to own attack code corrupt


return address to jump to existing code esp.
library code in libc
libc is a rich library that offers lots of functionality, eg.
system(), exec(),
which provides attackers with all they need...

73
Return-Oriented Programming (ROP)
Next stage in evolution of attacks, as people removed or protected
dangerous libc calls such as system()

Instead of using entire library call, attackers can:


1. look for gadgets, small snippets of code which end with a return, in
the existing code base
...; ins1 ; ins2 ; ins3 ; ret
2. chain these gadgets together as subroutines to form a program
that does what they want

This turns out to be doable


• Most libraries contain enough gadgets to provide a Turing
complete programming language
• ROP compilers can then translate arbitrary code to a string of
these gadgets

A newer variant is Jump-Oriented Programming (JOP) which uses


a different kind of code fragment as gadgets

74
Q: What is Return-Oriented Programming (ROP) and how does it work?
A: Return-Oriented Programming (ROP) is an advanced attack technique where
attackers chain together small snippets of existing code, known as gadgets, to
form a malicious program.

Q: Return-Oriented Programming (ROP)


is:
a) A method to protect dangerous libc calls
b) A technique that uses entire library calls
c) A method of chaining gadgets together to
form a program
d) A method that relies on system calls to
execute code

A: c
More advanced defences
Goals / Building blocks of att acks
• Code corruption attack
Overwrite the original program code in memory;
impossible with W⨁X
• Control-flow hijack attack
Overwrite a code pointer, eg return address, jump address,
function pointer, or pointer in the virtual function table (vtable)
C++ object
• Data-only attack
Overwrite some data, eg bool isAdmin;
• Information leak
Only reading some data; recall Heartbleed attack on TLS

77
Q: Which attack type involves overwriting a
code pointer, such as a return address or
function pointer?
a) Control-flow hijack attack
b) Data-only attack
c) Code corruption attack
d) Information leak

A: a

Q: Which of the following attack types


involves overwriting the original program
code in memory?
a) Control-flow hijack attack
b) Data-only attack
c) Code corruption attack
d) Information leak

A: c
Classification of defences
• 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

• Control-Flow Hijack Defenses


Basic idea: do additional bookkeeping & add runtime check to
prevent strange control flow

79
More randomness: Pointer Encryption (PointGuard)

Probabilistic
methods
• 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: 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
– We can still use encryption to ensure integrity
of the corrupted pointers . Normally NOT a
good idea, but here it works.
• Next step: Data Space Randomisation (DSR)
– encrypt not just pointers, but store all data
encrypted in
memory

80
More memory safety
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)
• …

81
Fat pointers
Memory
safety
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

82
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 (Static CFI)
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 suspect, as is a
return fro m g() to f()
This can detect Return-to-libc and ROP attacks

83
Q: Which type of attacks can Static Control
Flow Integrity (Static CFI) detect?
a) Injection attacks
b) Format string attacks
c) Return-to-libc and ROP attacks
d) Code reuse attack

A: c
Static control fl ow integrity
g()
void f() { f()
... ; g(); call g
call h
... ; g();
... ; h(); return h()
call g
...
}
void g(){ ..h();} call h return
void h(){ ... }

Before and/or after every control transfer (function call or return) we could check if
it is legal – ie. allowed by the cfg
Some weird returns would still be allowed
• eg if we call h() from g(), and the return is to f(), this would be allowed by the static
cfg
• Additional dynamic return integrity check can narrow this down to actual call site –
using recorded call site on shadow stack

85
Downsides of static control
fl ow integrity checks

• Requires a whole program analysis


• Use of function pointers in C or virtual functions in C++ (that both
result in so-called indirect control transfers) complicate
compile-time analysis of the cfg: we’d need
• a points-to analysis to determine where such code pointers can
point to
eg in C++, if Animal->eat() can resolve to if f->call(h)
Cat->eat() or Dog->eat(), so g->call(h) or …-
both are valid targets for transferring control >call(h)
• or: simply allow transfer to any function entry point

86
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

You might also like